From 1057b79ab838afc9f4915de509eb566389bdf5d4 Mon Sep 17 00:00:00 2001 From: Jarno Elonen Date: Wed, 18 Sep 2024 21:43:37 +0300 Subject: [PATCH] Switch from RSA4096 to 3072 due to HSM size limitations. Fix session issues. --- hsm-conf.yml | 66 ++++++++++++++++++------------------ hsm_secrets/tls/__init__.py | 6 +++- hsm_secrets/user/__init__.py | 40 +++++++++++----------- hsm_secrets/utils.py | 21 +++++++++--- run-tests.sh | 8 ++--- 5 files changed, 78 insertions(+), 63 deletions(-) diff --git a/hsm-conf.yml b/hsm-conf.yml index 49ab020..3540278 100644 --- a/hsm-conf.yml +++ b/hsm-conf.yml @@ -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' @@ -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', @@ -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 @@ -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 @@ -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: @@ -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'] @@ -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 @@ -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 @@ -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 @@ -694,10 +694,10 @@ 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 @@ -705,10 +705,10 @@ gpg: - 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 @@ -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 @@ -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 diff --git a/hsm_secrets/tls/__init__.py b/hsm_secrets/tls/__init__.py index 3e27e89..f55b115 100644 --- a/hsm_secrets/tls/__init__.py +++ b/hsm_secrets/tls/__init__.py @@ -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 @@ -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': diff --git a/hsm_secrets/user/__init__.py b/hsm_secrets/user/__init__.py index 8981968..e693cca 100644 --- a/hsm_secrets/user/__init__.py +++ b/hsm_secrets/user/__init__.py @@ -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) diff --git a/hsm_secrets/utils.py b/hsm_secrets/utils.py index 724b66a..8237634 100644 --- a/hsm_secrets/utils.py +++ b/hsm_secrets/utils.py @@ -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 @@ -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]: """ @@ -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 @@ -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 @@ -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: diff --git a/run-tests.sh b/run-tests.sh index dfd38b6..5e265f1 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -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) @@ -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 @@ -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