From d2aa13454091910fffbe25b62eb306922d28f2c3 Mon Sep 17 00:00:00 2001 From: Matt Dean Date: Wed, 15 Oct 2025 13:06:50 +0100 Subject: [PATCH 1/3] [NRL-1700] Add rotation tools to truststore.sh --- scripts/truststore.sh | 215 ++++++++++++++++++++++++++++++---- truststore/config/client.conf | 1 - 2 files changed, 190 insertions(+), 26 deletions(-) diff --git a/scripts/truststore.sh b/scripts/truststore.sh index 1cc0e915f..16360d4c0 100755 --- a/scripts/truststore.sh +++ b/scripts/truststore.sh @@ -1,5 +1,13 @@ #!/bin/bash # Script to manage the NRLF truststore files +# +# Note that all creative and destructive operations are local only apart from +# "push-all". Files are not uploaded to or deleted from S3 automatically. +# +# Once changes have been made locally and have been tested thoroughly, the files +# in the truststore directories can be uploaded to S3 using the appropriate AWS +# CLI commands or the "push-all" command below. +# set -o errexit -o pipefail -o nounset BUCKET="nhsd-nrlf--truststore" @@ -14,8 +22,14 @@ function _truststore_help() { echo " build-cert - Build a single client cert + private key" echo " build-all - Build the standard trust store certs" echo " pull-ca - Pull the certificate authority" + echo " pull-ca-key - Pull the certificate authority private key" echo " pull-client - pull the files needed for a client connection" echo " pull-server - pull the files needed for a server connection" + echo " rotate-ca - rotate the certificate authority, archiving the previous one" + echo " rotate-client - rotate the client certificate, archiving the previous one" + echo " disable-archived-ca - disable an archived certificate authority" + echo " restore-archived-ca - restore an archived certificate authority" + echo " restore-archived-client - restore an archived client certificate" echo } @@ -40,80 +54,175 @@ function _truststore_build_ca() { exit 1 fi - ca=$1 + env=$1 fqdn=$2 substitute_env_in_file ./truststore/config/ca.conf /tmp/ca.conf - echo -e "🚧 Building CA: $ca \t(FQDN: $fqdn)" + echo -e "🚧 Building CA: $env \t(FQDN: $fqdn)" openssl req -newkey rsa:4096 \ -nodes \ - -keyout ./truststore/ca/$ca.key \ + -keyout ./truststore/ca/$env.key \ -new \ -x509 \ -days 36500 \ - -out ./truststore/ca/$ca.crt \ + -out ./truststore/ca/$env.crt \ -config /tmp/ca.conf \ -extensions v3_req \ -extensions v3_ca &> /dev/null rm /tmp/ca.conf - echo -e "✅ Successfully Built CA: $ca" + echo -e "✅ Successfully Built CA: $env (FQDN: $fqdn)" + + cat ./truststore/ca/$env.crt > ./truststore/server/$env.pem + echo -e "✅ Successfully Built Server Truststore: $env" } -# buld a certificate +# build a certificate function _truststore_build_cert() { if [ $# -ne 3 ]; then echo "Usage: $0 build-cert " exit 1; fi - client=$1 - ca=$2 + cert_name=$1 + ca_name=$2 fqdn=$3 serial=$(date +%s%3N) substitute_env_in_file ./truststore/config/client.conf /tmp/client.conf - echo -e "🚧 Generating $client keypair" + echo -e "🚧 Generating $cert_name keypair" openssl req \ -newkey rsa:4096 \ -nodes \ - -keyout truststore/client/$client.key \ + -keyout truststore/client/$cert_name.key \ -new \ - -out truststore/client/$client.csr \ + -out truststore/client/$cert_name.csr \ -config /tmp/client.conf \ -extensions v3_req \ -extensions usr_cert &> /dev/null + echo -e "🚧 Signing $cert_name certificate with ca=$ca_name (serial: $serial)" + openssl x509 \ -req \ - -in truststore/client/$client.csr \ - -CA truststore/ca/$ca.crt \ - -CAkey truststore/ca/$ca.key \ + -in truststore/client/$cert_name.csr \ + -CA truststore/ca/$ca_name.crt \ + -CAkey truststore/ca/$ca_name.key \ -set_serial $serial \ - -out truststore/client/$client.crt \ + -out truststore/client/$cert_name.crt \ -days 36500 \ -sha256 \ -extfile /tmp/client.conf \ -extensions v3_req \ -extensions usr_cert &> /dev/null - cat truststore/client/$client.crt truststore/ca/$ca.crt > truststore/server/$client.pem - - echo -e "✅ Successfully generated $client keypair" + echo -e "✅ Successfully generated $cert_name keypair for ca=$ca_name (FQDN: $fqdn)" rm /tmp/client.conf + rm truststore/client/$cert_name.csr +} + +# Rotate a CA - archive the existing CA and build a new one +# The previous CA is added to the new CA bundle (can be removed after clients have updated) +function _truststore_rotate_ca() { + if [ $# -ne 2 ]; then + echo "Usage: $0 rotate-ca " + exit 1; + fi + + env=$1 + fqdn=$2 + + # Archive the existing ca certs + archive_date=$(date +%Y-%m-%d) + if [ -f "truststore/ca/$env.archived_$archive_date.crt" ] || + [ -f "truststore/ca/$env.archived_$archive_date.key" ] || + [ -f "truststore/server/$env.archived_$archive_date.pem" ]; then + echo "Error: Archive files already exist for date $archive_date - please resolve before rotating the CA" + exit 1 + fi + + mv truststore/ca/$env.crt truststore/ca/$env.archived_$archive_date.crt + mv truststore/ca/$env.key truststore/ca/$env.archived_$archive_date.key + mv truststore/server/$env.pem truststore/server/$env.archived_$archive_date.pem + + # Build a new CA + _truststore_build_ca $env $fqdn + + # Add the previous CA to the new CA bundle (can be removed after clients have updated) + cat truststore/ca/$env.archived_$archive_date.crt >> truststore/server/$env.pem + + echo -e "✅ Successfully rotated CA for $env - previous CA archived with date: $archive_date" +} + +# Rotate a client cert - archive the existing cert and build a new one +function _truststore_rotate_cert() { + if [ $# -ne 3 ]; then + echo "Usage: $0 rotate-cert " + exit 1; + fi + + cert_name=$1 + ca_name=$2 + fqdn=$3 + + # Archive the existing client certs + archive_date=$(date +%Y-%m-%d) + if [ -f "truststore/client/$cert_name.archived_$archive_date.crt" ] || + [ -f "truststore/client/$cert_name.archived_$archive_date.key" ]; then + echo "Error: Archive files already exist for date $archive_date - please resolve before rotating the client cert" + exit 1 + fi + + mv truststore/client/$cert_name.crt truststore/client/$cert_name.archived_$archive_date.crt + mv truststore/client/$cert_name.key truststore/client/$cert_name.archived_$archive_date.key + + # Build a new client cert + _truststore_build_cert $cert_name $ca_name $fqdn + + echo -e "✅ Successfully rotated client cert for $cert_name - previous cert archived with date: $archive_date" +} + +# Disable an archived CA by removing it from the server pem file +function _disable_archived_ca() { + env=$1 + cat truststore/ca/$env.crt > truststore/server/$env.pem + + echo -e "✅ Successfully disabled archived CA for $env" +} + +# Restore an archived CA by moving the archived files back to their original names +function _restore_archived_ca() { + env=$1 + archive_date=$2 + + mv truststore/ca/$env.archived_$archive_date.crt truststore/ca/$env.crt + mv truststore/ca/$env.archived_$archive_date.key truststore/ca/$env.key + mv truststore/server/$env.archived_$archive_date.pem truststore/server/$env.pem + + echo -e "✅ Successfully restored archived CA for $env from date: $archive_date" } +# Restore an archived client cert by moving the archived files back to their original names +function _restore_archived_cert() { + env=$1 + archive_date=$2 + + mv truststore/client/$env.archived_$archive_date.crt truststore/client/$env.crt + mv truststore/client/$env.archived_$archive_date.key truststore/client/$env.key + + echo -e "✅ Successfully restored archived client cert for $env from date: $archive_date" +} function _truststore_build_all() { - _truststore_build_ca "prod" "record-locator.national.nhs.uk" - _truststore_build_ca "int" "record-locator.int.national.nhs.uk" - _truststore_build_ca "ref" "record-locator.ref.national.nhs.uk" - _truststore_build_ca "qa" "qa.record-locator.national.nhs.uk" - _truststore_build_ca "dev" "record-locator.dev.national.nhs.uk" + _truststore_build_ca "prod" "record-locator.national.nhs.uk_CA2" + _truststore_build_ca "int" "record-locator.int.national.nhs.uk_CA2" + _truststore_build_ca "ref" "record-locator.ref.national.nhs.uk_CA2" + _truststore_build_ca "qa" "qa.record-locator.national.nhs.uk_CA2" + _truststore_build_ca "dev" "record-locator.dev.national.nhs.uk_CA2" _truststore_build_cert "prod" "prod" "api.record-locator.national.nhs.uk" _truststore_build_cert "int" "int" "int.api.record-locator.int.national.nhs.uk" @@ -124,18 +233,26 @@ function _truststore_build_all() { function _truststore_pull_ca() { env=$1 + echo "Pulling ${env} ca certificate" aws s3 cp "s3://${BUCKET}/ca/${env}.crt" "truststore/ca/${env}.crt" } +function _truststore_pull_ca_key() { + env=$1 + echo "Pulling ${env} ca private key" + aws s3 cp "s3://${BUCKET}/ca/${env}.key" "truststore/ca/${env}.key" +} + function _truststore_pull_client() { env=$1 + echo "Pulling ${env} client certificate" aws s3 cp "s3://${BUCKET}/client/${env}.key" "truststore/client/${env}.key" aws s3 cp "s3://${BUCKET}/client/${env}.crt" "truststore/client/${env}.crt" } function _truststore_pull_server() { env=$1 - echo "Pulling truststore/server/${env}.pem" + echo "Pulling ${env} server trust certificate" aws s3 cp "s3://${BUCKET}/server/${env}.pem" "truststore/server/${env}.pem" } @@ -146,6 +263,47 @@ function _truststore_pull_all() { _truststore_pull_server $env } +function _truststore_push_all() { + env=$1 + + timestamp="$(date +%Y-%m-%d_%H%M%S)" + backup_dir="truststore/backup/${env}_${timestamp}" + + echo -e "Backing up existing files to ${backup_dir}...." + mkdir -p "${backup_dir}/ca" "${backup_dir}/client" "${backup_dir}/server" + for f in "ca/${env}.crt" "ca/${env}.key" "client/${env}.crt" "client/${env}.key" "server/${env}.pem" + do + echo + aws s3 cp "s3://${BUCKET}/$f" "${backup_dir}/$f" || echo "No existing file s3://${BUCKET}/$f to back up" + + if [ -f "${backup_dir}/$f" ] + then + diff --brief "truststore/$f" "${backup_dir}/$f" || true + fi + done + + echo + echo -n "WARNING: You are about to upload files to the ${env} truststore - are you sure? [yes/NO] " + read answer + if [ "$answer" != "yes" ]; then + echo "Aborting upload to ${env} truststore" + exit 1 + fi + + echo "Uploading ${env} ca certificate" + aws s3 cp "truststore/ca/${env}.crt" "s3://${BUCKET}/ca/${env}.crt" + + echo "Uploading ${env} ca private key" + aws s3 cp "truststore/ca/${env}.key" "s3://${BUCKET}/ca/${env}.key" + + echo "Uploading ${env} client certificate" + aws s3 cp "truststore/client/${env}.key" "s3://${BUCKET}/client/${env}.key" + aws s3 cp "truststore/client/${env}.crt" "s3://${BUCKET}/client/${env}.crt" + + echo "Uploading ${env} server trust certificate" + aws s3 cp "truststore/server/${env}.pem" "s3://${BUCKET}/server/${env}.pem" +} + function _truststore() { local command=$1; shift local args=$@ @@ -157,7 +315,14 @@ function _truststore() { "pull-all") _truststore_pull_all $args ;; "pull-server") _truststore_pull_server $args ;; "pull-client") _truststore_pull_client $args ;; - "pull-ca") _truststore_pull_server $args ;; + "pull-ca") _truststore_pull_ca $args ;; + "pull-ca-key") _truststore_pull_ca_key $args ;; + "push-all") _truststore_push_all $args ;; + "rotate-ca") _truststore_rotate_ca $args ;; + "rotate-cert") _truststore_rotate_cert $args ;; + "disable-archived-ca") _disable_archived_ca $args ;; + "restore-archived-ca") _restore_archived_ca $args ;; + "restore-archived-cert") _restore_archived_cert $args ;; *) _truststore_help $args ;; esac } diff --git a/truststore/config/client.conf b/truststore/config/client.conf index 42e6df1fb..29cc6b4c9 100644 --- a/truststore/config/client.conf +++ b/truststore/config/client.conf @@ -28,4 +28,3 @@ nsCertType = client keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = critical, clientAuth, codeSigning subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid, issuer From 1dc7b8d11c5ee320e0c320e56d8ee10ad27c17e4 Mon Sep 17 00:00:00 2001 From: Matt Dean Date: Wed, 15 Oct 2025 17:33:20 +0100 Subject: [PATCH 2/3] [NRL-1700] Fixup sonarcloud warnings in truststore.sh --- scripts/truststore.sh | 120 ++++++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 45 deletions(-) diff --git a/scripts/truststore.sh b/scripts/truststore.sh index 16360d4c0..c66774d73 100755 --- a/scripts/truststore.sh +++ b/scripts/truststore.sh @@ -45,13 +45,15 @@ EOF" cat > ${outfile} < " - exit 1 + return 1 fi env=$1 @@ -77,13 +79,14 @@ function _truststore_build_ca() { cat ./truststore/ca/$env.crt > ./truststore/server/$env.pem echo -e "✅ Successfully Built Server Truststore: $env" + return 0 } # build a certificate function _truststore_build_cert() { - if [ $# -ne 3 ]; then + if [[ $# -ne 3 ]]; then echo "Usage: $0 build-cert " - exit 1; + return 1; fi cert_name=$1 @@ -123,98 +126,104 @@ function _truststore_build_cert() { echo -e "✅ Successfully generated $cert_name keypair for ca=$ca_name (FQDN: $fqdn)" rm /tmp/client.conf rm truststore/client/$cert_name.csr + return 0 } # Rotate a CA - archive the existing CA and build a new one # The previous CA is added to the new CA bundle (can be removed after clients have updated) function _truststore_rotate_ca() { - if [ $# -ne 2 ]; then + if [[ $# -ne 2 ]]; then echo "Usage: $0 rotate-ca " - exit 1; + return 1; fi - env=$1 - fqdn=$2 + env="$1" + fqdn="$2" # Archive the existing ca certs - archive_date=$(date +%Y-%m-%d) - if [ -f "truststore/ca/$env.archived_$archive_date.crt" ] || - [ -f "truststore/ca/$env.archived_$archive_date.key" ] || - [ -f "truststore/server/$env.archived_$archive_date.pem" ]; then - echo "Error: Archive files already exist for date $archive_date - please resolve before rotating the CA" - exit 1 + archive_date="$(date +%Y-%m-%d)" + if [[ -f "truststore/ca/$env.archived_$archive_date.crt" ]] || + [[ -f "truststore/ca/$env.archived_$archive_date.key" ]] || + [[ -f "truststore/server/$env.archived_$archive_date.pem" ]]; then + echo "Error: Archive files already exist for date $archive_date - please resolve before rotating the CA" 1>&2 + return 1 fi - mv truststore/ca/$env.crt truststore/ca/$env.archived_$archive_date.crt - mv truststore/ca/$env.key truststore/ca/$env.archived_$archive_date.key - mv truststore/server/$env.pem truststore/server/$env.archived_$archive_date.pem + mv "truststore/ca/$env.crt" "truststore/ca/$env.archived_$archive_date.crt" + mv "truststore/ca/$env.key" "truststore/ca/$env.archived_$archive_date.key" + mv "truststore/server/$env.pem" "truststore/server/$env.archived_$archive_date.pem" # Build a new CA - _truststore_build_ca $env $fqdn + _truststore_build_ca "$env" "$fqdn" # Add the previous CA to the new CA bundle (can be removed after clients have updated) - cat truststore/ca/$env.archived_$archive_date.crt >> truststore/server/$env.pem + cat "truststore/ca/$env.archived_$archive_date.crt" >> "truststore/server/$env.pem" echo -e "✅ Successfully rotated CA for $env - previous CA archived with date: $archive_date" + return 0 } # Rotate a client cert - archive the existing cert and build a new one function _truststore_rotate_cert() { - if [ $# -ne 3 ]; then + if [[ $# -ne 3 ]]; then echo "Usage: $0 rotate-cert " exit 1; fi - cert_name=$1 - ca_name=$2 - fqdn=$3 + cert_name="$1" + ca_name="$2" + fqdn="$3" # Archive the existing client certs archive_date=$(date +%Y-%m-%d) - if [ -f "truststore/client/$cert_name.archived_$archive_date.crt" ] || - [ -f "truststore/client/$cert_name.archived_$archive_date.key" ]; then - echo "Error: Archive files already exist for date $archive_date - please resolve before rotating the client cert" - exit 1 + if [[ -f "truststore/client/$cert_name.archived_$archive_date.crt" ]] || + [[ -f "truststore/client/$cert_name.archived_$archive_date.key" ]]; then + echo "Error: Archive files already exist for date $archive_date - please resolve before rotating the client cert" 1>&2 + return 1 fi - mv truststore/client/$cert_name.crt truststore/client/$cert_name.archived_$archive_date.crt - mv truststore/client/$cert_name.key truststore/client/$cert_name.archived_$archive_date.key + mv "truststore/client/$cert_name.crt" "truststore/client/$cert_name.archived_$archive_date.crt" + mv "truststore/client/$cert_name.key" "truststore/client/$cert_name.archived_$archive_date.key" # Build a new client cert - _truststore_build_cert $cert_name $ca_name $fqdn + _truststore_build_cert "$cert_name" "$ca_name" "$fqdn" echo -e "✅ Successfully rotated client cert for $cert_name - previous cert archived with date: $archive_date" + return 0 } # Disable an archived CA by removing it from the server pem file function _disable_archived_ca() { - env=$1 - cat truststore/ca/$env.crt > truststore/server/$env.pem + env="$1" + cat "truststore/ca/$env.crt" > "truststore/server/$env.pem" echo -e "✅ Successfully disabled archived CA for $env" + return 0 } # Restore an archived CA by moving the archived files back to their original names function _restore_archived_ca() { - env=$1 - archive_date=$2 + env="$1" + archive_date="$2" - mv truststore/ca/$env.archived_$archive_date.crt truststore/ca/$env.crt - mv truststore/ca/$env.archived_$archive_date.key truststore/ca/$env.key - mv truststore/server/$env.archived_$archive_date.pem truststore/server/$env.pem + mv "truststore/ca/$env.archived_$archive_date.crt" "truststore/ca/$env.crt" + mv "truststore/ca/$env.archived_$archive_date.key" "truststore/ca/$env.key" + mv "truststore/server/$env.archived_$archive_date.pem" "truststore/server/$env.pem" echo -e "✅ Successfully restored archived CA for $env from date: $archive_date" + return 0 } # Restore an archived client cert by moving the archived files back to their original names function _restore_archived_cert() { - env=$1 - archive_date=$2 + env="$1" + archive_date="$2" - mv truststore/client/$env.archived_$archive_date.crt truststore/client/$env.crt - mv truststore/client/$env.archived_$archive_date.key truststore/client/$env.key + mv "truststore/client/$env.archived_$archive_date.crt" "truststore/client/$env.crt" + mv "truststore/client/$env.archived_$archive_date.key" "truststore/client/$env.key" echo -e "✅ Successfully restored archived client cert for $env from date: $archive_date" + return 0 } function _truststore_build_all() { @@ -229,18 +238,27 @@ function _truststore_build_all() { _truststore_build_cert "ref" "ref" "ref.api.record-locator.ref.national.nhs.uk" _truststore_build_cert "qa" "qa" "api.qa.record-locator.national.nhs.uk" _truststore_build_cert "dev" "dev" "dev.api.record-locator.dev.national.nhs.uk" + + echo -e "✅ Successfully built all truststore files" + return 0 } function _truststore_pull_ca() { env=$1 echo "Pulling ${env} ca certificate" aws s3 cp "s3://${BUCKET}/ca/${env}.crt" "truststore/ca/${env}.crt" + + echo -e "✅ Successfully pulled ${env} ca certificate from s3://${BUCKET}" + return 0 } function _truststore_pull_ca_key() { env=$1 echo "Pulling ${env} ca private key" aws s3 cp "s3://${BUCKET}/ca/${env}.key" "truststore/ca/${env}.key" + + echo -e "✅ Successfully pulled ${env} ca private key from s3://${BUCKET}" + return 0 } function _truststore_pull_client() { @@ -248,12 +266,18 @@ function _truststore_pull_client() { echo "Pulling ${env} client certificate" aws s3 cp "s3://${BUCKET}/client/${env}.key" "truststore/client/${env}.key" aws s3 cp "s3://${BUCKET}/client/${env}.crt" "truststore/client/${env}.crt" + + echo -e "✅ Successfully pulled ${env} client truststore files from s3://${BUCKET}" + return 0 } function _truststore_pull_server() { env=$1 echo "Pulling ${env} server trust certificate" aws s3 cp "s3://${BUCKET}/server/${env}.pem" "truststore/server/${env}.pem" + + echo -e "✅ Successfully pulled ${env} server truststore files from s3://${BUCKET}" + return 0 } function _truststore_pull_all() { @@ -261,6 +285,9 @@ function _truststore_pull_all() { _truststore_pull_ca $env _truststore_pull_client $env _truststore_pull_server $env + + echo -e "✅ Successfully pulled all ${env} truststore files from s3://${BUCKET}" + return 0 } function _truststore_push_all() { @@ -276,7 +303,7 @@ function _truststore_push_all() { echo aws s3 cp "s3://${BUCKET}/$f" "${backup_dir}/$f" || echo "No existing file s3://${BUCKET}/$f to back up" - if [ -f "${backup_dir}/$f" ] + if [[ -f "${backup_dir}/$f" ]] then diff --brief "truststore/$f" "${backup_dir}/$f" || true fi @@ -285,9 +312,9 @@ function _truststore_push_all() { echo echo -n "WARNING: You are about to upload files to the ${env} truststore - are you sure? [yes/NO] " read answer - if [ "$answer" != "yes" ]; then - echo "Aborting upload to ${env} truststore" - exit 1 + if [[ "$answer" != "yes" ]]; then + echo "Aborting upload to ${env} truststore" 1>&2 + return 1 fi echo "Uploading ${env} ca certificate" @@ -302,6 +329,9 @@ function _truststore_push_all() { echo "Uploading ${env} server trust certificate" aws s3 cp "truststore/server/${env}.pem" "s3://${BUCKET}/server/${env}.pem" + + echo -e "✅ Successfully uploaded all ${env} truststore files to s3://${BUCKET}" + return 0 } function _truststore() { From dc582f55f5a6b7de7ed201b6bbd0893275371c9a Mon Sep 17 00:00:00 2001 From: Matt Dean Date: Fri, 17 Oct 2025 15:00:05 +0100 Subject: [PATCH 3/3] [NRL-1700] Fix up issues with truststore help --- scripts/truststore.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/truststore.sh b/scripts/truststore.sh index c66774d73..605c8cbe7 100755 --- a/scripts/truststore.sh +++ b/scripts/truststore.sh @@ -25,11 +25,13 @@ function _truststore_help() { echo " pull-ca-key - Pull the certificate authority private key" echo " pull-client - pull the files needed for a client connection" echo " pull-server - pull the files needed for a server connection" + echo " pull-all - pull all the truststore files for an environment" + echo " push-all - push all the truststore files for an environment" echo " rotate-ca - rotate the certificate authority, archiving the previous one" - echo " rotate-client - rotate the client certificate, archiving the previous one" + echo " rotate-cert - rotate the client certificate, archiving the previous one" echo " disable-archived-ca - disable an archived certificate authority" echo " restore-archived-ca - restore an archived certificate authority" - echo " restore-archived-client - restore an archived client certificate" + echo " restore-archived-cert - restore an archived client certificate" echo }