Skip to content

Commit

Permalink
Switch from RSA4096 to 3072 due to HSM size limitations. Fix session …
Browse files Browse the repository at this point in the history
…issues.
  • Loading branch information
elonen committed Sep 18, 2024
1 parent 3db73e6 commit 1057b79
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 63 deletions.
66 changes: 33 additions & 33 deletions hsm-conf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ admin:
audit:
# Specify logging/audit policies for the devices.
# 'fixed' is like 'on', but cannot be turned off again except by a factory reset
forced_audit: 'off' # If on/fixed, HSM refuses further commands until log is audited when it fills up
forced_audit: 'on' # If on/fixed, HSM refuses further commands until log is audited when it fills up
default_command_logging: 'on' # Default for commands not listed below
command_logging: # Overrides for specific commands
reset-device: 'fixed'
Expand Down Expand Up @@ -258,7 +258,7 @@ service_keys:
# This is why the user keys are not allowed to call this capability wantonly.
- label: svc_log-audit
id: 0x0008
domains: []
domains: ['all']
capabilities: [
'get-log-entries',
'set-option',
Expand All @@ -273,7 +273,7 @@ service_keys:
- label: svc_attestation
id: 0x0009
domains: ['all']
capabilities: ['sign-attestation-certificate', 'get-opaque', 'change-authentication-key']
capabilities: ['sign-attestation-certificate', 'get-opaque', 'change-authentication-key', 'exportable-under-wrap']
delegated_capabilities: []

# Service key for NAC (Network Access Control) for
Expand All @@ -300,25 +300,25 @@ x509:
root_certs:
-
key:
label: key_ca-root-a1-rsa4096
label: key_ca-root-a1-rsa3072
id: 0x0110
domains: ['x509']
algorithm: rsa4096
algorithm: rsa3072
capabilities:
- sign-pss # prefer this for RSA
- sign-pkcs
- exportable-under-wrap
crl_distribution_points:
- "{{CRL_URL}}/root-a1-rsa4096.crl"
- "{{CRL_URL}}/root-a1-rsa3072.crl"
x509_info: &ROOT_COMMON_CERT_INFO
validity_days: 7300 # 20 years
basic_constraints:
path_len: null # No limit for root CAs
attribs:
common_name: '{{ ORG_NAME }} Root A1 RSA4096'
common_name: '{{ ORG_NAME }} Root A1 RSA3072'
signed_certs: # Certificates to create (and store in HSM) for this key
- id: 0x0111
label: cert_ca-root-a1-rsa4096
label: cert_ca-root-a1-rsa3072
domains: ['x509', 'tls', 'nac', 'piv', 'gpg', 'codesign'] # Allow the root cert (though not the key) to be read by all services
algorithm: opaque-x509-certificate
sign_by: 0x0111 # Root CA signs its own certificate
Expand Down Expand Up @@ -375,29 +375,29 @@ tls:
intermediate_cas:
-
key:
label: key_tls-t1-rsa4096
label: key_tls-t1-rsa3072
id: 0x0210
domains: ['tls']
algorithm: rsa4096
algorithm: rsa3072
capabilities:
- sign-pss
- sign-pkcs
- exportable-under-wrap
crl_distribution_points:
- "{{CRL_URL}}/tls-t1-rsa4096.crl"
- "{{CRL_URL}}/tls-t1-rsa3072.crl"
x509_info: &TLS_COMMON_CERT_INFO
basic_constraints:
path_len: 0 # Allow end-entity certificate signing only
name_constraints:
<<: *TLS_NAME_CONSTRAINTS
attribs:
common_name: '{{ ORG_NAME }} TLS Intermediate T1 RSA4096'
common_name: '{{ ORG_NAME }} TLS Intermediate T1 RSA3072'
signed_certs:
- id: 0x0211
label: cert_tls-t1-rsa4096
label: cert_tls-t1-rsa3072
domains: ['tls'] # Only allow TLS services to read this cert
algorithm: opaque-x509-certificate
sign_by: 0x0111 # RSA4096 Root CA
sign_by: 0x0111 # RSA3072 Root CA

-
key:
Expand All @@ -416,10 +416,10 @@ tls:
common_name: '{{ ORG_NAME }} TLS Intermediate T1 Ed25519'
signed_certs: # Cross-sign with legacy certs for compatibility
- id: 0x0221
label: cert_tls-t1-ed25519_rsa4096-root
label: cert_tls-t1-ed25519_rsa3072-root
domains: ['tls']
algorithm: opaque-x509-certificate
sign_by: 0x0111 # RSA4096 Root CA
sign_by: 0x0111 # RSA3072 Root CA
- id: 0x0222
label: cert_tls-t1-ed25519_ed25519-root
domains: ['tls']
Expand Down Expand Up @@ -449,7 +449,7 @@ tls:
common_name: '{{ ORG_NAME }} TLS Intermediate T1 ECP384'
signed_certs:
- id: 0x0231
label: cert_tls-t1-ecp384_rsa4096-root
label: cert_tls-t1-ecp384_rsa3072-root
domains: ['tls']
algorithm: opaque-x509-certificate
sign_by: 0x0111 # RSA Root CA
Expand All @@ -464,24 +464,24 @@ tls:
# A partner might not to trust this CA, but it could be used for internal services.
-
key:
label: key_tls-tu1-rsa4096
label: key_tls-tu1-rsa3072
id: 0x021A
domains: ['tls']
algorithm: rsa4096
algorithm: rsa3072
capabilities:
- sign-pss
- sign-pkcs
- exportable-under-wrap
crl_distribution_points:
- "{{CRL_URL}}/tls-tu1-rsa4096.crl"
- "{{CRL_URL}}/tls-tu1-rsa3072.crl"
x509_info:
<<: *TLS_COMMON_CERT_INFO
name_constraints: {} # Remove name constraints
attribs:
common_name: '{{ ORG_NAME }} TLS Unconstrained TU1 RSA4096'
common_name: '{{ ORG_NAME }} TLS Unconstrained TU1 RSA3072'
signed_certs:
- id: 0x021B
label: cert_tls-tu1-rsa4096
label: cert_tls-tu1-rsa3072
domains: ['tls']
algorithm: opaque-x509-certificate
sign_by: 0x0111
Expand Down Expand Up @@ -650,10 +650,10 @@ ssh:
default_host_ca: 0x0520 # You can separate these, but given the HSM, having to change one but not the other is unlikely
root_ca_keys:

- label: key_ssh-root-ca-rsa4096
- label: key_ssh-root-ca-rsa3072
id: 0x0510
domains: ['ssh']
algorithm: rsa4096
algorithm: rsa3072
capabilities:
- sign-ssh-certificate
- sign-pss
Expand Down Expand Up @@ -694,21 +694,21 @@ ssh:
gpg:
keys:

- label: key_gpg-g1-rsa4096-sca
- label: key_gpg-g1-rsa3072-sca
id: 0x0610
domains: ['gpg']
algorithm: rsa4096
algorithm: rsa3072
capabilities:
- sign-pss # preferred
- sign-pkcs
- decrypt-oaep
- decrypt-pkcs
- exportable-under-wrap

- label: key_gpg-g1-rsa4096-e
- label: key_gpg-g1-rsa3072-e
id: 0x0611
domains: ['gpg']
algorithm: rsa4096
algorithm: rsa3072
capabilities:
- sign-pss # preferred
- sign-pkcs
Expand Down Expand Up @@ -741,19 +741,19 @@ codesign:
certs:
-
key:
label: key_codesign-cs1-rsa4096
label: key_codesign-cs1-rsa3072
id: 0x0710
domains: ['codesign']
algorithm: rsa4096
algorithm: rsa3072
capabilities:
- sign-pss
- sign-pkcs
- exportable-under-wrap
crl_distribution_points:
- "{{CRL_URL}}/codesign-cs1-rsa4096.crl"
- "{{CRL_URL}}/codesign-cs1-rsa3072.crl"
x509_info: &CODESIGN_COMMON_CERT_INFO
attribs:
common_name: '{{ ORG_NAME }} Code Signing CS1 RSA4096'
common_name: '{{ ORG_NAME }} Code Signing CS1 RSA3072'
key_usage:
usages:
- digitalSignature
Expand All @@ -765,7 +765,7 @@ codesign:
- timeStamping
signed_certs:
- id: 0x0711
label: cert_codesign-cs1-rsa4096
label: cert_codesign-cs1-rsa3072
domains: ['codesign']
algorithm: opaque-x509-certificate
sign_by: 0x0111
Expand Down
6 changes: 5 additions & 1 deletion hsm_secrets/tls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def cmd_tls(ctx: click.Context):
@click.option('--san-dns', '-d', multiple=True, help="DNS SAN (Subject Alternative Name)")
@click.option('--san-ip', '-i', multiple=True, help="IP SAN (Subject Alternative Name)")
@click.option('--validity', '-v', default=365, help="Validity period in days")
@click.option('--keyfmt', '-f', type=click.Choice(['rsa4096', 'ed25519', 'ecp256', 'ecp384']), default='ecp384', help="Key format")
@click.option('--keyfmt', '-f', type=click.Choice(['rsa2048', 'rsa3072', 'rsa4096', 'ed25519', 'ecp256', 'ecp384']), default='ecp384', help="Key format")
@click.option('--sign-ca', '-s', type=str, required=False, help="CA ID (hex) or label to sign with, or 'self'. Default: use config", default=None)
def server_cert(ctx: HsmSecretsCtx, out: click.Path, common_name: str, san_dns: list[str], san_ip: list[str], validity: int, keyfmt: str, sign_ca: str):
"""Create a TLS server certificate + key
Expand Down Expand Up @@ -85,6 +85,10 @@ def server_cert(ctx: HsmSecretsCtx, out: click.Path, common_name: str, san_dns:
priv_key: PrivateKeyOrAdapter
if keyfmt == 'rsa4096':
priv_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
elif keyfmt == 'rsa3072':
priv_key = rsa.generate_private_key(public_exponent=65537, key_size=3072)
elif keyfmt == 'rsa2048':
priv_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
elif keyfmt == 'ed25519':
priv_key = ed25519.Ed25519PrivateKey.generate()
elif keyfmt == 'ecp256':
Expand Down
40 changes: 20 additions & 20 deletions hsm_secrets/user/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,31 +163,31 @@ def add_service(ctx: HsmSecretsCtx, obj_ids: tuple[str], all_accts: bool, askpw:

for ad in acct_defs:
with open_hsm_session(ctx, HSMAuthMethod.DEFAULT_ADMIN) as ses:

if not confirm_and_delete_old_yubihsm_object_if_exists(ses, ad.id, yubihsm.defs.OBJECT.AUTHENTICATION_KEY, abort=False):
cli_warn(f"Skipping service user '{ad.label}' (ID: 0x{ad.id:04x})...")
continue

cli_info(f"Adding service user '{ad.label}' (ID: 0x{ad.id:04x}) to device {ctx.hsm_serial}...")
if askpw:
pw = prompt_for_secret(f"Enter password for service user '{ad.label}'", confirm=True)
else:
rnd = ses.get_pseudo_random(16)
pw = group_by_4(rnd.hex()).replace(' ', '-')
retries = 0
while True:
retries += 1
if retries > 5:
raise click.Abort("Too many retries. Aborting.")
cli_pause("Press ENTER to reveal the generated password.")
secure_display_secret(pw)
confirm = cli_prompt("Enter the password again to confirm", hide_input=True)
if confirm != pw:
cli_ui_msg("Passwords do not match. Try again.")
continue
else:
break
cli_info(f"Adding service user '{ad.label}' (ID: 0x{ad.id:04x}) to device {ctx.hsm_serial}...")
if askpw:
pw = prompt_for_secret(f"Enter password for service user '{ad.label}'", confirm=True)
else:
rnd = secrets.token_bytes(16)
pw = group_by_4(rnd.hex()).replace(' ', '-')
retries = 0
while True:
retries += 1
if retries > 5:
raise click.Abort("Too many retries. Aborting.")
cli_pause("Press ENTER to reveal the generated password.")
secure_display_secret(pw)
confirm = cli_prompt("Enter the password again to confirm", hide_input=True)
if confirm != pw:
cli_ui_msg("Passwords do not match. Try again.")
continue
else:
break

with open_hsm_session(ctx, HSMAuthMethod.DEFAULT_ADMIN) as ses:
#hsm_put_derived_auth_key(ses, ctx.hsm_serial, ctx.conf, ad, pw)
confirm_and_delete_old_yubihsm_object_if_exists(ses, ad.id, yubihsm.defs.OBJECT.AUTHENTICATION_KEY)
info = ses.auth_key_put_derived(ad, pw)
Expand Down
21 changes: 16 additions & 5 deletions hsm_secrets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import yubikit.core
import yubikit.hsmauth as hsmauth

from hsm_secrets import yubihsm
import hsm_secrets.config as hscfg
import unicurses as curses # type: ignore [import]
import click
Expand Down Expand Up @@ -382,6 +383,16 @@ def open_hsm_session(
yield RealHSMSession(ctx.conf, session=ses, dev_serial=int(device_serial))



def _close_hsm_session(ses):
try:
ses.close()
except YubiHsmDeviceError as e:
if e.code == ERROR.INVALID_SESSION:
cli_warn("YubiHSM session invalidated. Already closed.")
else:
raise

@contextmanager
def open_hsm_session_with_yubikey(ctx: HsmSecretsCtx, device_serial: str|None = None) -> Generator[AuthSession, None, None]:
"""
Expand All @@ -395,7 +406,7 @@ def open_hsm_session_with_yubikey(ctx: HsmSecretsCtx, device_serial: str|None =
try:
yield session
finally:
session.close()
_close_hsm_session(session)


@contextmanager
Expand Down Expand Up @@ -430,8 +441,8 @@ def open_hsm_session_with_default_admin(ctx: HsmSecretsCtx, device_serial: str|N
yield session
finally:
cli_info(click.style(f"Closing HSM session {session.sid}.", fg='magenta'))
session.close()
hsm.close()
_close_hsm_session(session)
_close_hsm_session(hsm)


@contextmanager
Expand All @@ -454,8 +465,8 @@ def open_hsm_session_with_password(ctx: HsmSecretsCtx, auth_key_id: int, passwor
try:
yield RealHSMSession(ctx.conf, session=session, dev_serial=int(device_serial))
finally:
session.close()
hsm.close()
_close_hsm_session(session)
_close_hsm_session(hsm)


def pretty_fmt_yubihsm_object(info: ObjectInfo) -> str:
Expand Down
8 changes: 4 additions & 4 deletions run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ test_tls_certificates() {
run_cmd -q x509 cert get --all | openssl x509 -text -noout
assert_success

for KEYTYPE in ed25519 ecp256 ecp384 rsa4096; do
for KEYTYPE in ed25519 ecp256 ecp384 rsa3072; do
KEYBITS=$(echo $KEYTYPE | sed -E 's/[^0-9]//g')

local output=$(run_cmd tls server-cert --out $TEMPDIR/www-example-com_$KEYTYPE.pem --common-name www.example.com --san-dns www.example.org --san-ip 192.168.0.1 --san-ip fd12:123::80 --keyfmt $KEYTYPE)
Expand Down Expand Up @@ -379,7 +379,7 @@ test_ssh_user_certificates() {

# RSA key
ssh-keygen -t rsa -b 2048 -f $TEMPDIR/testkey_rsa -N '' -C 'testkey'
run_cmd ssh sign-user -u test.user --ca key_ssh-root-ca-rsa4096 -p users,admins $TEMPDIR/testkey_rsa.pub
run_cmd ssh sign-user -u test.user --ca key_ssh-root-ca-rsa3072 -p users,admins $TEMPDIR/testkey_rsa.pub
assert_success

# ECDSA 256 key
Expand Down Expand Up @@ -451,11 +451,11 @@ test_codesign_sign_osslsigncode_hash() {
[ -f "$test_dir/tiny.signed.req" ] || { echo "ERROR: Signed file not created"; return 1; }

# Get the full certificate chain from HSM
run_cmd x509 cert get --bundle "$test_dir/bundle.pem" cert_codesign-cs1-rsa4096 cert_ca-root-a1-rsa4096
run_cmd x509 cert get --bundle "$test_dir/bundle.pem" cert_codesign-cs1-rsa3072 cert_ca-root-a1-rsa3072
assert_success

# Create a CRL
run_cmd x509 crl init --ca cert_ca-root-a1-rsa4096 -o "$test_dir/crl.pem"
run_cmd x509 crl init --ca cert_ca-root-a1-rsa3072 -o "$test_dir/crl.pem"
assert_success

# Attach the signature to the executable
Expand Down

0 comments on commit 1057b79

Please sign in to comment.