mirror of
https://github.com/outbackdingo/labca.git
synced 2026-01-27 10:19:34 +00:00
Use ceremony tool for generating keys and certs; store keys on SoftHSM
Replace openssl certificate / CRL generation with the tool as used by Let's Encrypt, storing the keys on SoftHSMv2, a simulated HSM (Hardware Security Module). Include migration of old setups where key files were also stored on disk.
This commit is contained in:
5
backup
5
backup
@@ -21,6 +21,11 @@ docker compose exec bmysql mysqldump boulder_sa_integration >$TMPDIR/boulder_sa_
|
||||
cp -p /etc/nginx/ssl/*key* /etc/nginx/ssl/*cert.pem /etc/nginx/ssl/*.csr $TMPDIR/
|
||||
|
||||
cp -rp /opt/labca/data $TMPDIR/
|
||||
#cp -p /opt/labca/data/config.json $TMPDIR/
|
||||
|
||||
cp -rp /opt/boulder/labca/certs/webpki $TMPDIR/
|
||||
|
||||
cp -rp /var/lib/softhsm/tokens $TMPDIR/
|
||||
|
||||
|
||||
cd /tmp
|
||||
|
||||
@@ -37,6 +37,7 @@ RUN export DEBIAN_FRONTEND=noninteractive \
|
||||
cron \
|
||||
curl \
|
||||
python3 \
|
||||
softhsm2 \
|
||||
tzdata \
|
||||
ucspi-tcp \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
@@ -59,6 +60,7 @@ COPY tmp/restore /opt/labca/
|
||||
COPY tmp/utils.sh /opt/labca/
|
||||
COPY tmp/src/labca /opt/staging/boulder_labca
|
||||
COPY tmp/admin/apply-boulder /opt/labca/
|
||||
COPY tmp/admin/apply /opt/labca/
|
||||
|
||||
COPY tmp/admin/static /opt/staging/static
|
||||
COPY tmp/admin/data /opt/staging/data
|
||||
@@ -68,7 +70,4 @@ COPY tmp/admin/apply-nginx /opt/labca/
|
||||
|
||||
COPY tmp/bin/boulder /opt/boulder/bin/
|
||||
|
||||
RUN cd /opt/boulder/bin/ \
|
||||
&& ln -s boulder admin-revoker \
|
||||
&& ln -s boulder mail-tester \
|
||||
&& mkdir /opt/logs
|
||||
RUN mkdir /opt/logs
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:focal as builder
|
||||
FROM ubuntu:focal AS builder
|
||||
|
||||
RUN export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get update \
|
||||
@@ -31,6 +31,7 @@ FROM ubuntu:focal
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
softhsm2 \
|
||||
tzdata \
|
||||
unzip \
|
||||
zip \
|
||||
@@ -46,3 +47,6 @@ COPY tmp/admin/apply-boulder /opt/labca/
|
||||
COPY tmp/admin/apply-nginx /opt/labca/
|
||||
COPY tmp/admin/restart_control /opt/labca/
|
||||
COPY tmp/admin/templates /opt/labca/templates/
|
||||
|
||||
COPY tmp/bin/ceremony /opt/boulder/bin/
|
||||
COPY tmp/bin/nameid /opt/boulder/bin/
|
||||
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
- boulder_data:/opt/boulder/labca
|
||||
- certificates:/opt/boulder/labca/certs
|
||||
- nginx_html:/opt/wwwstatic
|
||||
- softhsm:/var/lib/softhsm/tokens:cached
|
||||
- softhsm:/var/lib/softhsm/tokens
|
||||
networks:
|
||||
bouldernet:
|
||||
ipv4_address: 10.77.77.77
|
||||
@@ -113,6 +113,7 @@ services:
|
||||
- backup:/opt/backup
|
||||
- boulder_data:/opt/boulder/labca
|
||||
- certificates:/opt/boulder/labca/certs
|
||||
- softhsm:/var/lib/softhsm/tokens
|
||||
expose:
|
||||
- 3000
|
||||
depends_on:
|
||||
@@ -154,6 +155,7 @@ services:
|
||||
- logs:/opt/logs
|
||||
- boulder_data:/opt/boulder/labca
|
||||
- certificates:/opt/boulder/labca/certs
|
||||
- softhsm:/var/lib/softhsm/tokens
|
||||
- nginx_conf:/etc/nginx/conf.d
|
||||
- nginx_ssl:/etc/nginx/ssl
|
||||
- nginx_html:/var/www/html
|
||||
|
||||
@@ -35,7 +35,7 @@ if [ "$BRANCH" == "master" ] || [ "$BRANCH" == "main" ]; then
|
||||
fi
|
||||
|
||||
cnt=$(ls -1 tmp/bin | wc -l)
|
||||
[ $cnt -gt 20 ] || die "Only found $cnt boulder binaries!" # ?? still correct??
|
||||
[ $cnt -gt 16 ] || die "Only found $cnt boulder binaries!" # ?? still correct??
|
||||
docker build -f Dockerfile-boulder -t $LABCA_BOULDER_TAG .
|
||||
|
||||
if [ "$BRANCH" == "master" ] || [ "$BRANCH" == "main" ]; then
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/docker-compose.yml b/docker-compose.yml
|
||||
index c7939ece4..0a2854919 100644
|
||||
index 71203004d..b17125e54 100644
|
||||
--- a/docker-compose.yml
|
||||
+++ b/docker-compose.yml
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
@@ -19,11 +19,11 @@ index c7939ece4..0a2854919 100644
|
||||
- - /home/labca/boulder_labca:/opt/boulder/labca
|
||||
- - /home/labca/nginx_data/static:/opt/wwwstatic
|
||||
- - ./.gocache:/root/.cache/go-build:cached
|
||||
- - /home/labca/boulder_labca/certs/.softhsm-tokens/:/var/lib/softhsm/tokens/:cached
|
||||
- - /home/labca/boulder_labca/certs/.softhsm-tokens/:/var/lib/softhsm/tokens/
|
||||
+ - boulder_data:/opt/boulder/labca
|
||||
+ - certificates:/opt/boulder/labca/certs
|
||||
+ - nginx_html:/opt/wwwstatic
|
||||
+ - softhsm:/var/lib/softhsm/tokens:cached
|
||||
+ - softhsm:/var/lib/softhsm/tokens
|
||||
networks:
|
||||
bouldernet:
|
||||
ipv4_address: 10.77.77.77
|
||||
@@ -35,7 +35,7 @@ index c7939ece4..0a2854919 100644
|
||||
entrypoint: labca/entrypoint.sh
|
||||
working_dir: &boulder_working_dir /opt/boulder
|
||||
logging:
|
||||
@@ -87,34 +87,39 @@ services:
|
||||
@@ -87,35 +87,40 @@ services:
|
||||
|
||||
bconsul:
|
||||
image: hashicorp/consul:1.15.4
|
||||
@@ -67,12 +67,14 @@ index c7939ece4..0a2854919 100644
|
||||
- - /home/labca/backup:/opt/backup
|
||||
- - .:/opt/boulder
|
||||
- - /home/labca/boulder_labca:/opt/boulder/labca
|
||||
- - /home/labca/boulder_labca/certs/.softhsm-tokens/:/var/lib/softhsm/tokens/
|
||||
+ - ./docker-compose.yml:/opt/boulder/docker-compose.yml
|
||||
+ - ldata:/opt/labca/data
|
||||
+ - nginx_html:/opt/wwwstatic
|
||||
+ - backup:/opt/backup
|
||||
+ - boulder_data:/opt/boulder/labca
|
||||
+ - certificates:/opt/boulder/labca/certs
|
||||
+ - softhsm:/var/lib/softhsm/tokens
|
||||
expose:
|
||||
- 3000
|
||||
depends_on:
|
||||
@@ -85,7 +87,7 @@ index c7939ece4..0a2854919 100644
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
@@ -131,27 +136,27 @@ services:
|
||||
@@ -132,28 +137,28 @@ services:
|
||||
- 80:80
|
||||
- 443:443
|
||||
volumes:
|
||||
@@ -113,6 +115,7 @@ index c7939ece4..0a2854919 100644
|
||||
- - /home/labca/control_logs:/opt/logs
|
||||
- - .:/opt/boulder
|
||||
- - /home/labca/boulder_labca:/opt/boulder/labca
|
||||
- - /home/labca/boulder_labca/certs/.softhsm-tokens/:/var/lib/softhsm/tokens/
|
||||
- - /home/labca/nginx_data/conf.d:/etc/nginx/conf.d
|
||||
- - /home/labca/nginx_data/ssl:/etc/nginx/ssl
|
||||
- - /home/labca/nginx_data/static:/var/www/html
|
||||
@@ -122,13 +125,14 @@ index c7939ece4..0a2854919 100644
|
||||
+ - logs:/opt/logs
|
||||
+ - boulder_data:/opt/boulder/labca
|
||||
+ - certificates:/opt/boulder/labca/certs
|
||||
+ - softhsm:/var/lib/softhsm/tokens
|
||||
+ - nginx_conf:/etc/nginx/conf.d
|
||||
+ - nginx_ssl:/etc/nginx/ssl
|
||||
+ - nginx_html:/var/www/html
|
||||
expose:
|
||||
- 3030
|
||||
environment:
|
||||
@@ -169,6 +174,15 @@ services:
|
||||
@@ -171,6 +176,15 @@ services:
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
|
||||
32
checkcrl
32
checkcrl
@@ -2,32 +2,34 @@
|
||||
|
||||
set -e
|
||||
|
||||
if [ -e data/root-ca.crl ] && [ ! -e /var/www/html/crl/root-ca.crl ]; then
|
||||
cp -p data/root-ca.crl /var/www/html/crl/root-ca.crl
|
||||
touch /var/www/html/crl
|
||||
fi
|
||||
if [ -e data/root-ca.crl ] && [ data/root-ca.crl -nt /var/www/html/crl/root-ca.crl ]; then
|
||||
cp -p data/root-ca.crl /var/www/html/crl/root-ca.crl
|
||||
touch /var/www/html/crl
|
||||
fi
|
||||
|
||||
cd /var/www/html
|
||||
|
||||
ROOT_CRL_FILE=/opt/boulder/labca/certs/webpki/root-01-crl.pem
|
||||
ROOT_CRL_NAME=$(basename $ROOT_CRL_FILE)
|
||||
[ -e "crl/$ROOT_CRL_NAME" ] || (cp $ROOT_CRL_FILE crl/ && touch crl/)
|
||||
[ $ROOT_CRL_FILE -ot "crl/$ROOT_CRL_NAME" ] || (cp $ROOT_CRL_FILE crl/ && touch crl/)
|
||||
|
||||
if [ crl/ -nt certs/index.html ]; then
|
||||
echo "Updating certs/index.html with latest CRL info..."
|
||||
|
||||
PKI_ROOT_CERT_BASE="crl/root-ca"
|
||||
PKI_ISSUER_NAME_ID=$(grep issuer_name_id /opt/labca/data/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/,//g' | sed -e 's/\"//g')
|
||||
|
||||
PKI_ROOT_CRL_LINK=""
|
||||
PKI_ROOT_CRL_VALIDITY=""
|
||||
if [ -e "$PKI_ROOT_CERT_BASE.crl" ]; then
|
||||
PKI_ROOT_CRL_VALIDITY="$(openssl crl -noout -in $PKI_ROOT_CERT_BASE.crl -lastupdate | sed -e "s/.*=/Last Update: /")<br/> $(openssl crl -noout -in $PKI_ROOT_CERT_BASE.crl -nextupdate | sed -e "s/.*=/Next Update: /")"
|
||||
if [ -e $ROOT_CRL_FILE ]; then
|
||||
PKI_ROOT_CRL_LINK="<a class=\"public\" href=\"../crl/$ROOT_CRL_NAME\">$ROOT_CRL_NAME</a></td>"
|
||||
PKI_ROOT_CRL_VALIDITY="$(openssl crl -noout -in $ROOT_CRL_FILE -lastupdate | sed -e "s/.*=/Last Update: /")<br/> $(openssl crl -noout -in $ROOT_CRL_FILE -nextupdate | sed -e "s/.*=/Next Update: /")"
|
||||
fi
|
||||
sed -i -e "s|<\!-- BEGIN PKI_ROOT_CRL_LINK -->.*<\!-- END PKI_ROOT_CRL_LINK -->|<\!-- BEGIN PKI_ROOT_CRL_LINK -->$PKI_ROOT_CRL_LINK<\!-- END PKI_ROOT_CRL_LINK -->|g" certs/index.html
|
||||
sed -i -e "s|<\!-- BEGIN PKI_ROOT_CRL_VALIDITY -->.*<\!-- END PKI_ROOT_CRL_VALIDITY -->|<\!-- BEGIN PKI_ROOT_CRL_VALIDITY -->$PKI_ROOT_CRL_VALIDITY<\!-- END PKI_ROOT_CRL_VALIDITY -->|g" certs/index.html
|
||||
|
||||
PKI_INT_CERT_BASE="/opt/boulder/labca/certs/webpki/issuer-01-cert"
|
||||
INT_BASE_NAME=$(basename $PKI_INT_CERT_BASE)
|
||||
INT_CRL_NAME=${INT_BASE_NAME//-cert/-crl}.pem
|
||||
PKI_ISSUER_NAME_ID=$(grep issuer_name_id /opt/labca/data/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/,//g' | sed -e 's/\"//g')
|
||||
PKI_INT_CRL_LINK=""
|
||||
PKI_INT_CRL_VALIDITY=""
|
||||
if [ -e "crl/$PKI_ISSUER_NAME_ID.crl" ]; then
|
||||
PKI_INT_CRL_LINK="<a class=\"public\" href=\"../crl/$PKI_ISSUER_NAME_ID.crl\">$PKI_ISSUER_NAME_ID.crl</a></td>"
|
||||
[ -L "crl/$INT_CRL_NAME" ] || ln -sf $PKI_ISSUER_NAME_ID.crl crl/$INT_CRL_NAME
|
||||
PKI_INT_CRL_LINK="<a class=\"public\" href=\"../crl/$INT_CRL_NAME\">$INT_CRL_NAME</a></td>"
|
||||
PKI_INT_CRL_VALIDITY="$(openssl crl -noout -inform der -in crl/$PKI_ISSUER_NAME_ID.crl -lastupdate | sed -e "s/.*=/Last Update: /")<br/> $(openssl crl -noout -inform der -in crl/$PKI_ISSUER_NAME_ID.crl -nextupdate | sed -e "s/.*=/Next Update: /")"
|
||||
fi
|
||||
sed -i -e "s|<\!-- BEGIN PKI_INT_CRL_LINK -->.*<\!-- END PKI_INT_CRL_LINK -->|<\!-- BEGIN PKI_INT_CRL_LINK -->$PKI_INT_CRL_LINK<\!-- END PKI_INT_CRL_LINK -->|g" certs/index.html
|
||||
|
||||
@@ -12,3 +12,6 @@ if ! expires=`openssl x509 -checkend $[ 86400 * $RENEW ] -noout -in /etc/nginx/s
|
||||
cp -p /etc/nginx/ssl/labca_cert.pem /etc/nginx/ssl/labca_cert_$TODAY.pem
|
||||
/opt/labca/renew
|
||||
fi
|
||||
|
||||
cd /opt/labca/gui
|
||||
/opt/labca/bin/labca-gui -config /opt/labca/data/config.json -renewcrl $RENEW
|
||||
|
||||
35
commander
35
commander
@@ -51,12 +51,12 @@ case $txt in
|
||||
wait_up $PS_BOULDER $PS_BOULDER_COUNT &>>$LOGFILE
|
||||
cd /etc/nginx/ssl
|
||||
[ -e account.key ] || openssl genrsa 4096 > account.key
|
||||
[ ! -f labca_key.pem ] || mv labca_key.pem labca_key_rsa.pem
|
||||
[ -L labca_key.pem ] || mv labca_key.pem labca_key_rsa.pem
|
||||
[ -e labca_key_rsa.pem ] || openssl genrsa 4096 > labca_key_rsa.pem
|
||||
[ -e labca_key_ecdsa.pem ] || openssl ecparam -name secp384r1 -genkey -out labca_key_ecdsa.pem
|
||||
|
||||
set +e
|
||||
curve_count=$(openssl pkey -pubin -in /opt/boulder/labca/test-ca.pubkey.pem -text | grep -i curve | wc -l)
|
||||
curve_count=$(openssl pkey -pubin -in /opt/boulder/labca/certs/webpki/issuer-01-pubkey.pem -text | grep -i curve | wc -l)
|
||||
set -e
|
||||
[ "$curve_count" == "0" ] && ln -sf labca_key_rsa.pem labca_key.pem || /bin/true
|
||||
[ "$curve_count" != "0" ] && ln -sf labca_key_ecdsa.pem labca_key.pem || /bin/true
|
||||
@@ -75,6 +75,11 @@ case $txt in
|
||||
wait_server $url
|
||||
sleep 10
|
||||
/opt/labca/renew
|
||||
|
||||
sleep 5
|
||||
cd /opt/boulder
|
||||
docker compose exec -i boulder ./bin/boulder crl-updater --config labca/config/crl-updater.json -runOnce -debug-addr :18021
|
||||
/opt/labca/checkcrl
|
||||
fi
|
||||
|
||||
ln -sf /opt/labca/cron_d /etc/cron.d/labca
|
||||
@@ -83,12 +88,12 @@ case $txt in
|
||||
"acme-change")
|
||||
read fqdn
|
||||
cd /etc/nginx/ssl
|
||||
[ ! -f labca_key.pem ] || mv labca_key.pem labca_key_rsa.pem
|
||||
[ -L labca_key.pem ] || mv labca_key.pem labca_key_rsa.pem
|
||||
[ -e labca_key_rsa.pem ] || openssl genrsa 4096 > labca_key_rsa.pem
|
||||
[ -e labca_key_ecdsa.pem ] || openssl ecparam -name secp384r1 -genkey -out labca_key_ecdsa.pem
|
||||
|
||||
set +e
|
||||
curve_count=$(openssl pkey -pubin -in /opt/boulder/labca/test-ca.pubkey.pem -text | grep -i curve | wc -l)
|
||||
curve_count=$(openssl pkey -pubin -in /opt/boulder/labca/certs/webpki/issuer-01-pubkey.pem -text | grep -i curve | wc -l)
|
||||
set -e
|
||||
[ "$curve_count" == "0" ] && ln -sf labca_key_rsa.pem labca_key.pem || /bin/true
|
||||
[ "$curve_count" != "0" ] && ln -sf labca_key_ecdsa.pem labca_key.pem || /bin/true
|
||||
@@ -270,14 +275,34 @@ case $txt in
|
||||
nohup /labca/install -b $branch &>>$LOGFILE
|
||||
fi
|
||||
;;
|
||||
"gen-root-crl")
|
||||
cd /opt/labca/gui
|
||||
/opt/labca/bin/labca-gui -config /opt/labca/data/config.json -renewcrl 999 &>>$LOGFILE
|
||||
/opt/labca/checkcrl &>>$LOGFILE
|
||||
;;
|
||||
"gen-issuer-crl")
|
||||
cd /opt/boulder
|
||||
docker compose exec -i boulder ./bin/boulder crl-updater --config labca/config/crl-updater.json -runOnce -debug-addr :18021 &>>$LOGFILE
|
||||
docker compose exec -i boulder ./bin/boulder crl-updater -config labca/config/crl-updater.json -runOnce -debug-addr :18021 &>>$LOGFILE
|
||||
/opt/labca/checkcrl &>>$LOGFILE
|
||||
;;
|
||||
"check-crl")
|
||||
/opt/labca/checkcrl &>>$LOGFILE
|
||||
;;
|
||||
"apply")
|
||||
[ ! -e /opt/labca/apply ] || /opt/labca/apply &>>$LOGFILE
|
||||
[ ! -e /opt/labca/gui/apply ] || /opt/labca/gui/apply &>>$LOGFILE
|
||||
[ -e /opt/labca/apply ] || [ -e /opt/labca/gui/apply ] || echo "Could not find apply script!"
|
||||
;;
|
||||
"git-version")
|
||||
if [ -x /usr/bin/git ]; then
|
||||
git config --global --add safe.directory /opt/labca &>>$LOGFILE
|
||||
gd=$(git describe --always --tags HEAD)
|
||||
echo "$gd"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command '$txt'. ERROR!"
|
||||
exit 1
|
||||
|
||||
@@ -53,13 +53,8 @@ setup_nginx_data() {
|
||||
[ -e cert ] || ln -s certs cert
|
||||
cp -rp /opt/staging/static/* .
|
||||
|
||||
[ -e /opt/labca/data/root-ca.crl ] && cp /opt/labca/data/root-ca.crl crl/ || true
|
||||
[ -e /opt/labca/data/root-ca.pem ] && cp /opt/labca/data/root-ca.pem certs/ || true
|
||||
[ -e /opt/labca/data/root-ca.pem ] && ln -sf root-ca.pem certs/test-root.pem || true
|
||||
[ -e /opt/labca/data/root-ca.der ] && cp /opt/labca/data/root-ca.der certs/ || true
|
||||
[ -e /opt/labca/data/issuer/ca-int.pem ] && cp /opt/labca/data/issuer/ca-int.pem certs/ || true
|
||||
[ -e /opt/labca/data/issuer/ca-int.pem ] && ln -sf ca-int.pem certs/test-ca.pem || true
|
||||
[ -e /opt/labca/data/issuer/ca-int.pem ] && cp /opt/labca/data/issuer/ca-int.der certs/ || true
|
||||
|
||||
if [ ! -e /etc/nginx/ssl/labca_cert.pem ]; then
|
||||
pushd /etc/nginx/ssl >/dev/null
|
||||
|
||||
15
gui/apply
15
gui/apply
@@ -3,26 +3,23 @@
|
||||
set -e
|
||||
|
||||
baseDir=$(cd $(dirname $0) && pwd)
|
||||
dataDir="$baseDir/data"
|
||||
dataDir="/opt/boulder/labca/certs/webpki"
|
||||
|
||||
export PKI_ROOT_CERT_BASE="$dataDir/root-ca"
|
||||
export PKI_INT_CERT_BASE="$dataDir/issuer/ca-int"
|
||||
export PKI_ROOT_CERT_BASE="$dataDir/root-01-cert"
|
||||
export PKI_INT_CERT_BASE="$dataDir/issuer-01-cert"
|
||||
|
||||
cd /opt/boulder/labca
|
||||
$baseDir/apply-boulder
|
||||
|
||||
cd /opt/wwwstatic
|
||||
|
||||
if [ -e "$PKI_ROOT_CERT_BASE.crl" ]; then
|
||||
cp $PKI_ROOT_CERT_BASE.crl crl/
|
||||
PKI_ROOT_CRL_FILE=${PKI_ROOT_CERT_BASE/-cert/-crl}.pem
|
||||
if [ -e "$PKI_ROOT_CRL_FILE" ]; then
|
||||
cp $PKI_ROOT_CRL_FILE crl/
|
||||
else
|
||||
echo "WARNING: no Root CRL file present - please upload one from the manage page"
|
||||
fi
|
||||
cp $PKI_ROOT_CERT_BASE.pem certs/
|
||||
ln -sf root-ca.pem certs/test-root.pem
|
||||
cp $PKI_ROOT_CERT_BASE.der certs/
|
||||
cp $PKI_INT_CERT_BASE.pem certs/
|
||||
ln -sf ca-int.pem certs/test-ca.pem
|
||||
cp $PKI_INT_CERT_BASE.der certs/
|
||||
|
||||
$baseDir/apply-nginx
|
||||
|
||||
@@ -14,8 +14,8 @@ PKI_DOMAIN=$(echo $PKI_FQDN | perl -p0e 's/.*?\.//')
|
||||
PKI_DOMAIN_MODE=$(grep domain_mode $dataDir/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g')
|
||||
PKI_LOCKDOWN_DOMAINS=$(grep lockdown $dataDir/config.json | grep -v domain_mode | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g')
|
||||
PKI_WHITELIST_DOMAINS=$(grep whitelist $dataDir/config.json | grep -v domain_mode | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g')
|
||||
PKI_ROOT_CERT_BASE="$dataDir/root-ca"
|
||||
PKI_INT_CERT_BASE="$dataDir/issuer/ca-int"
|
||||
PKI_ROOT_CERT_BASE="/opt/boulder/labca/certs/webpki/root-01-cert"
|
||||
PKI_INT_CERT_BASE="/opt/boulder/labca/certs/webpki/issuer-01-cert"
|
||||
PKI_ISSUER_NAME_ID=$(grep issuer_name_id $dataDir/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/,//g' | sed -e 's/\"//g')
|
||||
if [ -z "$PKI_ISSUER_NAME_ID" ] && [ -e "$PKI_INT_CERT_BASE.pem" ]; then
|
||||
nmid=$(/opt/boulder/bin/nameid -s $PKI_INT_CERT_BASE.pem)
|
||||
@@ -38,15 +38,15 @@ if [ "$enabled" == "true," ]; then
|
||||
PKI_EMAIL_PASS=$(grep pass $dataDir/config.json | grep -v password | head -1 | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g')
|
||||
pwd=""
|
||||
if [ -e $baseDir/bin/labca-gui ]; then
|
||||
pwd=$([ -e ] && $baseDir/bin/labca-gui -d $PKI_EMAIL_PASS || echo "")
|
||||
pwd=$([ -e ] && $baseDir/bin/labca-gui -config $dataDir/config.json -d $PKI_EMAIL_PASS || echo "")
|
||||
elif [ -e $baseDir/bin/labca-gui_prev ]; then
|
||||
pwd=$([ -e ] && $baseDir/bin/labca-gui_prev -d $PKI_EMAIL_PASS || echo "")
|
||||
pwd=$([ -e ] && $baseDir/bin/labca-gui_prev -config $dataDir/config.json -d $PKI_EMAIL_PASS || echo "")
|
||||
fi
|
||||
PKI_EMAIL_PASS=$pwd
|
||||
PKI_EMAIL_FROM=$(grep from $dataDir/config.json | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g')
|
||||
PKI_EMAIL_TRUST=$(grep trust_root $dataDir/config.json | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g')
|
||||
if [ "$PKI_EMAIL_TRUST" == "private" ]; then
|
||||
PKI_EMAIL_TRUST="labca/test-root.pem"
|
||||
PKI_EMAIL_TRUST="labca/certs/webpki/root-01-cert.pem"
|
||||
elif [ "$PKI_EMAIL_TRUST" == "skip" ]; then
|
||||
PKI_EMAIL_TRUST="InsecureSkipVerify"
|
||||
else
|
||||
@@ -166,7 +166,8 @@ rm -f config/orphan-finder.json
|
||||
rm -f config/ca-a.json
|
||||
rm -f config/ca-b.json
|
||||
|
||||
sed -i -e "s|\"issuerURL\": \".*\"|\"issuerURL\": \"http://$PKI_FQDN/certs/ca-int.pem\"|" config/ca.json
|
||||
INT_BASE_NAME=$(basename $PKI_INT_CERT_BASE.pem)
|
||||
sed -i -e "s|\"issuerURL\": \".*\"|\"issuerURL\": \"http://$PKI_FQDN/certs/$INT_BASE_NAME\"|" config/ca.json
|
||||
sed -i -e "s|\"ocspURL\": \".*\"|\"ocspURL\": \"http://$PKI_FQDN/ocsp/\"|" config/ca.json
|
||||
sed -i -e "s|\"crlURLBase\": \".*\"|\"crlURLBase\": \"http://$PKI_FQDN/crl/\"|" config/ca.json
|
||||
|
||||
@@ -229,31 +230,8 @@ rm -f test-root.pem
|
||||
rm -f test-root.der
|
||||
rm -f test-root.p8
|
||||
|
||||
if [ -e $PKI_INT_CERT_BASE.key ]; then
|
||||
cp -p $PKI_INT_CERT_BASE.key test-ca.key
|
||||
if [ ! -e $PKI_INT_CERT_BASE.key.der ]; then
|
||||
openssl pkey -in $PKI_INT_CERT_BASE.key -out $PKI_INT_CERT_BASE.key.der -outform der
|
||||
fi
|
||||
cp -p $PKI_INT_CERT_BASE.key.der test-ca.key.der
|
||||
cp -p $PKI_INT_CERT_BASE.pem test-ca.pem
|
||||
openssl rsa -in $PKI_INT_CERT_BASE.key -pubout > test-ca.pubkey.pem 2>/dev/null || openssl ec -in $PKI_INT_CERT_BASE.key -pubout > test-ca.pubkey.pem
|
||||
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in test-ca.key -out test-ca.p8
|
||||
fi
|
||||
if [ -e $PKI_ROOT_CERT_BASE.key ]; then
|
||||
cp -p $PKI_ROOT_CERT_BASE.key test-root.key
|
||||
if [ ! -e $PKI_ROOT_CERT_BASE.key.der ]; then
|
||||
openssl pkey -in $PKI_ROOT_CERT_BASE.key -out $PKI_ROOT_CERT_BASE.key.der -outform der
|
||||
fi
|
||||
cp -p $PKI_ROOT_CERT_BASE.key.der test-root.key.der
|
||||
openssl rsa -in $PKI_ROOT_CERT_BASE.key -pubout > test-root.pubkey.pem 2>/dev/null || openssl ec -in $PKI_ROOT_CERT_BASE.key -pubout > test-root.pubkey.pem
|
||||
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in test-root.key -out test-root.p8
|
||||
fi
|
||||
if [ -e $PKI_ROOT_CERT_BASE.pem ]; then
|
||||
cp -p $PKI_ROOT_CERT_BASE.pem test-root.pem
|
||||
fi
|
||||
|
||||
chown -R `ls -l helpers.py | cut -d" " -f 3,4 | sed 's/ /:/g'` .
|
||||
|
||||
if [ -e $PKI_INT_CERT_BASE.key ] && [ -e $PKI_ROOT_CERT_BASE.pem ]; then
|
||||
if [ -e $PKI_INT_CERT_BASE.pem ] && [ -e $PKI_ROOT_CERT_BASE.pem ]; then
|
||||
[ -f setup_complete ] || touch setup_complete
|
||||
fi
|
||||
|
||||
@@ -9,8 +9,10 @@ PKI_WEB_TITLE=$(grep web_title $dataDir/config.json | sed -e 's/.*:[ ]*//' | sed
|
||||
if [ "$PKI_WEB_TITLE" == "" ]; then
|
||||
export PKI_WEB_TITLE="LabCA"
|
||||
fi
|
||||
PKI_ROOT_CERT_BASE="$dataDir/root-ca"
|
||||
PKI_INT_CERT_BASE="$dataDir/issuer/ca-int"
|
||||
PKI_ROOT_CERT_BASE="/opt/boulder/labca/certs/webpki/root-01-cert"
|
||||
[ ! -d "/home/labca/boulder_labca/certs/webpki" ] || PKI_ROOT_CERT_BASE="/home/labca/boulder_labca/certs/webpki/root-01-cert"
|
||||
PKI_INT_CERT_BASE="/opt/boulder/labca/certs/webpki/issuer-01-cert"
|
||||
[ ! -d "/home/labca/boulder_labca/certs/webpki" ] || PKI_INT_CERT_BASE="/home/labca/boulder_labca/certs/webpki/issuer-01-cert"
|
||||
PKI_ISSUER_NAME_ID=$(grep issuer_name_id $dataDir/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/,//g' | sed -e 's/\"//g')
|
||||
if [ -z "$PKI_ISSUER_NAME_ID" ] && [ -e "$PKI_INT_CERT_BASE.pem" ]; then
|
||||
nmid=$(/opt/boulder/bin/nameid -s $PKI_INT_CERT_BASE.pem)
|
||||
@@ -27,26 +29,42 @@ sed -i -e "s|<title>.*</title>|<title>$PKI_WEB_TITLE</title>|g" 502.html
|
||||
sed -i -e "s|<\!-- BEGIN WEBTITLE -->.*<\!-- END WEBTITLE -->|<\!-- BEGIN WEBTITLE -->$PKI_WEB_TITLE<\!-- END WEBTITLE -->|g" 502.html
|
||||
|
||||
if [ -e $PKI_ROOT_CERT_BASE.pem ]; then
|
||||
PKI_ROOT_DN=$(openssl x509 -noout -in $PKI_ROOT_CERT_BASE.pem -subject | sed -e "s/subject= //")
|
||||
PKI_ROOT_DN=$(openssl x509 -noout -in $PKI_ROOT_CERT_BASE.pem -subject | sed -e "s/subject=//")
|
||||
sed -i -e "s|<\!-- BEGIN PKI_ROOT_DN -->.*<\!-- END PKI_ROOT_DN -->|<\!-- BEGIN PKI_ROOT_DN -->$PKI_ROOT_DN<\!-- END PKI_ROOT_DN -->|g" certs/index.html
|
||||
ROOT_BASE_NAME=$(basename $PKI_ROOT_CERT_BASE)
|
||||
PKI_ROOT_LINK="<a class=\"public\" href=\"$ROOT_BASE_NAME.pem\">$ROOT_BASE_NAME.pem</a></td>"
|
||||
sed -i -e "s|<\!-- BEGIN PKI_ROOT_LINK -->.*<\!-- END PKI_ROOT_LINK -->|<\!-- BEGIN PKI_ROOT_LINK -->$PKI_ROOT_LINK<\!-- END PKI_ROOT_LINK -->|g" certs/index.html
|
||||
PKI_ROOT_VALIDITY="$(openssl x509 -noout -in $PKI_ROOT_CERT_BASE.pem -startdate | sed -e "s/.*=/Not Before: /")<br/> $(openssl x509 -noout -in $PKI_ROOT_CERT_BASE.pem -enddate | sed -e "s/.*=/Not After: /")"
|
||||
sed -i -e "s|<\!-- BEGIN PKI_ROOT_VALIDITY -->.*<\!-- END PKI_ROOT_VALIDITY -->|<\!-- BEGIN PKI_ROOT_VALIDITY -->$PKI_ROOT_VALIDITY<\!-- END PKI_ROOT_VALIDITY -->|g" certs/index.html
|
||||
|
||||
ROOT_CRL_FILE=${PKI_ROOT_CERT_BASE/-cert/-crl}.pem
|
||||
PKI_ROOT_CRL_LINK=""
|
||||
PKI_ROOT_CRL_VALIDITY=""
|
||||
if [ -e "$PKI_ROOT_CERT_BASE.crl" ]; then
|
||||
PKI_ROOT_CRL_VALIDITY="$(openssl crl -noout -in $PKI_ROOT_CERT_BASE.crl -lastupdate | sed -e "s/.*=/Last Update: /")<br/> $(openssl crl -noout -in $PKI_ROOT_CERT_BASE.crl -nextupdate | sed -e "s/.*=/Next Update: /")"
|
||||
if [ -e $ROOT_CRL_FILE ]; then
|
||||
ROOT_CRL_NAME=$(basename $ROOT_CRL_FILE)
|
||||
[ -e "crl/$ROOT_CRL_NAME" ] || cp $ROOT_CRL_FILE crl/$ROOT_CRL_NAME
|
||||
PKI_ROOT_CRL_LINK="<a class=\"public\" href=\"../crl/$ROOT_CRL_NAME\">$ROOT_CRL_NAME</a></td>"
|
||||
PKI_ROOT_CRL_VALIDITY="$(openssl crl -noout -in $ROOT_CRL_FILE -lastupdate | sed -e "s/.*=/Last Update: /")<br/> $(openssl crl -noout -in $ROOT_CRL_FILE -nextupdate | sed -e "s/.*=/Next Update: /")"
|
||||
fi
|
||||
sed -i -e "s|<\!-- BEGIN PKI_ROOT_CRL_LINK -->.*<\!-- END PKI_ROOT_CRL_LINK -->|<\!-- BEGIN PKI_ROOT_CRL_LINK -->$PKI_ROOT_CRL_LINK<\!-- END PKI_ROOT_CRL_LINK -->|g" certs/index.html
|
||||
sed -i -e "s|<\!-- BEGIN PKI_ROOT_CRL_VALIDITY -->.*<\!-- END PKI_ROOT_CRL_VALIDITY -->|<\!-- BEGIN PKI_ROOT_CRL_VALIDITY -->$PKI_ROOT_CRL_VALIDITY<\!-- END PKI_ROOT_CRL_VALIDITY -->|g" certs/index.html
|
||||
fi
|
||||
|
||||
if [ -e $PKI_INT_CERT_BASE.pem ]; then
|
||||
PKI_INT_DN=$(openssl x509 -noout -in $PKI_INT_CERT_BASE.pem -subject | sed -e "s/subject= //")
|
||||
PKI_INT_DN=$(openssl x509 -noout -in $PKI_INT_CERT_BASE.pem -subject | sed -e "s/subject=//")
|
||||
sed -i -e "s|<\!-- BEGIN PKI_INT_DN -->.*<\!-- END PKI_INT_DN -->|<\!-- BEGIN PKI_INT_DN -->$PKI_INT_DN<\!-- END PKI_INT_DN -->|g" certs/index.html
|
||||
INT_BASE_NAME=$(basename $PKI_INT_CERT_BASE)
|
||||
PKI_INT_LINK="<a class=\"public\" href=\"$INT_BASE_NAME.pem\">$INT_BASE_NAME.pem</a></td>"
|
||||
sed -i -e "s|<\!-- BEGIN PKI_INT_LINK -->.*<\!-- END PKI_INT_LINK -->|<\!-- BEGIN PKI_INT_LINK -->$PKI_INT_LINK<\!-- END PKI_INT_LINK -->|g" certs/index.html
|
||||
PKI_INT_VALIDITY="$(openssl x509 -noout -in $PKI_INT_CERT_BASE.pem -startdate | sed -e "s/.*=/Not Before: /")<br/> $(openssl x509 -noout -in $PKI_INT_CERT_BASE.pem -enddate | sed -e "s/.*=/Not After: /")"
|
||||
sed -i -e "s|<\!-- BEGIN PKI_INT_VALIDITY -->.*<\!-- END PKI_INT_VALIDITY -->|<\!-- BEGIN PKI_INT_VALIDITY -->$PKI_INT_VALIDITY<\!-- END PKI_INT_VALIDITY -->|g" certs/index.html
|
||||
|
||||
INT_CRL_NAME=${INT_BASE_NAME/-cert/-crl}.pem
|
||||
PKI_INT_CRL_LINK=""
|
||||
PKI_INT_CRL_VALIDITY=""
|
||||
if [ -e "crl/$PKI_ISSUER_NAME_ID.crl" ]; then
|
||||
PKI_INT_CRL_LINK="<a class=\"public\" href=\"../crl/$PKI_ISSUER_NAME_ID.crl\">$PKI_ISSUER_NAME_ID.crl</a></td>"
|
||||
[ -L "crl/$INT_CRL_NAME" ] || ln -sf $PKI_ISSUER_NAME_ID.crl crl/$INT_CRL_NAME
|
||||
PKI_INT_CRL_LINK="<a class=\"public\" href=\"../crl/$INT_CRL_NAME\">$INT_CRL_NAME</a></td>"
|
||||
PKI_INT_CRL_VALIDITY="$(openssl crl -noout -inform der -in crl/$PKI_ISSUER_NAME_ID.crl -lastupdate | sed -e "s/.*=/Last Update: /")<br/> $(openssl crl -noout -inform der -in crl/$PKI_ISSUER_NAME_ID.crl -nextupdate | sed -e "s/.*=/Next Update: /")"
|
||||
fi
|
||||
sed -i -e "s|<\!-- BEGIN PKI_INT_CRL_LINK -->.*<\!-- END PKI_INT_CRL_LINK -->|<\!-- BEGIN PKI_INT_CRL_LINK -->$PKI_INT_CRL_LINK<\!-- END PKI_INT_CRL_LINK -->|g" certs/index.html
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -58,6 +59,7 @@ type CertDetails struct {
|
||||
CertFile string
|
||||
BaseName string
|
||||
Subject string
|
||||
KeyType string
|
||||
IsRoot bool
|
||||
ActiveIssuer bool
|
||||
NotAfter string
|
||||
@@ -69,6 +71,22 @@ type CertChain struct {
|
||||
IssuerCerts []CertDetails
|
||||
}
|
||||
|
||||
func getCertFileKeyType(certFile string) (string, error) {
|
||||
crt, err := readCertificate(certFile)
|
||||
if err != nil {
|
||||
fmt.Println("cannot read certificate file '" + certFile + "': " + fmt.Sprint(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
if crt.PublicKeyAlgorithm == x509.RSA {
|
||||
return "RSA", nil
|
||||
} else if crt.PublicKeyAlgorithm == x509.ECDSA {
|
||||
return "ECDSA", nil
|
||||
} else {
|
||||
return "", fmt.Errorf("unknown public key algorithm: %s", crt.PublicKeyAlgorithm)
|
||||
}
|
||||
}
|
||||
|
||||
func getCertFileDetails(certFile string) (string, error) {
|
||||
var details string
|
||||
|
||||
@@ -139,6 +157,9 @@ func enhanceChains(chains []CertChain) []CertChain {
|
||||
if chains[k].IssuerCerts[n].CertFile == rawChains[i].Location.CertFile {
|
||||
chains[k].IssuerCerts[n].ActiveIssuer = rawChains[i].Active
|
||||
certFile := locateFile(rawChains[i].Location.CertFile)
|
||||
if kt, err := getCertFileKeyType(certFile); err == nil {
|
||||
chains[k].IssuerCerts[n].KeyType = kt
|
||||
}
|
||||
if d, err := getCertFileDetails(certFile); err == nil {
|
||||
chains[k].IssuerCerts[n].Details = d
|
||||
}
|
||||
@@ -153,6 +174,9 @@ func enhanceChains(chains []CertChain) []CertChain {
|
||||
|
||||
if chains[k].RootCert.Subject == "" {
|
||||
certFile := locateFile(chains[k].RootCert.CertFile)
|
||||
if kt, err := getCertFileKeyType(certFile); err == nil {
|
||||
chains[k].RootCert.KeyType = kt
|
||||
}
|
||||
if d, err := getCertFileDetails(certFile); err == nil {
|
||||
chains[k].RootCert.Details = d
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ require (
|
||||
github.com/gorilla/sessions v1.2.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/miekg/pkcs11 v1.1.1
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/smallstep/certificates v0.24.2
|
||||
|
||||
@@ -694,6 +694,8 @@ github.com/micromdm/scep/v2 v2.1.0/go.mod h1:BkF7TkPPhmgJAMtHfP+sFTKXmgzNJgLQlvv
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
|
||||
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
|
||||
733
gui/hsm.go
Normal file
733
gui/hsm.go
Normal file
@@ -0,0 +1,733 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/subtle"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/pkcs11"
|
||||
)
|
||||
|
||||
const CERT_FILES_PATH = "/opt/boulder/labca/certs/webpki/"
|
||||
|
||||
type HSMConfig struct {
|
||||
Module string
|
||||
UserPIN string
|
||||
SOPIN string
|
||||
SlotID string
|
||||
Label string
|
||||
}
|
||||
|
||||
// HSMSession represents a session with a given PKCS#11 module. It is NOT safe for concurrent access.
|
||||
type HSMSession struct {
|
||||
Context PKCSCtx
|
||||
Handle pkcs11.SessionHandle
|
||||
}
|
||||
|
||||
type PKCSCtx interface {
|
||||
CloseSession(pkcs11.SessionHandle) error
|
||||
CreateObject(pkcs11.SessionHandle, []*pkcs11.Attribute) (pkcs11.ObjectHandle, error)
|
||||
DestroyObject(pkcs11.SessionHandle, pkcs11.ObjectHandle) error
|
||||
FindObjects(pkcs11.SessionHandle, int) ([]pkcs11.ObjectHandle, bool, error)
|
||||
FindObjectsInit(pkcs11.SessionHandle, []*pkcs11.Attribute) error
|
||||
FindObjectsFinal(pkcs11.SessionHandle) error
|
||||
GenerateKey(pkcs11.SessionHandle, []*pkcs11.Mechanism, []*pkcs11.Attribute) (pkcs11.ObjectHandle, error)
|
||||
GetAttributeValue(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error)
|
||||
Logout(pkcs11.SessionHandle) error
|
||||
WrapKey(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle, pkcs11.ObjectHandle) ([]byte, error)
|
||||
}
|
||||
|
||||
func (cfg *HSMConfig) Initialize(ca_type string, seqnr string) {
|
||||
cfg.Module = "/usr/lib/softhsm/libsofthsm2.so"
|
||||
cfg.UserPIN = "1234"
|
||||
cfg.SOPIN = "5678"
|
||||
cfg.SlotID = "0"
|
||||
if ca_type != "root" {
|
||||
cfg.SlotID = "1"
|
||||
}
|
||||
cfg.Label = fmt.Sprintf("%s %s", ca_type, seqnr)
|
||||
}
|
||||
|
||||
func (cfg *HSMConfig) CreateSlot() error {
|
||||
s, err := strconv.ParseUint(cfg.SlotID, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert slot id '%s' to uint: %s", cfg.SlotID, err.Error())
|
||||
}
|
||||
id, err := cfg.createSlot(uint(s), cfg.Label)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create slot: %s", err.Error())
|
||||
}
|
||||
cfg.SlotID = id
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findSlotWithLabel(ctx *pkcs11.Ctx, label string, missing_ok bool) (string, error) {
|
||||
slots, err := ctx.GetSlotList(true)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get slots list: %s", err)
|
||||
}
|
||||
|
||||
for _, slot := range slots {
|
||||
info, err := ctx.GetSlotInfo(slot)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get slot info: %s", err)
|
||||
}
|
||||
|
||||
if info.Flags&pkcs11.CKF_TOKEN_PRESENT == pkcs11.CKF_TOKEN_PRESENT {
|
||||
token, err := ctx.GetTokenInfo(slot)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get token info: %s", err)
|
||||
}
|
||||
|
||||
if token.Label == label {
|
||||
return fmt.Sprint(slot), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if missing_ok {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return "", errors.New("no slot found matching this label")
|
||||
}
|
||||
|
||||
func (cfg *HSMConfig) createSlot(slotId uint, label string) (string, error) {
|
||||
ctx := pkcs11.New(cfg.Module)
|
||||
if ctx == nil {
|
||||
return "", errors.New("failed to load pkcs11 module")
|
||||
}
|
||||
err := ctx.Initialize()
|
||||
if err != nil && err.Error() != "pkcs11: 0x191: CKR_CRYPTOKI_ALREADY_INITIALIZED" {
|
||||
return "", fmt.Errorf("failed to initialize pkcs11 context: %s", err)
|
||||
}
|
||||
|
||||
slot, err := findSlotWithLabel(ctx, label, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if slot != "" {
|
||||
return slot, nil
|
||||
}
|
||||
|
||||
// No slot found with this token label, so create a new one
|
||||
err = ctx.InitToken(slotId, cfg.SOPIN, label)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "0x3: CKR_SLOT_ID_INVALID") {
|
||||
slots, err := ctx.GetSlotList(true)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to initialize token, failed to get slot list: %s", err)
|
||||
}
|
||||
slotId = uint(len(slots) - 1)
|
||||
cfg.SlotID = fmt.Sprint(slotId)
|
||||
err = ctx.InitToken(slotId, cfg.SOPIN, label)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to initialize token with id %d: %s", slotId, err)
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("failed to initialize token: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
session, err := ctx.OpenSession(slotId, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open session: %s", err)
|
||||
}
|
||||
defer ctx.CloseSession(session)
|
||||
|
||||
err = ctx.Login(session, pkcs11.CKU_SO, cfg.SOPIN)
|
||||
if err != nil {
|
||||
if err.Error() == "pkcs11: 0xA0: CKR_PIN_INCORRECT" {
|
||||
return "", errors.New("incorrect SO PIN")
|
||||
} else {
|
||||
return "", fmt.Errorf("failed to login: %s", err)
|
||||
}
|
||||
}
|
||||
defer ctx.Logout(session)
|
||||
|
||||
err = ctx.InitPIN(session, cfg.UserPIN)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to initialize pin: %s", err)
|
||||
}
|
||||
|
||||
// Forced reconnect to get the renumbered slots from SoftHSM2
|
||||
ctx.Finalize()
|
||||
ctx.Destroy()
|
||||
ctx = pkcs11.New(cfg.Module)
|
||||
if ctx == nil {
|
||||
return "", errors.New("failed to reload pkcs11 module")
|
||||
}
|
||||
err = ctx.Initialize()
|
||||
if err != nil && err.Error() != "pkcs11: 0x191: CKR_CRYPTOKI_ALREADY_INITIALIZED" {
|
||||
return "", fmt.Errorf("failed to reinitialize pkcs11 context: %s", err)
|
||||
}
|
||||
|
||||
slot, err = findSlotWithLabel(ctx, label, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if slot != "" {
|
||||
return slot, nil
|
||||
}
|
||||
|
||||
return "", errors.New("failed to create slot")
|
||||
}
|
||||
|
||||
// getSession establishes a logged in session on a pkcs11 slot.
|
||||
//
|
||||
// Don't forget to call .Close() on the resulting session when done!
|
||||
func (cfg *HSMConfig) getSession() (*HSMSession, error) {
|
||||
ctx := pkcs11.New(cfg.Module)
|
||||
if ctx == nil {
|
||||
return nil, errors.New("failed to load pkcs11 module")
|
||||
}
|
||||
err := ctx.Initialize()
|
||||
if err != nil && err.Error() != "pkcs11: 0x191: CKR_CRYPTOKI_ALREADY_INITIALIZED" {
|
||||
return nil, fmt.Errorf("failed to initialize pkcs11 context: %s", err)
|
||||
}
|
||||
|
||||
slot, err := findSlotWithLabel(ctx, cfg.Label, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if slot == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
s, err := strconv.ParseUint(slot, 10, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert slot id '%s' to uint: %s", cfg.SlotID, err.Error())
|
||||
}
|
||||
|
||||
session, err := ctx.OpenSession(uint(s), pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open session: %s", err)
|
||||
}
|
||||
|
||||
err = ctx.Login(session, pkcs11.CKU_USER, cfg.UserPIN)
|
||||
if err != nil {
|
||||
if err.Error() == "pkcs11: 0xA0: CKR_PIN_INCORRECT" {
|
||||
return nil, errors.New("incorrect user PIN")
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to login: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &HSMSession{ctx, session}, nil
|
||||
}
|
||||
|
||||
func (cfg *HSMConfig) ClearAll() error {
|
||||
hs, err := cfg.getSession()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get session: %s", err)
|
||||
}
|
||||
defer hs.Close()
|
||||
|
||||
err = hs.DestroyAllObjects(cfg.Label)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func arrConcat(arrays ...[]byte) []byte {
|
||||
out := make([]byte, len(arrays[0]))
|
||||
copy(out, arrays[0])
|
||||
for _, array := range arrays[1:] {
|
||||
out = append(out, array...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func arrXor(arrL []byte, arrR []byte) []byte {
|
||||
out := make([]byte, len(arrL))
|
||||
for x := range arrL {
|
||||
out[x] = arrL[x] ^ arrR[x]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// AES Key Wrap algorithm is specified in RFC 3394
|
||||
func UnwrapKey(block cipher.Block, cipherText []byte) ([]byte, error) {
|
||||
//Initialize variables
|
||||
a := make([]byte, 8)
|
||||
n := (len(cipherText) / 8) - 1
|
||||
|
||||
r := make([][]byte, n)
|
||||
for i := range r {
|
||||
r[i] = make([]byte, 8)
|
||||
copy(r[i], cipherText[(i+1)*8:])
|
||||
}
|
||||
copy(a, cipherText[:8])
|
||||
|
||||
//Compute intermediate values
|
||||
for j := 5; j >= 0; j-- {
|
||||
for i := n; i >= 1; i-- {
|
||||
t := (n * j) + i
|
||||
tBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(tBytes, uint64(t))
|
||||
|
||||
b := arrConcat(arrXor(a, tBytes), r[i-1])
|
||||
block.Decrypt(b, b)
|
||||
|
||||
copy(a, b[:len(b)/2])
|
||||
copy(r[i-1], b[len(b)/2:])
|
||||
}
|
||||
}
|
||||
|
||||
var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6}
|
||||
if subtle.ConstantTimeCompare(a, defaultIV) != 1 {
|
||||
return nil, errors.New("integrity check failed - unexpected IV")
|
||||
}
|
||||
|
||||
//Output
|
||||
c := arrConcat(r...)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (cfg *HSMConfig) GetPrivateKey() ([]byte, error) {
|
||||
hs, err := cfg.getSession()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get session: %s", err)
|
||||
}
|
||||
defer hs.Close()
|
||||
|
||||
tmpl := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_LABEL, []byte(cfg.Label)),
|
||||
}
|
||||
|
||||
keyHandle, err := hs.FindObject(tmpl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find private key with label='%s': %w", cfg.Label, err)
|
||||
}
|
||||
|
||||
// Generate a temporary wrapping key in memory
|
||||
mechs := []*pkcs11.Mechanism{
|
||||
// pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -M | grep -v generate_key_pair | grep generate
|
||||
pkcs11.NewMechanism(pkcs11.CKM_AES_KEY_GEN, nil),
|
||||
}
|
||||
tmpl = []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_VALUE_LEN, 16),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ENCRYPT, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_DECRYPT, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_WRAP, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_UNWRAP, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, false),
|
||||
}
|
||||
wrapKeyHandle, err := hs.GenerateKey(mechs, tmpl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate wrapping key: %w", err)
|
||||
}
|
||||
|
||||
// Extract the key value
|
||||
tmpl = []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_VALUE, nil),
|
||||
}
|
||||
wrapKeyAttrs, err := hs.GetAttributeValue(wrapKeyHandle, tmpl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get attribute values from object: %w", err)
|
||||
}
|
||||
var wrapKey []byte
|
||||
for _, wrapKeyAttr := range wrapKeyAttrs {
|
||||
switch wrapKeyAttr.Type {
|
||||
case pkcs11.CKA_VALUE:
|
||||
wrapKey = wrapKeyAttr.Value
|
||||
default:
|
||||
if wrapKeyAttr.Value == nil {
|
||||
fmt.Printf("unexpected attribute #%d: nil\n", wrapKeyAttr.Type)
|
||||
} else {
|
||||
fmt.Printf("unexpected attribute #%d: %s / %s\n", wrapKeyAttr.Type, hex.EncodeToString(wrapKeyAttr.Value), wrapKeyAttr.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap the private key on the HSM
|
||||
mechs = []*pkcs11.Mechanism{
|
||||
// pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -M | grep wrap
|
||||
pkcs11.NewMechanism(pkcs11.CKM_AES_KEY_WRAP, nil),
|
||||
}
|
||||
wrappedKey, err := hs.WrapKey(mechs, wrapKeyHandle, keyHandle)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to wrap private key: %w", err)
|
||||
}
|
||||
|
||||
// Unwrap the key locally
|
||||
c, err := aes.NewCipher(wrapKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new aes cipher: %w", err)
|
||||
}
|
||||
key, err := UnwrapKey(c, wrappedKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unwrap key: %w", err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func loadKey(filename string) (crypto.PrivateKey, crypto.PublicKey, error) {
|
||||
var priv crypto.PrivateKey
|
||||
var pub crypto.PublicKey
|
||||
|
||||
keyPEM, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return priv, pub, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(keyPEM)
|
||||
if block == nil {
|
||||
return priv, pub, fmt.Errorf("no data in key PEM file %s", filename)
|
||||
}
|
||||
|
||||
parseResult, _ := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if reflect.TypeOf(parseResult).String() == "*rsa.PrivateKey" {
|
||||
k := parseResult.(*rsa.PrivateKey)
|
||||
priv = k
|
||||
pub = k.PublicKey
|
||||
} else if reflect.TypeOf(parseResult).String() == "*ecdsa.PrivateKey" {
|
||||
k := parseResult.(*ecdsa.PrivateKey)
|
||||
priv = k
|
||||
pub = k.PublicKey
|
||||
} else {
|
||||
return priv, pub, fmt.Errorf("unknown private key type '%s'", reflect.TypeOf(parseResult).String())
|
||||
}
|
||||
|
||||
if priv == nil {
|
||||
fmt.Printf("WARNING: unknown private key type for %+v\n", parseResult)
|
||||
return priv, pub, errors.New("unknown private key type")
|
||||
}
|
||||
|
||||
return priv, pub, nil
|
||||
}
|
||||
|
||||
func loadCert(filename string) (*x509.Certificate, error) {
|
||||
certPEM, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(certPEM)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("no data in certificate PEM file %s", filename)
|
||||
}
|
||||
|
||||
parseResult, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse certificate: %s", err.Error())
|
||||
}
|
||||
|
||||
return parseResult, nil
|
||||
}
|
||||
|
||||
var curveToOIDDER = map[string][]byte{
|
||||
elliptic.P224().Params().Name: {6, 5, 43, 129, 4, 0, 33},
|
||||
elliptic.P256().Params().Name: {6, 8, 42, 134, 72, 206, 61, 3, 1, 7},
|
||||
elliptic.P384().Params().Name: {6, 5, 43, 129, 4, 0, 34},
|
||||
elliptic.P521().Params().Name: {6, 5, 43, 129, 4, 0, 35},
|
||||
}
|
||||
|
||||
func storePubKey(hs *HSMSession, pubKey crypto.PublicKey, keyID []byte, label string) error {
|
||||
tmpl := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, false),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_VERIFY, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ENCRYPT, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_WRAP, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ID, keyID),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_LABEL, []byte(label)),
|
||||
}
|
||||
|
||||
if reflect.TypeOf(pubKey).String() == "rsa.PublicKey" {
|
||||
p := pubKey.(rsa.PublicKey)
|
||||
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA))
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_MODULUS, p.N.Bytes()))
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, big.NewInt(int64(p.E)).Bytes()))
|
||||
} else if reflect.TypeOf(pubKey).String() == "ecdsa.PublicKey" {
|
||||
p := pubKey.(ecdsa.PublicKey)
|
||||
eh, err := p.ECDH()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert ecdsa pubkey to ecdh: %s", err.Error())
|
||||
}
|
||||
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_EC))
|
||||
|
||||
encodedCurve := curveToOIDDER[p.Curve.Params().Name]
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, encodedCurve))
|
||||
|
||||
rawValue := asn1.RawValue{
|
||||
Tag: asn1.TagOctetString,
|
||||
Bytes: eh.Bytes(),
|
||||
}
|
||||
marshalledPoint, err := asn1.Marshal(rawValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshall ecdsa point: %s", err.Error())
|
||||
}
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, marshalledPoint))
|
||||
|
||||
} else {
|
||||
return fmt.Errorf("unknown public key type '%s'", reflect.TypeOf(pubKey).String())
|
||||
}
|
||||
|
||||
_, err := hs.CreateObject(tmpl)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create public key on HSM: %s\n", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func storePrivKey(hs *HSMSession, privKey crypto.PrivateKey, keyID []byte, label string, extractable bool) error {
|
||||
tmpl := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, extractable),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_SIGN, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_DECRYPT, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_DERIVE, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_WRAP_WITH_TRUSTED, false),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_UNWRAP, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ID, keyID),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_LABEL, []byte(label)),
|
||||
}
|
||||
|
||||
if reflect.TypeOf(privKey).String() == "*rsa.PrivateKey" {
|
||||
k := privKey.(*rsa.PrivateKey)
|
||||
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA))
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_MODULUS, k.PublicKey.N.Bytes()))
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, big.NewInt(int64(k.PublicKey.E)).Bytes()))
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_PRIVATE_EXPONENT, big.NewInt(int64(k.E)).Bytes()))
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_PRIME_1, new(big.Int).Set(k.Primes[0]).Bytes()))
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_PRIME_2, new(big.Int).Set(k.Primes[1]).Bytes()))
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_EXPONENT_1, new(big.Int).Set(k.Precomputed.Dp).Bytes()))
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_EXPONENT_2, new(big.Int).Set(k.Precomputed.Dq).Bytes()))
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_COEFFICIENT, new(big.Int).Set(k.Precomputed.Qinv).Bytes()))
|
||||
|
||||
} else if reflect.TypeOf(privKey).String() == "*ecdsa.PrivateKey" {
|
||||
k := privKey.(*ecdsa.PrivateKey)
|
||||
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_EC))
|
||||
encodedCurve := curveToOIDDER[k.Params().Name]
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, encodedCurve))
|
||||
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_VALUE, new(big.Int).Set(k.D).Bytes()))
|
||||
|
||||
} else {
|
||||
return fmt.Errorf("unknown private key type '%s'", reflect.TypeOf(privKey).String())
|
||||
}
|
||||
|
||||
_, err := hs.CreateObject(tmpl)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create private key on HSM: %s\n", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeCertificate(hs *HSMSession, certificate *x509.Certificate, keyID []byte, label string) error {
|
||||
serial, err := asn1.Marshal(certificate.SerialNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CERTIFICATE_TYPE, pkcs11.CKC_X_509),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, false),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_SUBJECT, certificate.RawSubject),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ISSUER, certificate.RawIssuer),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_SERIAL_NUMBER, serial),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ID, keyID),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_LABEL, []byte(label)),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_VALUE, certificate.Raw),
|
||||
}
|
||||
|
||||
_, err = hs.CreateObject(tmpl)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create certificate on HSM: %s\n", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *HSMConfig) ImportKeyCert(keyFile, certFile string) (crypto.PublicKey, error) {
|
||||
hs, err := cfg.getSession()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get session: %s", err)
|
||||
}
|
||||
defer hs.Close()
|
||||
|
||||
privKey, pubKey, err := loadKey(keyFile)
|
||||
if err != nil {
|
||||
return pubKey, err
|
||||
}
|
||||
|
||||
keyID := make([]byte, 4)
|
||||
_, err = rand.Read(keyID)
|
||||
if err != nil {
|
||||
return pubKey, err
|
||||
}
|
||||
|
||||
err = storePubKey(hs, pubKey, keyID, cfg.Label)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to store public key on HSM: %s\n", err.Error())
|
||||
return pubKey, err
|
||||
}
|
||||
|
||||
extractable := true // For now, with SoftHSM, this is fine. In future we need to ask for informed consent!
|
||||
|
||||
err = storePrivKey(hs, privKey, keyID, cfg.Label, extractable)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to store private key on HSM: %s\n", err.Error())
|
||||
return pubKey, err
|
||||
}
|
||||
|
||||
if strings.Index(filepath.Base(keyFile), "root-") != 0 {
|
||||
jsonFile := path.Join(CERT_FILES_PATH, filepath.Base(keyFile))
|
||||
jsonFile = strings.Replace(jsonFile, "-key.pem", ".pkcs11.json", -1)
|
||||
contents := fmt.Sprintf(`{"module": %q, "tokenLabel": %q, "pin": %q}`, cfg.Module, cfg.Label, cfg.UserPIN)
|
||||
err = os.WriteFile(jsonFile, []byte(contents), 0644)
|
||||
if err != nil {
|
||||
return pubKey, fmt.Errorf("failed to write '%s' file: %s", jsonFile, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if certFile != "" {
|
||||
cert, err := loadCert(certFile)
|
||||
if err != nil {
|
||||
return pubKey, err
|
||||
}
|
||||
|
||||
err = storeCertificate(hs, cert, keyID, cfg.Label)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to store certificate on HSM: %s\n", err.Error())
|
||||
return pubKey, err
|
||||
}
|
||||
}
|
||||
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
func (hs *HSMSession) Close() {
|
||||
hs.Context.CloseSession(hs.Handle)
|
||||
hs.Context.Logout(hs.Handle)
|
||||
}
|
||||
|
||||
func (hs *HSMSession) CreateObject(tmpl []*pkcs11.Attribute) (pkcs11.ObjectHandle, error) {
|
||||
return hs.Context.CreateObject(hs.Handle, tmpl)
|
||||
}
|
||||
|
||||
func (hs *HSMSession) DestroyObject(object pkcs11.ObjectHandle) error {
|
||||
return hs.Context.DestroyObject(hs.Handle, object)
|
||||
}
|
||||
|
||||
func (hs *HSMSession) DestroyAllObjects(label string) error {
|
||||
tmpl := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_LABEL, []byte(label)),
|
||||
}
|
||||
|
||||
keys, err := hs.FindObjects(tmpl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find objects with label='%s': %w", label, err)
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
err = hs.DestroyObject(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to destroy object '%+v': %w", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *HSMSession) FindObject(tmpl []*pkcs11.Attribute) (pkcs11.ObjectHandle, error) {
|
||||
err := hs.Context.FindObjectsInit(hs.Handle, tmpl)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
handles, _, err := hs.Context.FindObjects(hs.Handle, 2)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = hs.Context.FindObjectsFinal(hs.Handle)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(handles) == 0 {
|
||||
return 0, errors.New("no objects found matching provided template")
|
||||
}
|
||||
if len(handles) > 1 {
|
||||
return 0, fmt.Errorf("too many objects (%d) that match the provided template", len(handles))
|
||||
}
|
||||
return handles[0], nil
|
||||
}
|
||||
|
||||
func (hs *HSMSession) FindObjects(tmpl []*pkcs11.Attribute) ([]pkcs11.ObjectHandle, error) {
|
||||
result := []pkcs11.ObjectHandle{}
|
||||
|
||||
err := hs.Context.FindObjectsInit(hs.Handle, tmpl)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
for {
|
||||
handles, _, err := hs.Context.FindObjects(hs.Handle, 10)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if len(handles) == 0 {
|
||||
break
|
||||
}
|
||||
result = append(result, handles...)
|
||||
}
|
||||
|
||||
err = hs.Context.FindObjectsFinal(hs.Handle)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (hs *HSMSession) GenerateKey(mechs []*pkcs11.Mechanism, tmpl []*pkcs11.Attribute) (pkcs11.ObjectHandle, error) {
|
||||
return hs.Context.GenerateKey(hs.Handle, mechs, tmpl)
|
||||
}
|
||||
|
||||
func (hs *HSMSession) GetAttributeValue(handle pkcs11.ObjectHandle, tmpl []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
|
||||
return hs.Context.GetAttributeValue(hs.Handle, handle, tmpl)
|
||||
}
|
||||
|
||||
func (hs *HSMSession) WrapKey(mechs []*pkcs11.Mechanism, wkh pkcs11.ObjectHandle, kh pkcs11.ObjectHandle) ([]byte, error) {
|
||||
return hs.Context.WrapKey(hs.Handle, mechs, wkh, kh)
|
||||
}
|
||||
378
gui/main.go
378
gui/main.go
@@ -26,6 +26,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@@ -973,13 +974,58 @@ func _emailSendHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func _exportHandler(w http.ResponseWriter, r *http.Request) {
|
||||
certname := r.Form.Get("certname")
|
||||
certFile := fmt.Sprintf("%s%s.pem", CERT_FILES_PATH, certname)
|
||||
|
||||
certFile := locateFile(certname + ".pem")
|
||||
keyFile := strings.TrimSuffix(certFile, filepath.Ext(certFile)) + ".key"
|
||||
seqnr := ""
|
||||
re := regexp.MustCompile(`-(\d{2})-`)
|
||||
match := re.FindStringSubmatch(certname)
|
||||
if len(match) > 1 {
|
||||
seqnr = match[1]
|
||||
} else {
|
||||
errorHandler(w, r, fmt.Errorf("failed to extract sequence number from filename '%s'", certFile), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
cfg := &HSMConfig{}
|
||||
if strings.HasPrefix(certname, "root-") {
|
||||
cfg.Initialize("root", seqnr)
|
||||
}
|
||||
if strings.HasPrefix(certname, "issuer-") {
|
||||
cfg.Initialize("issuer", seqnr)
|
||||
}
|
||||
|
||||
key, err := cfg.GetPrivateKey()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if strings.Contains(err.Error(), "CKR_KEY_UNEXTRACTABLE") {
|
||||
errorHandler(w, r, err, http.StatusBadRequest)
|
||||
} else {
|
||||
errorHandler(w, r, err, http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "labca")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
errorHandler(w, r, err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
keyFile := path.Join(tmpDir, fmt.Sprintf("%s.pem", strings.Replace(certname, "-cert", "-key", -1)))
|
||||
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: key})
|
||||
err = os.WriteFile(keyFile, keyPEM, os.ModeAppend)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
errorHandler(w, r, err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Form.Get("type") == "pfx" {
|
||||
w.Header().Set("Content-Type", "application/x-pkcs12")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=labca_"+certname+".pfx")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=labca-"+certname+".pfx")
|
||||
|
||||
cmd := "openssl pkcs12 -export -inkey " + keyFile + " -in " + certFile + " -passout pass:" + r.Form.Get("export-pwd")
|
||||
|
||||
@@ -988,7 +1034,7 @@ func _exportHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Form.Get("type") == "zip" {
|
||||
w.Header().Set("Content-Type", "application/zip")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=labca_"+certname+".zip")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=labca-"+certname+".zip")
|
||||
|
||||
cmd := "zip -j -P " + r.Form.Get("export-pwd") + " - " + keyFile + " " + certFile
|
||||
|
||||
@@ -1088,7 +1134,7 @@ func (res *Result) ManageComponents(w http.ResponseWriter, r *http.Request, acti
|
||||
}
|
||||
}
|
||||
|
||||
func _checkUpdatesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
func _checkUpdatesHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
res := struct {
|
||||
Success bool
|
||||
UpdateAvailable bool
|
||||
@@ -1112,62 +1158,14 @@ func _checkUpdatesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
func generateCRLHandler(w http.ResponseWriter, r *http.Request, isRoot bool) {
|
||||
res := makeErrorsResponse(true)
|
||||
|
||||
command := "gen-issuer-crl"
|
||||
if isRoot {
|
||||
path := "data/"
|
||||
certBase := "root-ca"
|
||||
keyFileExists := true
|
||||
if _, err := os.Stat(path + certBase + ".key"); errors.Is(err, fs.ErrNotExist) {
|
||||
keyFileExists = false
|
||||
}
|
||||
if keyFileExists {
|
||||
if _, err := exeCmd("openssl ca -config " + path + "openssl.cnf -gencrl -keyfile " + path + certBase + ".key -cert " + path + certBase + ".pem -out " + path + certBase + ".crl"); err != nil {
|
||||
res.Success = false
|
||||
res.Errors["CRL"] = "Could not generate Root CRL - see logs"
|
||||
}
|
||||
} else {
|
||||
if r.Form.Get("rootkey") == "" {
|
||||
res.Success = false
|
||||
res.Errors["CRL"] = "NO_ROOT_KEY"
|
||||
} else {
|
||||
rootci := &CertificateInfo{
|
||||
IsRoot: true,
|
||||
Key: r.Form.Get("rootkey"),
|
||||
Passphrase: r.Form.Get("rootpassphrase"),
|
||||
}
|
||||
if !rootci.StoreRootKey(path) {
|
||||
res.Success = false
|
||||
res.Errors["CRL"] = rootci.Errors["Modal"]
|
||||
} else {
|
||||
// Generate CRL now that we have the key
|
||||
if _, err := exeCmd("openssl ca -config " + path + "openssl.cnf -gencrl -keyfile " + path + certBase + ".key -cert " + path + certBase + ".pem -out " + path + certBase + ".crl"); err != nil {
|
||||
res.Success = false
|
||||
res.Errors["CRL"] = "Could not generate Root CRL - see logs"
|
||||
}
|
||||
// Remove the Root Key if we want to keep it offline
|
||||
if viper.GetBool("keep_root_offline") {
|
||||
if _, err := os.Stat(path + certBase + ".key"); !errors.Is(err, fs.ErrNotExist) {
|
||||
fmt.Println("Removing private Root key from the system...")
|
||||
if _, err := exeCmd("rm " + path + certBase + ".key"); err != nil {
|
||||
log.Printf("_certCreate: error deleting root key: %v", err)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(path + certBase + ".key.der"); !errors.Is(err, fs.ErrNotExist) {
|
||||
if _, err := exeCmd("rm " + path + certBase + ".key.der"); err != nil {
|
||||
log.Printf("_certCreate: error deleting root key (DER format): %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
command = "gen-root-crl"
|
||||
}
|
||||
|
||||
_hostCommand(w, r, "check-crl")
|
||||
|
||||
} else { // !isRoot
|
||||
if !_hostCommand(w, r, "gen-issuer-crl") {
|
||||
res.Success = false
|
||||
res.Errors["CRL"] = "Failed to generate CRL - see logs"
|
||||
}
|
||||
if !_hostCommand(w, r, command) {
|
||||
res.Success = false
|
||||
res.Errors["CRL"] = "Failed to generate CRL - see logs"
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -1754,7 +1752,7 @@ func getLog(w http.ResponseWriter, r *http.Request, logType string) string {
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Fprintf(conn, "log-"+logType+"\n")
|
||||
fmt.Fprintf(conn, "log-%s\n", logType)
|
||||
reader := bufio.NewReader(conn)
|
||||
contents, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
@@ -1787,7 +1785,7 @@ func showLog(ws *websocket.Conn, logType string) {
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Fprintf(conn, "log-"+logType+"\n")
|
||||
fmt.Fprintf(conn, "log-%s\n", logType)
|
||||
scanner := bufio.NewScanner(conn)
|
||||
for scanner.Scan() {
|
||||
msg := scanner.Text()
|
||||
@@ -1891,9 +1889,6 @@ func _buildCI(r *http.Request, session *sessions.Session, isRoot bool) *Certific
|
||||
if session.Values["o"] != nil {
|
||||
ci.Organization = session.Values["o"].(string)
|
||||
}
|
||||
if session.Values["ou"] != nil {
|
||||
ci.OrgUnit = session.Values["ou"].(string)
|
||||
}
|
||||
if session.Values["cn"] != nil {
|
||||
ci.CommonName = session.Values["cn"].(string)
|
||||
ci.CommonName = strings.Replace(ci.CommonName, "Root", "", -1)
|
||||
@@ -1933,13 +1928,24 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
|
||||
if r.Form.Get("revertroot") != "" {
|
||||
// From issuer certificate creation page it is possible to remove the root again and start over
|
||||
exeCmd("rm data/root-ca.key") // Does not necessarily exist
|
||||
exeCmd("rm data/root-ca.key.der") // Does not necessarily exist
|
||||
if _, err := exeCmd("rm data/root-ca.pem"); err != nil {
|
||||
errorHandler(w, r, err, http.StatusInternalServerError)
|
||||
return false
|
||||
rootseqnr := "01"
|
||||
seqnr := "01"
|
||||
err := deleteFiles(fmt.Sprintf("%sroot-%s*", CERT_FILES_PATH, rootseqnr))
|
||||
if err != nil {
|
||||
fmt.Printf("failed to delete root %s files: %+v\n", rootseqnr, err.Error())
|
||||
}
|
||||
certBase = "root-ca"
|
||||
err = deleteFiles(fmt.Sprintf("%sissuer-%s*", CERT_FILES_PATH, seqnr))
|
||||
if err != nil {
|
||||
fmt.Printf("failed to delete issuer %s files: %+v\n", seqnr, err.Error())
|
||||
}
|
||||
|
||||
cfg := &HSMConfig{}
|
||||
cfg.Initialize("issuer", seqnr)
|
||||
cfg.ClearAll()
|
||||
cfg.Initialize("root", rootseqnr)
|
||||
cfg.ClearAll()
|
||||
|
||||
certBase = "root-01"
|
||||
isRoot = true
|
||||
r.Method = "GET"
|
||||
sess, _ := sessionStore.Get(r, "labca")
|
||||
@@ -1947,6 +1953,7 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
if err := sess.Save(r, w); err != nil {
|
||||
log.Printf("cannot save session: %s\n", err)
|
||||
}
|
||||
|
||||
} else if r.Form.Get("ack-rootkey") == "yes" {
|
||||
// Root Key was shown, do we need to keep it online?
|
||||
viper.Set("keep_root_offline", r.Form.Get("keep-root-online") != "true")
|
||||
@@ -1964,24 +1971,20 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
}
|
||||
}
|
||||
|
||||
path := "data/"
|
||||
if !isRoot {
|
||||
path = path + "issuer/"
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path + certBase + ".pem"); errors.Is(err, fs.ErrNotExist) {
|
||||
if _, err := os.Stat(CERT_FILES_PATH + certBase + "-cert.pem"); errors.Is(err, fs.ErrNotExist) {
|
||||
session, _ := sessionStore.Get(r, "labca")
|
||||
|
||||
if r.Method == "GET" {
|
||||
ci := _buildCI(r, session, isRoot)
|
||||
if isRoot && (certBase == "root-ca" || certBase == "test-root") {
|
||||
if isRoot && (certBase == "root-ca" || certBase == "test-root" || certBase == "root-01") {
|
||||
ci.IsFirst = true
|
||||
} else if !isRoot && (certBase == "ca-int" || certBase == "test-ca") {
|
||||
} else if !isRoot && (certBase == "ca-int" || certBase == "test-ca" || certBase == "issuer-01") {
|
||||
ci.IsFirst = true
|
||||
}
|
||||
|
||||
if len(r.URL.Query()["root"]) > 0 {
|
||||
certFile := locateFile(r.URL.Query()["root"][0] + ".pem")
|
||||
|
||||
ci.RootEnddate, err = getCertFileNotAFter(certFile)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
@@ -2003,7 +2006,33 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
ci.Organization = val
|
||||
}
|
||||
} else if !isRoot {
|
||||
certFile := locateFile("root-ca.pem")
|
||||
certFile := CERT_FILES_PATH + "root-01-cert.pem"
|
||||
|
||||
// The rules are quite strict on what type is allowed for issuer certs!
|
||||
crt, err := readCertificate(certFile)
|
||||
if err == nil {
|
||||
validKeyTypes := make(map[string]string)
|
||||
|
||||
if crt.PublicKeyAlgorithm == x509.RSA {
|
||||
for k, v := range ci.KeyTypes {
|
||||
if strings.HasPrefix(k, "rsa") {
|
||||
validKeyTypes[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if crt.PublicKeyAlgorithm == x509.ECDSA {
|
||||
if crt.SignatureAlgorithm == x509.ECDSAWithSHA256 {
|
||||
validKeyTypes["ecdsa256"] = "ECDSA-256"
|
||||
}
|
||||
if crt.SignatureAlgorithm == x509.ECDSAWithSHA384 {
|
||||
validKeyTypes["ecdsa384"] = "ECDSA-384"
|
||||
}
|
||||
}
|
||||
|
||||
ci.KeyTypes = validKeyTypes
|
||||
}
|
||||
|
||||
ci.RootEnddate, err = getCertFileNotAFter(certFile)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
@@ -2038,7 +2067,6 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
}
|
||||
ci.Country = r.Form.Get("c")
|
||||
ci.Organization = r.Form.Get("o")
|
||||
ci.OrgUnit = r.Form.Get("ou")
|
||||
ci.CommonName = r.Form.Get("cn")
|
||||
|
||||
ci.RootEnddate = r.Form.Get("root-enddate")
|
||||
@@ -2103,7 +2131,7 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
}
|
||||
if !rootci.StoreCRL("data/") {
|
||||
ci.Errors["Modal"] = rootci.Errors["Modal"]
|
||||
csr, err := os.Open(path + certBase + ".csr")
|
||||
csr, err := os.Open(CERT_FILES_PATH + certBase + ".csr") // TODO !!
|
||||
if err != nil {
|
||||
ci.Errors[cases.Title(language.Und).String(ci.CreateType)] = "Error reading .csr file! See LabCA logs for details"
|
||||
log.Printf("_certCreate: read csr: %v", err)
|
||||
@@ -2118,7 +2146,7 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
}
|
||||
}
|
||||
|
||||
if err := ci.Create(path, certBase, wasCSR); err != nil {
|
||||
if err := ci.Create(certBase, wasCSR); err != nil {
|
||||
if err.Error() == "NO_ROOT_KEY" {
|
||||
if r.Form.Get("generate") != "" {
|
||||
if r.Form.Get("rootkey") == "" {
|
||||
@@ -2142,7 +2170,7 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
}
|
||||
|
||||
if r.Form.Get("getcsr") != "" {
|
||||
csr, err := os.Open(path + certBase + ".csr")
|
||||
csr, err := os.Open(CERT_FILES_PATH + certBase + ".csr") // TODO !
|
||||
if err != nil {
|
||||
ci.Errors[cases.Title(language.Und).String(ci.CreateType)] = "Error reading .csr file! See LabCA logs for details"
|
||||
log.Printf("_certCreate: read csr: %v", err)
|
||||
@@ -2170,27 +2198,13 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
}
|
||||
|
||||
if !ci.IsRoot {
|
||||
nameID, err := issuerNameID(path + certBase + ".pem")
|
||||
nameID, err := issuerNameID(CERT_FILES_PATH + "issuer-01-cert.pem")
|
||||
if err == nil {
|
||||
viper.Set("issuer_name_id", nameID)
|
||||
viper.WriteConfig()
|
||||
} else {
|
||||
log.Printf("_certCreate: could not calculate IssuerNameID: %v", err)
|
||||
}
|
||||
|
||||
if viper.GetBool("keep_root_offline") {
|
||||
if _, err := os.Stat(path + "../root-ca.key"); !errors.Is(err, fs.ErrNotExist) {
|
||||
fmt.Println("Removing private Root key from the system...")
|
||||
if _, err := exeCmd("rm " + path + "../root-ca.key"); err != nil {
|
||||
log.Printf("_certCreate: error deleting root key: %v", err)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(path + "../root-ca.key.der"); !errors.Is(err, fs.ErrNotExist) {
|
||||
if _, err := exeCmd("rm " + path + "../root-ca.key.der"); err != nil {
|
||||
log.Printf("_certCreate: error deleting root key (DER format): %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if viper.Get("labca.organization") == nil {
|
||||
@@ -2202,27 +2216,11 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
session.Values["kt"] = ci.KeyType
|
||||
session.Values["c"] = ci.Country
|
||||
session.Values["o"] = ci.Organization
|
||||
session.Values["ou"] = ci.OrgUnit
|
||||
session.Values["cn"] = ci.CommonName
|
||||
if err = session.Save(r, w); err != nil {
|
||||
log.Printf("cannot save session: %s\n", err)
|
||||
}
|
||||
|
||||
if ci.IsRoot && ci.CreateType == "generate" && r.Form.Get("ack-rootkey") != "yes" {
|
||||
key, err := os.Open(path + certBase + ".key")
|
||||
if err != nil {
|
||||
ci.Errors[cases.Title(language.Und).String(ci.CreateType)] = "Error reading .key file! See LabCA logs for details"
|
||||
log.Printf("_certCreate: read key: %v", err)
|
||||
render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)})
|
||||
return false
|
||||
}
|
||||
defer key.Close()
|
||||
b, _ := io.ReadAll(key)
|
||||
|
||||
render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "RootKey": string(b), "Progress": _progress(certBase), "HelpText": _helptext(certBase)})
|
||||
return false
|
||||
}
|
||||
|
||||
// Fake the method to GET as we need to continue in the setupHandler() function
|
||||
r.Method = "GET"
|
||||
} else {
|
||||
@@ -2234,6 +2232,28 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
|
||||
return true
|
||||
}
|
||||
|
||||
func deleteFiles(pattern string) error {
|
||||
files, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find files: %w", err)
|
||||
}
|
||||
|
||||
ok := true
|
||||
for _, file := range files {
|
||||
err := os.Remove(file)
|
||||
if err != nil {
|
||||
ok = false
|
||||
fmt.Printf("failed to remove %s: %v\n", file, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to remove at least one file, see logs for details")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func _hostCommand(w http.ResponseWriter, r *http.Request, command string, params ...string) bool {
|
||||
conn, err := net.Dial("tcp", "control:3030")
|
||||
if err != nil {
|
||||
@@ -2244,9 +2264,9 @@ func _hostCommand(w http.ResponseWriter, r *http.Request, command string, params
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Fprintf(conn, command+"\n")
|
||||
fmt.Fprint(conn, command+"\n")
|
||||
for _, param := range params {
|
||||
fmt.Fprintf(conn, param+"\n")
|
||||
fmt.Fprint(conn, param+"\n")
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(conn)
|
||||
@@ -2314,12 +2334,12 @@ func _progress(stage string) int {
|
||||
}
|
||||
curr += 3.0
|
||||
|
||||
if stage == "root-ca" {
|
||||
if stage == "root-01" {
|
||||
return int(math.Round(curr / max))
|
||||
}
|
||||
curr += 4.0
|
||||
|
||||
if stage == "ca-int" {
|
||||
if stage == "issuer-01" {
|
||||
return int(math.Round(curr / max))
|
||||
}
|
||||
curr += 3.0
|
||||
@@ -2365,12 +2385,12 @@ func _helptext(stage string) template.HTML {
|
||||
"domain, e.g. '.localdomain'. In lockdown mode only those domains are allowed. In whitelist mode\n",
|
||||
"those domains are allowed next to all official, internet accessible domains and in standard\n",
|
||||
"mode only the official domains are allowed.</p>"))
|
||||
} else if stage == "root-ca" {
|
||||
} else if stage == "root-01" {
|
||||
return template.HTML(fmt.Sprint("<p>This is the top level certificate that will sign the issuer\n",
|
||||
"certificate(s). You can either generate a fresh Root CA (Certificate Authority) or import an\n",
|
||||
"existing one, e.g. a backup from another LabCA instance.</p>\n",
|
||||
"<p>If you want to <b>generate</b> a new certificate, pick a key type and strength (the higher the number the\n",
|
||||
"more secure, ECDSA is more modern than RSA), provide at least a country and organization name,\n",
|
||||
"more secure, ECDSA is more modern than RSA), provide a country and organization name,\n",
|
||||
"and the common name. It is recommended that the common name contains the word 'Root' as well\n",
|
||||
"as your organization name so you can recognize it, and that's why that is automatically filled\n",
|
||||
"once you leave the organization field.</p>\n",
|
||||
@@ -2378,7 +2398,7 @@ func _helptext(stage string) template.HTML {
|
||||
"offline for security reasons according to best practices. If you do include it here, we will be able\n",
|
||||
"to generate an issuing certificate automatically in the next step. If you don't include it, we will\n",
|
||||
"ask for it when needed.</p>"))
|
||||
} else if stage == "ca-int" {
|
||||
} else if stage == "issuer-01" {
|
||||
return template.HTML(fmt.Sprint("<p>This is what end users will see as the issuing certificate. Again,\n",
|
||||
"you can either generate a fresh certificate or import an existing one, as long as it is signed by\n",
|
||||
"the Root CA from the previous step.</p>\n",
|
||||
@@ -2458,7 +2478,7 @@ func _setupAdminUser(w http.ResponseWriter, r *http.Request) bool {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Fprintf(conn, "backup-restore\n"+header.Filename+"\n")
|
||||
fmt.Fprint(conn, "backup-restore\n"+header.Filename+"\n")
|
||||
reader := bufio.NewReader(conn)
|
||||
message, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
@@ -2734,18 +2754,18 @@ func setupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// 3. Setup root CA certificate
|
||||
if !_certCreate(w, r, "root-ca", true) {
|
||||
if !_certCreate(w, r, "root-01", true) {
|
||||
// Cleanup the cert (if it even exists) so we will retry on the next run
|
||||
if _, err := os.Stat("data/root-ca.pem"); !errors.Is(err, fs.ErrNotExist) {
|
||||
exeCmd("mv data/root-ca.pem data/root-ca.pem_TMP")
|
||||
if _, err := os.Stat(CERT_FILES_PATH + "root-01-cert.pem"); !errors.Is(err, fs.ErrNotExist) {
|
||||
exeCmd("mv " + CERT_FILES_PATH + "root-01-cert.pem " + CERT_FILES_PATH + "root-01-cert.pem_TMP")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 4. Setup issuer certificate
|
||||
if !_certCreate(w, r, "ca-int", false) {
|
||||
if !_certCreate(w, r, "issuer-01", false) {
|
||||
// Cleanup the cert (if it even exists) so we will retry on the next run
|
||||
os.Remove("data/issuer/ca-int.pem")
|
||||
os.Remove(CERT_FILES_PATH + "issuer-01-cert.pem")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3326,9 +3346,10 @@ func init() {
|
||||
port := flag.Int("port", 0, "Port to listen on (default 3000 when using init)")
|
||||
versionFlag := flag.Bool("version", false, "Show version number and exit")
|
||||
decrypt := flag.String("d", "", "Decrypt a value")
|
||||
renewcrl := flag.Int("renewcrl", 0, "Check root CRL files and renew if nextUpdate is in less than this number of days")
|
||||
flag.Parse()
|
||||
|
||||
if *versionFlag {
|
||||
if *versionFlag && standaloneVersion != "" {
|
||||
fmt.Println(standaloneVersion)
|
||||
os.Exit(0)
|
||||
}
|
||||
@@ -3355,6 +3376,11 @@ func init() {
|
||||
panic(fmt.Errorf("fatal error config file: '%s'", err))
|
||||
}
|
||||
|
||||
if *versionFlag && standaloneVersion == "" {
|
||||
fmt.Println(viper.GetString("version"))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *decrypt != "" {
|
||||
plain, err := _decrypt(*decrypt)
|
||||
if err == nil {
|
||||
@@ -3365,6 +3391,57 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
if *renewcrl != 0 {
|
||||
crlFiles, err := filepath.Glob(filepath.Join(CERT_FILES_PATH, "root-*-crl.pem"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, crlFile := range crlFiles {
|
||||
read, err := os.ReadFile(crlFile)
|
||||
if err != nil {
|
||||
fmt.Printf("could not read '%s': %s\n", crlFile, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
block, _ := pem.Decode(read)
|
||||
if block == nil || block.Type != "X509 CRL" {
|
||||
fmt.Println(block)
|
||||
fmt.Println("failed to decode PEM block containing revocation list")
|
||||
os.Exit(1)
|
||||
}
|
||||
crl, err := x509.ParseRevocationList(block.Bytes)
|
||||
if err != nil {
|
||||
fmt.Printf("could not parse revocation list: %s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if crl.NextUpdate.Sub(now) < time.Hour*24*time.Duration(*renewcrl) {
|
||||
fmt.Printf("renewing crl file '%s'...\n", crlFile)
|
||||
re := regexp.MustCompile(`-(\d{2})-`)
|
||||
match := re.FindStringSubmatch(crlFile)
|
||||
if len(match) > 1 {
|
||||
seqnr := match[1]
|
||||
ci := &CertificateInfo{}
|
||||
ci.Initialize()
|
||||
err = ci.CeremonyRootCRL(seqnr)
|
||||
if err == nil {
|
||||
fmt.Printf("updated %s\n", crlFile)
|
||||
} else {
|
||||
fmt.Printf("could not update crl file '%s': %s\n", crlFile, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("could not extract sequence number from filename '%s'\n", crlFile)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var err error
|
||||
if *init || viper.GetBool("standalone") {
|
||||
tmpls, err = templates.New().ParseEmbed(embeddedTemplates, "templates/")
|
||||
@@ -3452,6 +3529,10 @@ func init() {
|
||||
|
||||
updateAvailable = false
|
||||
|
||||
if !viper.GetBool("standalone") {
|
||||
CheckUpgrades()
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: Still needs to be done for this!
|
||||
// Store boulder chains if we don't have them already
|
||||
@@ -3474,6 +3555,47 @@ func init() {
|
||||
*/
|
||||
}
|
||||
|
||||
type BackupResult struct {
|
||||
Existed bool
|
||||
NewName string
|
||||
OrigName string
|
||||
}
|
||||
|
||||
func (br BackupResult) Remove() {
|
||||
os.Remove(br.NewName)
|
||||
}
|
||||
|
||||
func (br BackupResult) Restore() {
|
||||
if br.Existed {
|
||||
os.Rename(br.NewName, br.OrigName)
|
||||
}
|
||||
}
|
||||
|
||||
func renameBackup(filename string) BackupResult {
|
||||
result := BackupResult{
|
||||
Existed: false,
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filename); !errors.Is(err, os.ErrNotExist) {
|
||||
os.Remove(filename + "_BAK") // May not exist...
|
||||
result.Existed = true
|
||||
}
|
||||
|
||||
if !result.Existed {
|
||||
return result
|
||||
}
|
||||
|
||||
err := os.Rename(filename, filename+"_BAK")
|
||||
if err != nil {
|
||||
fmt.Printf("warning: failed to backup previous file '%s': %s\n", filename, err.Error())
|
||||
} else {
|
||||
result.OrigName = filename
|
||||
result.NewName = filename + "_BAK"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func main() {
|
||||
tmpls.Parse()
|
||||
|
||||
|
||||
@@ -60,8 +60,7 @@
|
||||
<thead><tr>
|
||||
<th>CA Type</th>
|
||||
<th>Distinguished Name</th>
|
||||
<th>Windows format</th>
|
||||
<th>Linux format</th>
|
||||
<th>Certificate File</th>
|
||||
<th>Validity Period</th>
|
||||
<th>CRL</th>
|
||||
<th>CRL Validity</th>
|
||||
@@ -70,17 +69,15 @@
|
||||
<tr>
|
||||
<td>Root CA</td>
|
||||
<td><!-- BEGIN PKI_ROOT_DN -->PKI_ROOT_DN<!-- END PKI_ROOT_DN --></td>
|
||||
<td><a class="public" href="root-ca.der">root-ca.der</a></td>
|
||||
<td><a class="public" href="root-ca.pem">root-ca.pem</a></td>
|
||||
<td><!-- BEGIN PKI_ROOT_LINK --><a class="public" href="root-01-cert.pem">root-01-cert.pem</a><!-- END PKI_ROOT_LINK --></td>
|
||||
<td><!-- BEGIN PKI_ROOT_VALIDITY -->PKI_ROOT_VALIDITY<!-- END PKI_ROOT_VALIDITY --></td>
|
||||
<td><a class="public" href="../crl/root-ca.crl">root-ca.crl</a></td>
|
||||
<td><!-- BEGIN PKI_ROOT_CRL_LINK --><!-- END PKI_ROOT_CRL_LINK --></td>
|
||||
<td><!-- BEGIN PKI_ROOT_CRL_VALIDITY --><!-- END PKI_ROOT_CRL_VALIDITY --></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Issuing CA</td>
|
||||
<td><!-- BEGIN PKI_INT_DN -->PKI_INT_DN<!-- END PKI_INT_DN --></td>
|
||||
<td><a class="public" href="ca-int.der">ca-int.der</a></td>
|
||||
<td><a class="public" href="ca-int.pem">ca-int.pem</a></td>
|
||||
<td><!-- BEGIN PKI_INT_LINK --><a class="public" href="issuer-01-cert.pem">issuer-01-cert.pem</a><!-- END PKI_INT_LINK --></td>
|
||||
<td><!-- BEGIN PKI_INT_VALIDITY -->PKI_INT_VALIDITY<!-- END PKI_INT_VALIDITY --></td>
|
||||
<td><!-- BEGIN PKI_INT_CRL_LINK --><!-- END PKI_INT_CRL_LINK --></td>
|
||||
<td><!-- BEGIN PKI_INT_CRL_VALIDITY --><!-- END PKI_INT_CRL_VALIDITY --></td>
|
||||
@@ -90,9 +87,7 @@
|
||||
|
||||
<p>
|
||||
To trust the certificates provided by <!-- BEGIN WEBTITLE -->LabCA<!-- END WEBTITLE -->, all your client devices
|
||||
should install the root certificate in their <b>Trusted Root Certification Authorities</b> store. You may choose
|
||||
to download the format best suited for your Operating System: DER format for Windows machines or PEM format for
|
||||
Linux/unix machines and Android phones.
|
||||
should install the root certificate in their <b>Trusted Root Certification Authorities</b> store.
|
||||
</p>
|
||||
<p>
|
||||
The CRL (Certificate Revocation List) is a type of blocklist that includes certificates that should no longer be
|
||||
|
||||
26
gui/templates/cert-ceremonies/issuer-cert.yaml
Normal file
26
gui/templates/cert-ceremonies/issuer-cert.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
ceremony-type: intermediate
|
||||
pkcs11:
|
||||
module: {{ .Module }}
|
||||
pin: {{ .UserPIN }}
|
||||
signing-key-slot: {{ .RootSlotID }}
|
||||
signing-key-label: {{ .RootLabel }}
|
||||
inputs:
|
||||
public-key-path: {{ .Path }}issuer-{{ .SeqNr }}-pubkey.pem
|
||||
issuer-certificate-path: {{ .Path }}root-{{ .RootSeqNr }}-cert.pem
|
||||
outputs:
|
||||
certificate-path: {{ .Path }}issuer-{{ .SeqNr }}-cert.pem
|
||||
certificate-profile:
|
||||
signature-algorithm: {{ .SignAlgorithm }}
|
||||
common-name: {{ .CommonName }}
|
||||
organization: {{ .OrgName }}
|
||||
country: {{ .Country }}
|
||||
not-before: {{ .NotBefore }}
|
||||
not-after: {{ .NotAfter }}
|
||||
crl-url: {{ .CrlUrl }}
|
||||
issuer-url: {{ .IssuerUrl }}
|
||||
policies:
|
||||
- oid: 2.23.140.1.2.1
|
||||
key-usages:
|
||||
- Digital Signature
|
||||
- Cert Sign
|
||||
- CRL Sign
|
||||
19
gui/templates/cert-ceremonies/issuer-key.yaml
Normal file
19
gui/templates/cert-ceremonies/issuer-key.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
ceremony-type: key
|
||||
pkcs11:
|
||||
module: {{ .Module }}
|
||||
pin: {{ .UserPIN }}
|
||||
store-key-in-slot: {{ .SlotID }}
|
||||
store-key-with-label: {{ .Label }}
|
||||
key:
|
||||
type: {{ .KeyType }}
|
||||
{{ if eq .KeyType "rsa" }}
|
||||
rsa-mod-length: {{ .KeyParam }}
|
||||
{{ else }}
|
||||
ecdsa-curve: {{ .KeyParam }}
|
||||
{{ end }}
|
||||
{{ if eq .Extractable "true" }}
|
||||
extractable: true
|
||||
{{ end }}
|
||||
outputs:
|
||||
public-key-path: {{ .Path }}issuer-{{ .SeqNr }}-pubkey.pem
|
||||
pkcs11-config-path: {{ .Path }}issuer-{{ .SeqNr }}.pkcs11.json
|
||||
14
gui/templates/cert-ceremonies/root-crl.yaml
Normal file
14
gui/templates/cert-ceremonies/root-crl.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
ceremony-type: crl
|
||||
pkcs11:
|
||||
module: {{ .Module }}
|
||||
pin: {{ .UserPIN }}
|
||||
signing-key-slot: {{ .RootSlotID }}
|
||||
signing-key-label: {{ .RootLabel }}
|
||||
inputs:
|
||||
issuer-certificate-path: {{ .Path }}root-{{ .RootSeqNr }}-cert.pem
|
||||
outputs:
|
||||
crl-path: {{ .Path }}root-{{ .RootSeqNr }}-crl.pem
|
||||
crl-profile:
|
||||
this-update: {{ .ThisUpdate }}
|
||||
next-update: {{ .NextUpdate }}
|
||||
number: {{ .CrlNumber }}
|
||||
34
gui/templates/cert-ceremonies/root.yaml
Normal file
34
gui/templates/cert-ceremonies/root.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
ceremony-type: root
|
||||
pkcs11:
|
||||
module: {{ .Module }}
|
||||
pin: {{ .UserPIN }}
|
||||
store-key-in-slot: {{ .SlotID }}
|
||||
store-key-with-label: {{ .Label }}
|
||||
key:
|
||||
type: {{ .KeyType }}
|
||||
{{ if eq .KeyType "rsa" }}
|
||||
rsa-mod-length: {{ .KeyParam }}
|
||||
{{ else }}
|
||||
ecdsa-curve: {{ .KeyParam }}
|
||||
{{ end }}
|
||||
{{ if eq .Extractable "true" }}
|
||||
extractable: true
|
||||
{{ end }}
|
||||
outputs:
|
||||
public-key-path: {{ .Path }}root-{{ .SeqNr }}-pubkey.pem
|
||||
certificate-path: {{ .Path }}root-{{ .SeqNr }}-cert.pem
|
||||
certificate-profile:
|
||||
signature-algorithm: {{ .SignAlgorithm }}
|
||||
common-name: {{ .CommonName }}
|
||||
organization: {{ .OrgName }}
|
||||
country: {{ .Country }}
|
||||
not-before: {{ .NotBefore }}
|
||||
not-after: {{ .NotAfter }}
|
||||
key-usages:
|
||||
- Cert Sign
|
||||
- CRL Sign
|
||||
skip-lints:
|
||||
- n_ca_digital_signature_not_set
|
||||
{{ if eq .Renewal "true" }}
|
||||
renewal: true
|
||||
{{ end }}
|
||||
@@ -59,13 +59,6 @@
|
||||
<span class="error">{{ . }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ou">Org. Unit (optional):</label>
|
||||
<input class="form-control" type="text" id="ou" name="ou" value="{{ .OrgUnit }}" {{ if and (ne .Organization "") (.IsRootGenerated) }}readonly{{ end }}>
|
||||
{{ with .Errors.OrgUnit }}
|
||||
<span class="error">{{ . }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cn">Common Name:</label>
|
||||
<input class="form-control" type="text" id="cn" name="cn" value="{{ .CommonName }}" required>
|
||||
@@ -136,12 +129,12 @@
|
||||
<textarea class="form-control" id="certificate" name="certificate" rows="10" cols="80" required>{{ .Certificate }}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="key">Key (in PEM format{{ if .IsRoot }}; optional{{ end }}):
|
||||
<label for="key">Key (in PEM format):
|
||||
{{ with .Errors.Key }}
|
||||
<span class="error"><br/>{{ . }}</span>
|
||||
{{ end }}
|
||||
</label>
|
||||
<textarea class="form-control" id="key" name="key" rows="10" cols="80">{{ .Key }}</textarea>
|
||||
<textarea class="form-control" id="key" name="key" rows="10" cols="80" required>{{ .Key }}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="passphrase">Passphrase (optional):
|
||||
@@ -353,4 +346,4 @@
|
||||
updateEndDate();
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -165,6 +165,7 @@
|
||||
<tr>
|
||||
<th>Subject</th>
|
||||
<th></th>
|
||||
<th>Type</th>
|
||||
<th>Active</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@@ -177,10 +178,11 @@
|
||||
<td class="vmiddle">
|
||||
<a href="/certs/{{ $item.RootCert.BaseName }}.pem" title="Download this certificate">download</a>
|
||||
</td>
|
||||
<td class="vmiddle">{{ $item.RootCert.KeyType }}</td>
|
||||
<td class="vmiddle"></td>
|
||||
<td class="vmiddle">
|
||||
<button class="btn btn-outline btn-reg btn-success export-cert-key" type="button" title="Export this certificate and key" data-name="{{ $item.RootCert.BaseName }}" data-subject="{{ $item.RootCert.Subject }}">Export</button>
|
||||
<button class="btn btn-outline btn-reg btn-warning renew-cert" type="button" title="Generate new version with extended lifetime" data-name="{{ $item.RootCert.BaseName }}" data-rootname="{{ $item.RootCert.BaseName }}" data-subject="{{ $item.RootCert.Subject }}" data-rootsubject="{{ $item.RootCert.Subject }}" data-notbefore="{{ $item.RootCert.NotAfter }}" data-isroot="true">Renew</button>
|
||||
<button class="btn btn-outline btn-reg btn-warning renew-cert" type="button" title="Generate new version with extended lifetime" data-name="{{ $item.RootCert.BaseName }}" data-rootname="{{ $item.RootCert.BaseName }}" data-subject="{{ $item.RootCert.Subject }}" data-keytype="{{ $item.RootCert.KeyType }}" data-rootsubject="{{ $item.RootCert.Subject }}" data-notbefore="{{ $item.RootCert.NotAfter }}" data-isroot="true">Renew</button>
|
||||
<!--
|
||||
<button class="btn btn-outline btn-new-issuer btn-warning new-issuer-cert" type="button" title="Generate new Issuer Certificate under this Root" data-root="{{ $item.RootCert.BaseName }}" data-rootsubject="{{ $item.RootCert.Subject }}">New Issuer</button>
|
||||
<button class="btn btn-outline btn-reg btn-danger delete-cert hidden" type="button" title="Delete this certificate" data-name="{{ $item.RootCert.BaseName }}">Delete</button>
|
||||
@@ -196,10 +198,11 @@
|
||||
<td class="vmiddle">
|
||||
<a href="/certs/{{ $subitem.BaseName }}.pem" title="Download this certificate">download</a>
|
||||
</td>
|
||||
<td class="vmiddle">{{ $subitem.KeyType }}</td>
|
||||
<td class="vmiddle center"><input type="radio" id="active-{{ $subitem.BaseName }}" name="issue-active" value="{{ $subitem.BaseName }}" title="Use this certificate for issueing leave certificates" {{ if $subitem.ActiveIssuer }}data-orig="true" checked{{ end }}/></td>
|
||||
<td class="vmiddle">
|
||||
<button class="btn btn-outline btn-reg btn-success export-cert-key" type="button" title="Export this certificate and key" data-name="{{ $subitem.BaseName }}" data-subject="{{ $subitem.Subject }}">Export</button>
|
||||
<button class="btn btn-outline btn-reg btn-warning renew-cert" type="button" title="Generate new version with extended lifetime" data-name="{{ $subitem.BaseName }}" data-rootname="{{ $item.RootCert.BaseName }}" data-subject="{{ $subitem.Subject }}" data-rootsubject="{{ $item.RootCert.Subject }}" data-notbefore="{{ $subitem.NotAfter }}" data-notafter="{{ $item.RootCert.NotAfter }}" data-isroot="false">Renew</button>
|
||||
<button class="btn btn-outline btn-reg btn-warning renew-cert" type="button" title="Generate new version with extended lifetime" data-name="{{ $subitem.BaseName }}" data-rootname="{{ $item.RootCert.BaseName }}" data-subject="{{ $subitem.Subject }}" data-keytype="{{ $subitem.KeyType }}" data-rootsubject="{{ $item.RootCert.Subject }}" data-notbefore="{{ $subitem.NotAfter }}" data-notafter="{{ $item.RootCert.NotAfter }}" data-isroot="false">Renew</button>
|
||||
<!--
|
||||
<button class="btn btn-outline btn-reg btn-danger delete-cert" type="button" title="Delete this certificate" data-name="{{ $subitem.BaseName }}">Delete</button>
|
||||
-->
|
||||
@@ -226,13 +229,12 @@
|
||||
<td class="vmiddle">Root CRL</td>
|
||||
<td class="vmiddle">
|
||||
<button class="btn btn-outline btn-success mt5" type="button" id="gen-root-crl" title="Generate root CRL now (requires Root CA key)">Generate</button>
|
||||
<button class="btn btn-outline btn-success mt5" type="button" id="upload-root-crl" title="Upload a root CRL">Upload</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="vmiddle">Issuer CRL</td>
|
||||
<td class="vmiddle">
|
||||
<button class="btn btn-outline btn-success mt5" type="button" id="gen-issuer-crl" title="Generate root CRL now (requires Root CA key)">Generate</button>
|
||||
<button class="btn btn-outline btn-success mt5" type="button" id="gen-issuer-crl" title="Generate issuer CRL now">Generate</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -549,7 +551,8 @@
|
||||
<input type="hidden" id="modal-renew-cert" name="modal-renew-cert">
|
||||
<input type="hidden" id="renew-rootcert" name="renew-rootcert">
|
||||
<input type="hidden" id="renew-rootsubject" name="renew-rootsubject">
|
||||
Subject: <span id="renew-subject"></span><br><br>
|
||||
Subject: <span id="renew-subject"></span><br>
|
||||
Key Type: <span id="renew-keytype"></span><br><br>
|
||||
Current end date: <span id="renew-current-enddate"></span><br>
|
||||
<span id="renew-show-root-enddate" class="hidden">Root end date: <span id="renew-root-enddate"></span><br></span>
|
||||
<br>
|
||||
@@ -844,6 +847,9 @@
|
||||
}
|
||||
|
||||
$('#modal-export').modal('hide');
|
||||
|
||||
} else if (event.currentTarget.status == 400 || event.currentTarget.statusText == "Bad Request") {
|
||||
$("#modal-export-error").removeClass("hidden").show().text("Key is not extractable from the HSM!");
|
||||
} else {
|
||||
$("#modal-export-error").removeClass("hidden").show().text("Backend returned: " + event.currentTarget.statusText);
|
||||
}
|
||||
@@ -1363,6 +1369,7 @@
|
||||
$('#modal-renew-cert').val($(evt.target).data('name'));
|
||||
$('#renew-rootcert').val($(evt.target).data('rootname'));
|
||||
$('#renew-subject').text($(evt.target).data('subject'));
|
||||
$('#renew-keytype').text($(evt.target).data('keytype'));
|
||||
$('#renew-rootsubject').val($(evt.target).data('rootsubject'));
|
||||
d = new Date($(evt.target).data('notbefore')).toUTCString()
|
||||
$('#renew-current-enddate').text(d);
|
||||
|
||||
191
gui/upgrades.go
Normal file
191
gui/upgrades.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func CheckUpgrades() {
|
||||
v := viper.GetString("version")
|
||||
if standaloneVersion == "" {
|
||||
gitVersion := controlCommand("git-version")
|
||||
if gitVersion != "" {
|
||||
viper.Set("version", gitVersion)
|
||||
viper.WriteConfig()
|
||||
}
|
||||
} else if v != standaloneVersion {
|
||||
viper.Set("version", standaloneVersion)
|
||||
viper.WriteConfig()
|
||||
}
|
||||
|
||||
changed := CheckUpgrade_01_CeremonyHSM()
|
||||
|
||||
if changed {
|
||||
time.Sleep(2 * time.Second)
|
||||
log.Println("Applying updated configuration...")
|
||||
controlCommand("apply")
|
||||
time.Sleep(2 * time.Second)
|
||||
log.Println("Updating CRL links if needed...")
|
||||
controlCommand("check-crl")
|
||||
time.Sleep(2 * time.Second)
|
||||
log.Println("Restarting boulder containers...")
|
||||
controlCommand("boulder-restart")
|
||||
}
|
||||
}
|
||||
|
||||
func readFileAsString(filename string) string {
|
||||
read, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
log.Printf("**** Could not read '%s': %s\n", filename, err.Error())
|
||||
log.Println("**** ABORT MIGRATION ****")
|
||||
time.Sleep(1 * time.Minute)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return string(read)
|
||||
}
|
||||
|
||||
func controlCommand(command string) string {
|
||||
conn, err := net.Dial("tcp", "control:3030")
|
||||
if err != nil {
|
||||
log.Println("**** Failed to connect to control container!")
|
||||
time.Sleep(1 * time.Minute)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Fprint(conn, command+"\n")
|
||||
|
||||
reader := bufio.NewReader(conn)
|
||||
message, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
log.Printf("**** Failed to read response from control container: %s\n", err.Error())
|
||||
time.Sleep(1 * time.Minute)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(message) >= 4 {
|
||||
tail := message[len(message)-4:]
|
||||
if strings.Compare(string(tail), "\nok\n") == 0 {
|
||||
msg := message[0 : len(message)-4]
|
||||
log.Printf("**** Message from control server: '%s'", msg)
|
||||
}
|
||||
}
|
||||
|
||||
return string(message)
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
sourceFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
destinationFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destinationFile.Close()
|
||||
|
||||
_, err = io.Copy(destinationFile, sourceFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = destinationFile.Sync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if we should upgrade to using the Ceremony tool and store keys on SoftHSM (January 2025).
|
||||
func CheckUpgrade_01_CeremonyHSM() bool {
|
||||
baseDir := "/opt/labca/data/"
|
||||
prevRootCert := baseDir + "root-ca.pem"
|
||||
if _, err := os.Stat(prevRootCert); errors.Is(err, fs.ErrNotExist) {
|
||||
baseDir = "/go/src/labca/data/"
|
||||
prevRootCert = baseDir + "root-ca.pem"
|
||||
if _, err := os.Stat(prevRootCert); errors.Is(err, fs.ErrNotExist) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("**** BEGIN MIGRATION: upgrade01 ****")
|
||||
|
||||
rootCertFile := fmt.Sprintf("%sroot-01-cert.pem", CERT_FILES_PATH)
|
||||
if _, err := os.Stat(rootCertFile); !errors.Is(err, fs.ErrNotExist) {
|
||||
log.Printf("**** File %s already exists!\n", rootCertFile)
|
||||
log.Println("**** ABORT MIGRATION ****")
|
||||
time.Sleep(1 * time.Minute)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
prevRootKey := baseDir + "root-ca.key"
|
||||
if _, err := os.Stat(prevRootKey); errors.Is(err, fs.ErrNotExist) {
|
||||
log.Println("**** Root key file not present on the system: cannot upgrade automatically!")
|
||||
log.Println("**** Please do a fresh install of LabCA and import / upload the root certificate and key.")
|
||||
log.Println("**** ABORT MIGRATION ****")
|
||||
time.Sleep(1 * time.Minute)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Migrate root certificate and key
|
||||
ci := &CertificateInfo{IsRoot: true}
|
||||
ci.Initialize()
|
||||
ci.IsRoot = true
|
||||
ci.CreateType = "upload"
|
||||
ci.Certificate = readFileAsString(prevRootCert)
|
||||
ci.Key = readFileAsString(prevRootKey)
|
||||
prevRootCRL := baseDir + "root-ca.crl"
|
||||
if _, err := os.Stat(prevRootCRL); !errors.Is(err, fs.ErrNotExist) {
|
||||
ci.CRL = readFileAsString(prevRootCRL)
|
||||
copyFile(prevRootCRL, strings.Replace(rootCertFile, "-cert.", "-crl.", -1))
|
||||
}
|
||||
|
||||
if err := ci.Create("root-01", false); err != nil {
|
||||
log.Printf("**** Could not convert previous root certificate and key: %s\n", err.Error())
|
||||
log.Println("**** ABORT MIGRATION ****")
|
||||
time.Sleep(1 * time.Minute)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Migrate issuer certificate and key
|
||||
ci = &CertificateInfo{IsRoot: false}
|
||||
ci.Initialize()
|
||||
ci.IsRoot = false
|
||||
ci.CreateType = "upload"
|
||||
prevIssuerCert := baseDir + "issuer/ca-int.pem"
|
||||
ci.Certificate = readFileAsString(prevIssuerCert)
|
||||
prevIssuerKey := baseDir + "issuer/ca-int.key"
|
||||
ci.Key = readFileAsString(prevIssuerKey)
|
||||
ci.CRL = ""
|
||||
|
||||
if err := ci.Create("issuer-01", false); err != nil {
|
||||
log.Printf("**** Could not convert previous issuer certificate and key: %s\n", err.Error())
|
||||
log.Println("**** ABORT MIGRATION ****")
|
||||
time.Sleep(1 * time.Minute)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Rename(prevRootCert, prevRootCert+"_backup")
|
||||
os.Rename(prevRootKey, prevRootKey+"_backup")
|
||||
os.Rename(prevRootCRL, prevRootCRL+"_backup")
|
||||
os.Rename(prevIssuerCert, prevIssuerCert+"_backup")
|
||||
os.Rename(prevIssuerKey, prevIssuerKey+"_backup")
|
||||
|
||||
log.Println("**** END MIGRATION ****")
|
||||
return true
|
||||
}
|
||||
11
install
11
install
@@ -517,13 +517,8 @@ static_web() {
|
||||
mkdir -p crl
|
||||
[ -e cert ] || ln -s certs cert
|
||||
cp -rp $cloneDir/gui/static/* .
|
||||
[ -e $adminDir/data/root-ca.crl ] && cp $adminDir/data/root-ca.crl crl/ || true
|
||||
[ -e $adminDir/data/root-ca.pem ] && cp $adminDir/data/root-ca.pem certs/ || true
|
||||
[ -e $adminDir/data/root-ca.pem ] && ln -sf root-ca.pem certs/test-root.pem || true
|
||||
[ -e $adminDir/data/root-ca.der ] && cp $adminDir/data/root-ca.der certs/ || true
|
||||
[ -e $adminDir/data/issuer/ca-int.pem ] && cp $adminDir/data/issuer/ca-int.pem certs/ || true
|
||||
[ -e $adminDir/data/issuer/ca-int.pem ] && ln -sf ca-int.pem certs/test-ca.pem || true
|
||||
[ -e $adminDir/data/issuer/ca-int.der ] && cp $adminDir/data/issuer/ca-int.der certs/ || true
|
||||
|
||||
local have_config=$(grep restarted $adminDir/data/config.json | grep true)
|
||||
if [ "$have_config" != "" ]; then
|
||||
@@ -868,6 +863,12 @@ main() {
|
||||
curChecksum=$(md5sum $this 2>/dev/null | cut -d' ' -f1)
|
||||
[ ! -e "$cloneDir/cron_d" ] || chown labca:labca "$cloneDir/cron_d"
|
||||
|
||||
# Stop any running containers to prevent data migration issues...
|
||||
if [ -d "$boulderDir" ]; then
|
||||
cd "$boulderDir"
|
||||
docker compose stop &>/dev/null || true
|
||||
fi
|
||||
|
||||
parse_cmdline "$@"
|
||||
if [ $keepLocal -eq 0 ]; then
|
||||
clone_or_pull "$cloneDir" "$labcaUrl" "$cmdlineBranch"
|
||||
|
||||
45
patch-cfg.sh
45
patch-cfg.sh
@@ -44,27 +44,20 @@ fi
|
||||
for f in $(grep -l boulder-proxysql $boulderLabCADir/secrets/*); do sed -i -e "s/proxysql:6033/mysql:3306/" $f; done
|
||||
|
||||
cd "$boulderLabCADir"
|
||||
sed -i -e "s/test-ca2.pem/test-ca.pem/" config/ocsp-responder.json
|
||||
sed -i -e "s/test-ca2.pem/test-ca.pem/" config/publisher.json
|
||||
sed -i -e "s/test-ca2.pem/test-ca.pem/" config/ra.json
|
||||
sed -i -e "s/test-ca2.pem/test-ca.pem/" config/wfe2.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/test-ca.pem|" config/akamai-purger.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/test-ca.pem|" config/ocsp-responder.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/test-ca.pem|" config/publisher.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/test-ca.pem|" config/ca.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/test-ca.pem|" config/wfe2.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/test-ca.pem|" config/crl-storer.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/test-ca.pem|" config/crl-updater.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/test-ca.pem|" config/ra.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/test-ca.pem|" v2_integration.py
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.pkcs11.json|labca/test-ca.key-pkcs11.json|" config/ca.json
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/test-root.pem|" certs/root-ceremony-rsa.yaml
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/test-root.pem|" certs/root-crl-rsa.yaml
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/test-root.pem|" certs/intermediate-cert-ceremony-rsa.yaml
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/test-root.pem|" config/publisher.json
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/test-root.pem|" config/wfe2.json
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/test-root.pem|" integration-test.py
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/test-root.pem|" helpers.py
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/certs/webpki/issuer-01-cert.pem|" config/ocsp-responder.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/certs/webpki/issuer-01-cert.pem|" config/publisher.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/certs/webpki/issuer-01-cert.pem|" config/ca.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/certs/webpki/issuer-01-cert.pem|" config/wfe2.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/certs/webpki/issuer-01-cert.pem|" config/crl-storer.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/certs/webpki/issuer-01-cert.pem|" config/crl-updater.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.cert.pem|labca/certs/webpki/issuer-01-cert.pem|" config/ra.json
|
||||
sed -i -e "s|test/certs/webpki/int-rsa-a.pkcs11.json|labca/certs/webpki/issuer-01.pkcs11.json|" config/ca.json
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/certs/webpki/root-01-cert.pem|" certs/root-ceremony-rsa.yaml
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/certs/webpki/root-01-cert.pem|" certs/root-crl-rsa.yaml
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/certs/webpki/root-01-cert.pem|" certs/intermediate-cert-ceremony-rsa.yaml
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/certs/webpki/root-01-cert.pem|" config/publisher.json
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/certs/webpki/root-01-cert.pem|" config/wfe2.json
|
||||
sed -i -e "s|test/certs/webpki/root-rsa.cert.pem|labca/certs/webpki/root-01-cert.pem|" helpers.py
|
||||
sed -i -e "s|letsencrypt/boulder|hakwerk/labca|" config/wfe2.json
|
||||
sed -i -e "s|1.2.3.4|1.3.6.1.4.1.44947.1.1.1|g" config/ca.json
|
||||
sed -i -e "s/ocspURL.Path = encodedReq/ocspURL.Path += encodedReq/" ocsp/helper/helper.go
|
||||
@@ -90,14 +83,4 @@ done
|
||||
|
||||
sed -i -e "s/names/name\(s\)/" config/expiration-mailer.gotmpl
|
||||
|
||||
if [ ! -e "test-ca.key-pkcs11.json" ]; then
|
||||
cat > test-ca.key-pkcs11.json <<EOL
|
||||
{
|
||||
"module": "/usr/lib/softhsm/libsofthsm2.so",
|
||||
"tokenLabel": "intermediate signing key (rsa)",
|
||||
"pin": "1234"
|
||||
}
|
||||
EOL
|
||||
fi
|
||||
|
||||
rm -f test-ca2.pem
|
||||
|
||||
5
patch.sh
5
patch.sh
@@ -23,6 +23,10 @@ $SUDO patch -p1 < $cloneDir/patches/boulder-va_main.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/ca_ca.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/ca_ca_keytype_hack.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/ca_crl.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/ceremony_ecdsa.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/ceremony_key.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/ceremony_main.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/ceremony_rsa.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/cert-checker_main.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/cmd_config.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/config_duration.patch
|
||||
@@ -50,6 +54,7 @@ $SUDO patch -p1 < $cloneDir/patches/ra_ra.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/ratelimit_rate-limits.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/ratelimits_names.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/remoteva_main.patch
|
||||
$SUDO patch -p1 < $cloneDir/patches/start.patch
|
||||
if [ "$SUDO" == "" ]; then
|
||||
# TODO: should include this into startservers.patch
|
||||
$SUDO patch -p1 < $cloneDir/build/tmp2.patch
|
||||
|
||||
40
patches/ceremony_ecdsa.patch
Normal file
40
patches/ceremony_ecdsa.patch
Normal file
@@ -0,0 +1,40 @@
|
||||
diff --git a/cmd/ceremony/ecdsa.go b/cmd/ceremony/ecdsa.go
|
||||
index 65f5c6f99..24102ad8e 100644
|
||||
--- a/cmd/ceremony/ecdsa.go
|
||||
+++ b/cmd/ceremony/ecdsa.go
|
||||
@@ -29,7 +29,7 @@ var curveToOIDDER = map[string][]byte{
|
||||
// ecArgs constructs the private and public key template attributes sent to the
|
||||
// device and specifies which mechanism should be used. curve determines which
|
||||
// type of key should be generated.
|
||||
-func ecArgs(label string, curve elliptic.Curve, keyID []byte) generateArgs {
|
||||
+func ecArgs(label string, curve elliptic.Curve, keyID []byte, extractable bool) generateArgs {
|
||||
encodedCurve := curveToOIDDER[curve.Params().Name]
|
||||
log.Printf("\tEncoded curve parameters for %s: %X\n", curve.Params().Name, encodedCurve)
|
||||
return generateArgs{
|
||||
@@ -50,7 +50,7 @@ func ecArgs(label string, curve elliptic.Curve, keyID []byte) generateArgs {
|
||||
// Prevent attributes being retrieved
|
||||
pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true),
|
||||
// Prevent the key being extracted from the device
|
||||
- pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false),
|
||||
+ pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, extractable),
|
||||
// Allow the key to sign data
|
||||
pkcs11.NewAttribute(pkcs11.CKA_SIGN, true),
|
||||
},
|
||||
@@ -81,7 +81,7 @@ func ecPub(
|
||||
// specified by curveStr and with the provided label. It returns the public
|
||||
// part of the generated key pair as a ecdsa.PublicKey and the random key ID
|
||||
// that the HSM uses to identify the key pair.
|
||||
-func ecGenerate(session *pkcs11helpers.Session, label, curveStr string) (*ecdsa.PublicKey, []byte, error) {
|
||||
+func ecGenerate(session *pkcs11helpers.Session, label, curveStr string, extractable bool) (*ecdsa.PublicKey, []byte, error) {
|
||||
curve, present := stringToCurve[curveStr]
|
||||
if !present {
|
||||
return nil, nil, fmt.Errorf("curve %q not supported", curveStr)
|
||||
@@ -92,7 +92,7 @@ func ecGenerate(session *pkcs11helpers.Session, label, curveStr string) (*ecdsa.
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Printf("Generating ECDSA key with curve %s and ID %x\n", curveStr, keyID)
|
||||
- args := ecArgs(label, curve, keyID)
|
||||
+ args := ecArgs(label, curve, keyID, extractable)
|
||||
pub, _, err := session.GenerateKeyPair(args.mechanism, args.publicAttrs, args.privateAttrs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
19
patches/ceremony_key.patch
Normal file
19
patches/ceremony_key.patch
Normal file
@@ -0,0 +1,19 @@
|
||||
diff --git a/cmd/ceremony/key.go b/cmd/ceremony/key.go
|
||||
index e0ed20594..4c4656b4d 100644
|
||||
--- a/cmd/ceremony/key.go
|
||||
+++ b/cmd/ceremony/key.go
|
||||
@@ -56,12 +56,12 @@ func generateKey(session *pkcs11helpers.Session, label string, outputPath string
|
||||
var keyID []byte
|
||||
switch config.Type {
|
||||
case "rsa":
|
||||
- pubKey, keyID, err = rsaGenerate(session, label, config.RSAModLength)
|
||||
+ pubKey, keyID, err = rsaGenerate(session, label, config.RSAModLength, config.Extractable)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate RSA key pair: %s", err)
|
||||
}
|
||||
case "ecdsa":
|
||||
- pubKey, keyID, err = ecGenerate(session, label, config.ECDSACurve)
|
||||
+ pubKey, keyID, err = ecGenerate(session, label, config.ECDSACurve, config.Extractable)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate ECDSA key pair: %s", err)
|
||||
}
|
||||
85
patches/ceremony_main.patch
Normal file
85
patches/ceremony_main.patch
Normal file
@@ -0,0 +1,85 @@
|
||||
diff --git a/cmd/ceremony/main.go b/cmd/ceremony/main.go
|
||||
index f18979fef..0aa8eb0c7 100644
|
||||
--- a/cmd/ceremony/main.go
|
||||
+++ b/cmd/ceremony/main.go
|
||||
@@ -98,6 +98,7 @@ type keyGenConfig struct {
|
||||
Type string `yaml:"type"`
|
||||
RSAModLength uint `yaml:"rsa-mod-length"`
|
||||
ECDSACurve string `yaml:"ecdsa-curve"`
|
||||
+ Extractable bool `yaml:"extractable"`
|
||||
}
|
||||
|
||||
var allowedCurves = map[string]bool{
|
||||
@@ -174,6 +175,7 @@ type rootConfig struct {
|
||||
} `yaml:"outputs"`
|
||||
CertProfile certProfile `yaml:"certificate-profile"`
|
||||
SkipLints []string `yaml:"skip-lints"`
|
||||
+ Renewal bool `yaml:"renewal"`
|
||||
}
|
||||
|
||||
func (rc rootConfig) validate() error {
|
||||
@@ -189,9 +191,11 @@ func (rc rootConfig) validate() error {
|
||||
}
|
||||
|
||||
// Output fields
|
||||
- err = checkOutputFile(rc.Outputs.PublicKeyPath, "public-key-path")
|
||||
- if err != nil {
|
||||
- return err
|
||||
+ if !rc.Renewal {
|
||||
+ err = checkOutputFile(rc.Outputs.PublicKeyPath, "public-key-path")
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
}
|
||||
err = checkOutputFile(rc.Outputs.CertificatePath, "certificate-path")
|
||||
if err != nil {
|
||||
@@ -629,23 +633,42 @@ func rootCeremony(configBytes []byte) error {
|
||||
return fmt.Errorf("failed to setup session and PKCS#11 context for slot %d: %s", config.PKCS11.StoreSlot, err)
|
||||
}
|
||||
log.Printf("Opened PKCS#11 session for slot %d\n", config.PKCS11.StoreSlot)
|
||||
- keyInfo, err := generateKey(session, config.PKCS11.StoreLabel, config.Outputs.PublicKeyPath, config.Key)
|
||||
- if err != nil {
|
||||
- return err
|
||||
+ var rKeyInfo *keyInfo
|
||||
+ if config.Renewal {
|
||||
+ // Reuse existing root key for a renewal
|
||||
+ pub, _, err := loadPubKey(config.Outputs.PublicKeyPath)
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
+
|
||||
+ der, err := x509.MarshalPKIXPublicKey(pub)
|
||||
+ if err != nil {
|
||||
+ return fmt.Errorf("Failed to marshal public key: %s", err)
|
||||
+ }
|
||||
+
|
||||
+ rKeyInfo = &keyInfo{
|
||||
+ key: pub,
|
||||
+ der: der,
|
||||
+ }
|
||||
+ } else {
|
||||
+ rKeyInfo, err = generateKey(session, config.PKCS11.StoreLabel, config.Outputs.PublicKeyPath, config.Key)
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
}
|
||||
- signer, err := session.NewSigner(config.PKCS11.StoreLabel, keyInfo.key)
|
||||
+ signer, err := session.NewSigner(config.PKCS11.StoreLabel, rKeyInfo.key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve signer: %s", err)
|
||||
}
|
||||
- template, err := makeTemplate(newRandReader(session), &config.CertProfile, keyInfo.der, nil, rootCert)
|
||||
+ template, err := makeTemplate(newRandReader(session), &config.CertProfile, rKeyInfo.der, nil, rootCert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create certificate profile: %s", err)
|
||||
}
|
||||
- lintCert, err := issueLintCertAndPerformLinting(template, template, keyInfo.key, signer, config.SkipLints)
|
||||
+ lintCert, err := issueLintCertAndPerformLinting(template, template, rKeyInfo.key, signer, config.SkipLints)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
- finalCert, err := signAndWriteCert(template, template, lintCert, keyInfo.key, signer, config.Outputs.CertificatePath)
|
||||
+ finalCert, err := signAndWriteCert(template, template, lintCert, rKeyInfo.key, signer, config.Outputs.CertificatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
39
patches/ceremony_rsa.patch
Normal file
39
patches/ceremony_rsa.patch
Normal file
@@ -0,0 +1,39 @@
|
||||
diff --git a/cmd/ceremony/rsa.go b/cmd/ceremony/rsa.go
|
||||
index 69e326b39..1bd60f750 100644
|
||||
--- a/cmd/ceremony/rsa.go
|
||||
+++ b/cmd/ceremony/rsa.go
|
||||
@@ -18,7 +18,7 @@ const (
|
||||
// device and specifies which mechanism should be used. modulusLen specifies the
|
||||
// length of the modulus to be generated on the device in bits and exponent
|
||||
// specifies the public exponent that should be used.
|
||||
-func rsaArgs(label string, modulusLen, exponent uint, keyID []byte) generateArgs {
|
||||
+func rsaArgs(label string, modulusLen, exponent uint, keyID []byte, extractable bool) generateArgs {
|
||||
// Encode as unpadded big endian encoded byte slice
|
||||
expSlice := big.NewInt(int64(exponent)).Bytes()
|
||||
log.Printf("\tEncoded public exponent (%d) as: %0X\n", exponent, expSlice)
|
||||
@@ -44,7 +44,7 @@ func rsaArgs(label string, modulusLen, exponent uint, keyID []byte) generateArgs
|
||||
// Prevent attributes being retrieved
|
||||
pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true),
|
||||
// Prevent the key being extracted from the device
|
||||
- pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false),
|
||||
+ pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, extractable),
|
||||
// Allow the key to create signatures
|
||||
pkcs11.NewAttribute(pkcs11.CKA_SIGN, true),
|
||||
},
|
||||
@@ -75,14 +75,14 @@ func rsaPub(session *pkcs11helpers.Session, object pkcs11.ObjectHandle, modulusL
|
||||
// specified by modulusLen and with the exponent 65537.
|
||||
// It returns the public part of the generated key pair as a rsa.PublicKey
|
||||
// and the random key ID that the HSM uses to identify the key pair.
|
||||
-func rsaGenerate(session *pkcs11helpers.Session, label string, modulusLen uint) (*rsa.PublicKey, []byte, error) {
|
||||
+func rsaGenerate(session *pkcs11helpers.Session, label string, modulusLen uint, extractable bool) (*rsa.PublicKey, []byte, error) {
|
||||
keyID := make([]byte, 4)
|
||||
_, err := newRandReader(session).Read(keyID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Printf("Generating RSA key with %d bit modulus and public exponent %d and ID %x\n", modulusLen, rsaExp, keyID)
|
||||
- args := rsaArgs(label, modulusLen, rsaExp, keyID)
|
||||
+ args := rsaArgs(label, modulusLen, rsaExp, keyID, extractable)
|
||||
pub, _, err := session.GenerateKeyPair(args.mechanism, args.publicAttrs, args.privateAttrs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/docker-compose.yml b/docker-compose.yml
|
||||
index 6caf66306..c7939ece4 100644
|
||||
index d0a439f0f..71203004d 100644
|
||||
--- a/docker-compose.yml
|
||||
+++ b/docker-compose.yml
|
||||
@@ -1,3 +1,4 @@
|
||||
@@ -22,7 +22,7 @@ index 6caf66306..c7939ece4 100644
|
||||
+ - /home/labca/nginx_data/static:/opt/wwwstatic
|
||||
- ./.gocache:/root/.cache/go-build:cached
|
||||
- - ./test/certs/.softhsm-tokens/:/var/lib/softhsm/tokens/:cached
|
||||
+ - /home/labca/boulder_labca/certs/.softhsm-tokens/:/var/lib/softhsm/tokens/:cached
|
||||
+ - /home/labca/boulder_labca/certs/.softhsm-tokens/:/var/lib/softhsm/tokens/
|
||||
networks:
|
||||
bouldernet:
|
||||
ipv4_address: 10.77.77.77
|
||||
@@ -66,7 +66,7 @@ index 6caf66306..c7939ece4 100644
|
||||
networks:
|
||||
bouldernet:
|
||||
aliases:
|
||||
@@ -84,46 +79,96 @@ services:
|
||||
@@ -84,46 +79,98 @@ services:
|
||||
# small.
|
||||
command: mysqld --bind-address=0.0.0.0 --slow-query-log --log-output=TABLE --log-queries-not-using-indexes=ON
|
||||
logging:
|
||||
@@ -122,6 +122,7 @@ index 6caf66306..c7939ece4 100644
|
||||
+ - /home/labca/backup:/opt/backup
|
||||
+ - .:/opt/boulder
|
||||
+ - /home/labca/boulder_labca:/opt/boulder/labca
|
||||
+ - /home/labca/boulder_labca/certs/.softhsm-tokens/:/var/lib/softhsm/tokens/
|
||||
+ expose:
|
||||
+ - 3000
|
||||
+ depends_on:
|
||||
@@ -162,6 +163,7 @@ index 6caf66306..c7939ece4 100644
|
||||
+ - /home/labca/control_logs:/opt/logs
|
||||
+ - .:/opt/boulder
|
||||
+ - /home/labca/boulder_labca:/opt/boulder/labca
|
||||
+ - /home/labca/boulder_labca/certs/.softhsm-tokens/:/var/lib/softhsm/tokens/
|
||||
+ - /home/labca/nginx_data/conf.d:/etc/nginx/conf.d
|
||||
+ - /home/labca/nginx_data/ssl:/etc/nginx/ssl
|
||||
+ - /home/labca/nginx_data/static:/var/www/html
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
diff --git a/test/entrypoint.sh b/test/entrypoint.sh
|
||||
index a47fd2c9a..90148c0d5 100755
|
||||
index a47fd2c9a..626d57155 100755
|
||||
--- a/test/entrypoint.sh
|
||||
+++ b/test/entrypoint.sh
|
||||
@@ -13,15 +13,27 @@ service rsyslog start
|
||||
@@ -13,15 +13,15 @@ service rsyslog start
|
||||
# make sure we can reach the mysqldb.
|
||||
./test/wait-for-it.sh boulder-mysql 3306
|
||||
|
||||
@@ -17,18 +17,6 @@ index a47fd2c9a..90148c0d5 100755
|
||||
|
||||
+# Generate the internal keys and certs
|
||||
+./test/certs/generate.sh
|
||||
+
|
||||
+fl=$(pwd)/labca/setup_complete
|
||||
+while [ ! -f $fl ]; do
|
||||
+ echo "Waiting for $fl to appear..."
|
||||
+ sleep 30
|
||||
+done
|
||||
+
|
||||
+#softhsm2-util --show-slots
|
||||
+softhsm2-util --init-token --slot 0 --label "intermediate signing key (rsa)" --pin 1234 --so-pin 5678 | /bin/true
|
||||
+[ -e labca/test-ca.p8 ] && softhsm2-util --import labca/test-ca.p8 --id 333333 --force --token "intermediate signing key (rsa)" --pin 1234 --so-pin 5678 --label 'intermediate_key'
|
||||
+softhsm2-util --init-token --slot 1 --label "root signing key (rsa)" --pin 1234 --so-pin 5678 | /bin/true
|
||||
+[ -e labca/test-root.p8 ] && softhsm2-util --import labca/test-root.p8 --id 777777 --force --token "root signing key (rsa)" --pin 1234 --so-pin 5678 --label 'root_key'
|
||||
+
|
||||
if [[ $# -eq 0 ]]; then
|
||||
exec python3 ./start.py
|
||||
|
||||
16
patches/start.patch
Normal file
16
patches/start.patch
Normal file
@@ -0,0 +1,16 @@
|
||||
diff --git a/start.py b/start.py
|
||||
index f224b9e6c..017fe5cd5 100755
|
||||
--- a/start.py
|
||||
+++ b/start.py
|
||||
@@ -20,6 +20,11 @@ import startservers
|
||||
if not startservers.install(race_detection=False):
|
||||
raise(Exception("failed to build"))
|
||||
|
||||
+fl = "./labca/setup_complete"
|
||||
+while not os.path.exists(fl):
|
||||
+ print(f"Waiting for '{fl}' to appear...")
|
||||
+ time.sleep(30)
|
||||
+
|
||||
if not startservers.start(fakeclock=None):
|
||||
sys.exit(1)
|
||||
try:
|
||||
6
restore
6
restore
@@ -25,4 +25,10 @@ vrs=$(grep version /opt/labca/data/config.json | sed -e 's/.*:[ ]*//' | sed -e '
|
||||
rm -rf /opt/labca/data/* && mv $TMPDIR/data/* /opt/labca/data/
|
||||
sed -i -e "s/\"version\": \".*\"/\"version\": \"$vrs\"/" /opt/labca/data/config.json
|
||||
|
||||
[ -d $TMPDIR/webpki ] || (echo "Public CA files backup not found"; exit 1)
|
||||
rm -rf /opt/boulder/labca/certs/webpki/* && mv $TMPDIR/webpki/* /opt/boulder/labca/certs/webpki/
|
||||
|
||||
[ -d $TMPDIR/tokens ] || (echo "SoftHSMv2 tokens folder backup not found"; exit 1)
|
||||
rm -rf /var/lib/softhsm/tokens/* && mv $TMPDIR/tokens/* /var/lib/softhsm/tokens/
|
||||
|
||||
rm -rf $TMPDIR
|
||||
|
||||
Reference in New Issue
Block a user