Skip to content

Commit df3ca99

Browse files
Merge pull request #1070 from NHSDigital/feature/made14-NRL-1700-cert-rotate-tools
[NRL-1700] Add rotation tools to truststore.sh
2 parents 18196ea + dc582f5 commit df3ca99

File tree

2 files changed

+226
-30
lines changed

2 files changed

+226
-30
lines changed

scripts/truststore.sh

Lines changed: 226 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
#!/bin/bash
22
# Script to manage the NRLF truststore files
3+
#
4+
# Note that all creative and destructive operations are local only apart from
5+
# "push-all". Files are not uploaded to or deleted from S3 automatically.
6+
#
7+
# Once changes have been made locally and have been tested thoroughly, the files
8+
# in the truststore directories can be uploaded to S3 using the appropriate AWS
9+
# CLI commands or the "push-all" command below.
10+
#
311
set -o errexit -o pipefail -o nounset
412

513
BUCKET="nhsd-nrlf--truststore"
@@ -14,8 +22,16 @@ function _truststore_help() {
1422
echo " build-cert <name> <ca> <fqdn> - Build a single client cert + private key"
1523
echo " build-all - Build the standard trust store certs"
1624
echo " pull-ca <ca> - Pull the certificate authority"
25+
echo " pull-ca-key <ca> - Pull the certificate authority private key"
1726
echo " pull-client <env> - pull the files needed for a client connection"
1827
echo " pull-server <env> - pull the files needed for a server connection"
28+
echo " pull-all <env> - pull all the truststore files for an environment"
29+
echo " push-all <env> - push all the truststore files for an environment"
30+
echo " rotate-ca <env> - rotate the certificate authority, archiving the previous one"
31+
echo " rotate-cert <env> - rotate the client certificate, archiving the previous one"
32+
echo " disable-archived-ca <env> - disable an archived certificate authority"
33+
echo " restore-archived-ca <env> - restore an archived certificate authority"
34+
echo " restore-archived-cert <env> - restore an archived client certificate"
1935
echo
2036
}
2137

@@ -31,119 +47,293 @@ EOF"
3147
cat > ${outfile} <<EOF
3248
${output}
3349
EOF
50+
51+
return 0
3452
}
3553

3654
# build a certificate authority
3755
function _truststore_build_ca() {
38-
if [ $# -ne 2 ]; then
56+
if [[ $# -ne 2 ]]; then
3957
echo "Usage: $0 build-ca <name> <fqdn>"
40-
exit 1
58+
return 1
4159
fi
4260

43-
ca=$1
61+
env=$1
4462
fqdn=$2
4563

4664
substitute_env_in_file ./truststore/config/ca.conf /tmp/ca.conf
4765

48-
echo -e "🚧 Building CA: $ca \t(FQDN: $fqdn)"
66+
echo -e "🚧 Building CA: $env \t(FQDN: $fqdn)"
4967

5068
openssl req -newkey rsa:4096 \
5169
-nodes \
52-
-keyout ./truststore/ca/$ca.key \
70+
-keyout ./truststore/ca/$env.key \
5371
-new \
5472
-x509 \
5573
-days 36500 \
56-
-out ./truststore/ca/$ca.crt \
74+
-out ./truststore/ca/$env.crt \
5775
-config /tmp/ca.conf \
5876
-extensions v3_req \
5977
-extensions v3_ca &> /dev/null
6078

6179
rm /tmp/ca.conf
62-
echo -e "✅ Successfully Built CA: $ca"
80+
echo -e "✅ Successfully Built CA: $env (FQDN: $fqdn)"
81+
82+
cat ./truststore/ca/$env.crt > ./truststore/server/$env.pem
83+
echo -e "✅ Successfully Built Server Truststore: $env"
84+
return 0
6385
}
6486

65-
# buld a certificate
87+
# build a certificate
6688
function _truststore_build_cert() {
67-
if [ $# -ne 3 ]; then
89+
if [[ $# -ne 3 ]]; then
6890
echo "Usage: $0 build-cert <name> <ca> <fqdn>"
69-
exit 1;
91+
return 1;
7092
fi
7193

72-
client=$1
73-
ca=$2
94+
cert_name=$1
95+
ca_name=$2
7496
fqdn=$3
7597
serial=$(date +%s%3N)
7698

7799
substitute_env_in_file ./truststore/config/client.conf /tmp/client.conf
78100

79-
echo -e "🚧 Generating $client keypair"
101+
echo -e "🚧 Generating $cert_name keypair"
80102

81103
openssl req \
82104
-newkey rsa:4096 \
83105
-nodes \
84-
-keyout truststore/client/$client.key \
106+
-keyout truststore/client/$cert_name.key \
85107
-new \
86-
-out truststore/client/$client.csr \
108+
-out truststore/client/$cert_name.csr \
87109
-config /tmp/client.conf \
88110
-extensions v3_req \
89111
-extensions usr_cert &> /dev/null
90112

113+
echo -e "🚧 Signing $cert_name certificate with ca=$ca_name (serial: $serial)"
114+
91115
openssl x509 \
92116
-req \
93-
-in truststore/client/$client.csr \
94-
-CA truststore/ca/$ca.crt \
95-
-CAkey truststore/ca/$ca.key \
117+
-in truststore/client/$cert_name.csr \
118+
-CA truststore/ca/$ca_name.crt \
119+
-CAkey truststore/ca/$ca_name.key \
96120
-set_serial $serial \
97-
-out truststore/client/$client.crt \
121+
-out truststore/client/$cert_name.crt \
98122
-days 36500 \
99123
-sha256 \
100124
-extfile /tmp/client.conf \
101125
-extensions v3_req \
102126
-extensions usr_cert &> /dev/null
103127

104-
cat truststore/client/$client.crt truststore/ca/$ca.crt > truststore/server/$client.pem
105-
106-
echo -e "✅ Successfully generated $client keypair"
128+
echo -e "✅ Successfully generated $cert_name keypair for ca=$ca_name (FQDN: $fqdn)"
107129
rm /tmp/client.conf
130+
rm truststore/client/$cert_name.csr
131+
return 0
132+
}
133+
134+
# Rotate a CA - archive the existing CA and build a new one
135+
# The previous CA is added to the new CA bundle (can be removed after clients have updated)
136+
function _truststore_rotate_ca() {
137+
if [[ $# -ne 2 ]]; then
138+
echo "Usage: $0 rotate-ca <env> <fqdn>"
139+
return 1;
140+
fi
141+
142+
env="$1"
143+
fqdn="$2"
144+
145+
# Archive the existing ca certs
146+
archive_date="$(date +%Y-%m-%d)"
147+
if [[ -f "truststore/ca/$env.archived_$archive_date.crt" ]] ||
148+
[[ -f "truststore/ca/$env.archived_$archive_date.key" ]] ||
149+
[[ -f "truststore/server/$env.archived_$archive_date.pem" ]]; then
150+
echo "Error: Archive files already exist for date $archive_date - please resolve before rotating the CA" 1>&2
151+
return 1
152+
fi
153+
154+
mv "truststore/ca/$env.crt" "truststore/ca/$env.archived_$archive_date.crt"
155+
mv "truststore/ca/$env.key" "truststore/ca/$env.archived_$archive_date.key"
156+
mv "truststore/server/$env.pem" "truststore/server/$env.archived_$archive_date.pem"
157+
158+
# Build a new CA
159+
_truststore_build_ca "$env" "$fqdn"
160+
161+
# Add the previous CA to the new CA bundle (can be removed after clients have updated)
162+
cat "truststore/ca/$env.archived_$archive_date.crt" >> "truststore/server/$env.pem"
163+
164+
echo -e "✅ Successfully rotated CA for $env - previous CA archived with date: $archive_date"
165+
return 0
108166
}
109167

168+
# Rotate a client cert - archive the existing cert and build a new one
169+
function _truststore_rotate_cert() {
170+
if [[ $# -ne 3 ]]; then
171+
echo "Usage: $0 rotate-cert <env> <ca> <fqdn>"
172+
exit 1;
173+
fi
174+
175+
cert_name="$1"
176+
ca_name="$2"
177+
fqdn="$3"
178+
179+
# Archive the existing client certs
180+
archive_date=$(date +%Y-%m-%d)
181+
if [[ -f "truststore/client/$cert_name.archived_$archive_date.crt" ]] ||
182+
[[ -f "truststore/client/$cert_name.archived_$archive_date.key" ]]; then
183+
echo "Error: Archive files already exist for date $archive_date - please resolve before rotating the client cert" 1>&2
184+
return 1
185+
fi
186+
187+
mv "truststore/client/$cert_name.crt" "truststore/client/$cert_name.archived_$archive_date.crt"
188+
mv "truststore/client/$cert_name.key" "truststore/client/$cert_name.archived_$archive_date.key"
189+
190+
# Build a new client cert
191+
_truststore_build_cert "$cert_name" "$ca_name" "$fqdn"
192+
193+
echo -e "✅ Successfully rotated client cert for $cert_name - previous cert archived with date: $archive_date"
194+
return 0
195+
}
196+
197+
# Disable an archived CA by removing it from the server pem file
198+
function _disable_archived_ca() {
199+
env="$1"
200+
cat "truststore/ca/$env.crt" > "truststore/server/$env.pem"
201+
202+
echo -e "✅ Successfully disabled archived CA for $env"
203+
return 0
204+
}
205+
206+
# Restore an archived CA by moving the archived files back to their original names
207+
function _restore_archived_ca() {
208+
env="$1"
209+
archive_date="$2"
210+
211+
mv "truststore/ca/$env.archived_$archive_date.crt" "truststore/ca/$env.crt"
212+
mv "truststore/ca/$env.archived_$archive_date.key" "truststore/ca/$env.key"
213+
mv "truststore/server/$env.archived_$archive_date.pem" "truststore/server/$env.pem"
214+
215+
echo -e "✅ Successfully restored archived CA for $env from date: $archive_date"
216+
return 0
217+
}
218+
219+
# Restore an archived client cert by moving the archived files back to their original names
220+
function _restore_archived_cert() {
221+
env="$1"
222+
archive_date="$2"
223+
224+
mv "truststore/client/$env.archived_$archive_date.crt" "truststore/client/$env.crt"
225+
mv "truststore/client/$env.archived_$archive_date.key" "truststore/client/$env.key"
226+
227+
echo -e "✅ Successfully restored archived client cert for $env from date: $archive_date"
228+
return 0
229+
}
110230

111231
function _truststore_build_all() {
112-
_truststore_build_ca "prod" "record-locator.national.nhs.uk"
113-
_truststore_build_ca "int" "record-locator.int.national.nhs.uk"
114-
_truststore_build_ca "ref" "record-locator.ref.national.nhs.uk"
115-
_truststore_build_ca "qa" "qa.record-locator.national.nhs.uk"
116-
_truststore_build_ca "dev" "record-locator.dev.national.nhs.uk"
232+
_truststore_build_ca "prod" "record-locator.national.nhs.uk_CA2"
233+
_truststore_build_ca "int" "record-locator.int.national.nhs.uk_CA2"
234+
_truststore_build_ca "ref" "record-locator.ref.national.nhs.uk_CA2"
235+
_truststore_build_ca "qa" "qa.record-locator.national.nhs.uk_CA2"
236+
_truststore_build_ca "dev" "record-locator.dev.national.nhs.uk_CA2"
117237

118238
_truststore_build_cert "prod" "prod" "api.record-locator.national.nhs.uk"
119239
_truststore_build_cert "int" "int" "int.api.record-locator.int.national.nhs.uk"
120240
_truststore_build_cert "ref" "ref" "ref.api.record-locator.ref.national.nhs.uk"
121241
_truststore_build_cert "qa" "qa" "api.qa.record-locator.national.nhs.uk"
122242
_truststore_build_cert "dev" "dev" "dev.api.record-locator.dev.national.nhs.uk"
243+
244+
echo -e "✅ Successfully built all truststore files"
245+
return 0
123246
}
124247

125248
function _truststore_pull_ca() {
126249
env=$1
250+
echo "Pulling ${env} ca certificate"
127251
aws s3 cp "s3://${BUCKET}/ca/${env}.crt" "truststore/ca/${env}.crt"
252+
253+
echo -e "✅ Successfully pulled ${env} ca certificate from s3://${BUCKET}"
254+
return 0
255+
}
256+
257+
function _truststore_pull_ca_key() {
258+
env=$1
259+
echo "Pulling ${env} ca private key"
260+
aws s3 cp "s3://${BUCKET}/ca/${env}.key" "truststore/ca/${env}.key"
261+
262+
echo -e "✅ Successfully pulled ${env} ca private key from s3://${BUCKET}"
263+
return 0
128264
}
129265

130266
function _truststore_pull_client() {
131267
env=$1
268+
echo "Pulling ${env} client certificate"
132269
aws s3 cp "s3://${BUCKET}/client/${env}.key" "truststore/client/${env}.key"
133270
aws s3 cp "s3://${BUCKET}/client/${env}.crt" "truststore/client/${env}.crt"
271+
272+
echo -e "✅ Successfully pulled ${env} client truststore files from s3://${BUCKET}"
273+
return 0
134274
}
135275

136276
function _truststore_pull_server() {
137277
env=$1
138-
echo "Pulling truststore/server/${env}.pem"
278+
echo "Pulling ${env} server trust certificate"
139279
aws s3 cp "s3://${BUCKET}/server/${env}.pem" "truststore/server/${env}.pem"
280+
281+
echo -e "✅ Successfully pulled ${env} server truststore files from s3://${BUCKET}"
282+
return 0
140283
}
141284

142285
function _truststore_pull_all() {
143286
env=$1
144287
_truststore_pull_ca $env
145288
_truststore_pull_client $env
146289
_truststore_pull_server $env
290+
291+
echo -e "✅ Successfully pulled all ${env} truststore files from s3://${BUCKET}"
292+
return 0
293+
}
294+
295+
function _truststore_push_all() {
296+
env=$1
297+
298+
timestamp="$(date +%Y-%m-%d_%H%M%S)"
299+
backup_dir="truststore/backup/${env}_${timestamp}"
300+
301+
echo -e "Backing up existing files to ${backup_dir}...."
302+
mkdir -p "${backup_dir}/ca" "${backup_dir}/client" "${backup_dir}/server"
303+
for f in "ca/${env}.crt" "ca/${env}.key" "client/${env}.crt" "client/${env}.key" "server/${env}.pem"
304+
do
305+
echo
306+
aws s3 cp "s3://${BUCKET}/$f" "${backup_dir}/$f" || echo "No existing file s3://${BUCKET}/$f to back up"
307+
308+
if [[ -f "${backup_dir}/$f" ]]
309+
then
310+
diff --brief "truststore/$f" "${backup_dir}/$f" || true
311+
fi
312+
done
313+
314+
echo
315+
echo -n "WARNING: You are about to upload files to the ${env} truststore - are you sure? [yes/NO] "
316+
read answer
317+
if [[ "$answer" != "yes" ]]; then
318+
echo "Aborting upload to ${env} truststore" 1>&2
319+
return 1
320+
fi
321+
322+
echo "Uploading ${env} ca certificate"
323+
aws s3 cp "truststore/ca/${env}.crt" "s3://${BUCKET}/ca/${env}.crt"
324+
325+
echo "Uploading ${env} ca private key"
326+
aws s3 cp "truststore/ca/${env}.key" "s3://${BUCKET}/ca/${env}.key"
327+
328+
echo "Uploading ${env} client certificate"
329+
aws s3 cp "truststore/client/${env}.key" "s3://${BUCKET}/client/${env}.key"
330+
aws s3 cp "truststore/client/${env}.crt" "s3://${BUCKET}/client/${env}.crt"
331+
332+
echo "Uploading ${env} server trust certificate"
333+
aws s3 cp "truststore/server/${env}.pem" "s3://${BUCKET}/server/${env}.pem"
334+
335+
echo -e "✅ Successfully uploaded all ${env} truststore files to s3://${BUCKET}"
336+
return 0
147337
}
148338

149339
function _truststore() {
@@ -157,7 +347,14 @@ function _truststore() {
157347
"pull-all") _truststore_pull_all $args ;;
158348
"pull-server") _truststore_pull_server $args ;;
159349
"pull-client") _truststore_pull_client $args ;;
160-
"pull-ca") _truststore_pull_server $args ;;
350+
"pull-ca") _truststore_pull_ca $args ;;
351+
"pull-ca-key") _truststore_pull_ca_key $args ;;
352+
"push-all") _truststore_push_all $args ;;
353+
"rotate-ca") _truststore_rotate_ca $args ;;
354+
"rotate-cert") _truststore_rotate_cert $args ;;
355+
"disable-archived-ca") _disable_archived_ca $args ;;
356+
"restore-archived-ca") _restore_archived_ca $args ;;
357+
"restore-archived-cert") _restore_archived_cert $args ;;
161358
*) _truststore_help $args ;;
162359
esac
163360
}

truststore/config/client.conf

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,3 @@ nsCertType = client
2828
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
2929
extendedKeyUsage = critical, clientAuth, codeSigning
3030
subjectKeyIdentifier = hash
31-
authorityKeyIdentifier = keyid, issuer

0 commit comments

Comments
 (0)