Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 210 additions & 0 deletions app/security/document-signing/setup-document-signing.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
source "$MANJIKAZE_DIR/lib/bitwarden.sh"

setup_document_signing_cert() {
local cert_dir="$HOME/.pdf-signing"
local p12_file="$cert_dir/signing-cert.p12"

echo ""
gum style \
--border rounded --border-foreground 212 \
--padding "1 2" --margin "0 1" \
"📄 10KB Document Signing Certificate" \
"" \
"This creates a personal X.509 certificate for signing PDFs" \
"and other documents, issued by the 10KB Document Signing CA." \
"" \
"You will need a Bitwarden Send link from the CA administrator" \
"containing the CA private key (.key file)." \
"" \
"The CA key will be removed from this machine after signing."
echo ""

if [[ -f "$p12_file" ]]; then
if ! gum confirm "A signing certificate already exists at $p12_file. Replace it?" --default=false; then
status "Keeping existing certificate."
return 0
fi
fi

# ── Collect user input ─────────────────────────────────────────────
local name
name=$(gum input --header "Your full name (as it appears on documents):" --value "$(git config user.name)")
if [[ -z "$name" ]]; then
status "Error: Name is required."
return 1
fi

local email
email=$(gum input --header "Your email address:" --value "$(git config user.email)")
if [[ -z "$email" ]]; then
status "Error: Email is required."
return 1
fi

# ── Get CA key via Bitwarden Send ──────────────────────────────────
if ! gum confirm "Do you have a Bitwarden Send link for the Document Signing CA?" --default=false; then
status "Ask your CA administrator for a Bitwarden Send link."
return 1
fi

local send_url
send_url=$(gum input --header "Paste the Bitwarden Send link:" --placeholder "https://vault.bitwarden.com/...")
if [[ -z "$send_url" ]]; then
status "Error: Send URL is required."
return 1
fi

local send_password=""
if gum confirm "Does the Send link require a password?" --default=false; then
send_password=$(gum input --password --header "Enter the Send password:")
fi

# Download CA private key (.key file)
local ca_key_file
ca_key_file=$(mktemp)
local ca_cert_file="$MANJIKAZE_DIR/assets/certs/10kb-document-signing-ca.crt"

status "Downloading CA key from Bitwarden Send..."
local receive_cmd="bw send receive \"$send_url\" --output \"$ca_key_file\""
if [[ -n "$send_password" ]]; then
receive_cmd="$receive_cmd --password \"$send_password\""
fi

if ! eval "$receive_cmd" 2>/dev/null; then
status "Error: Could not download CA key from Bitwarden Send."
status "The link may have expired or already been used."
rm -f "$ca_key_file"
return 1
fi

if [[ ! -f "$ca_cert_file" ]]; then
status "Error: CA certificate not found at $ca_cert_file."
rm -f "$ca_key_file"
return 1
fi

# ── Generate developer certificate ─────────────────────────────────
status "Generating your document signing certificate..."
mkdir -p "$cert_dir"

# Generate private key
openssl genrsa -out "$cert_dir/signing-key.pem" 4096 2>/dev/null

# Create CSR
openssl req -new \
-key "$cert_dir/signing-key.pem" \
-out "$cert_dir/signing.csr" \
-subj "/C=NL/O=10KB B.V./CN=$name/emailAddress=$email" \
2>/dev/null

# Sign with CA
openssl x509 -req \
-in "$cert_dir/signing.csr" \
-CA "$ca_cert_file" \
-CAkey "$ca_key_file" \
-CAcreateserial \
-out "$cert_dir/signing-cert.pem" \
-days 1825 \
-sha256 \
2>/dev/null

if [[ $? -ne 0 ]]; then
status "Error: Failed to sign certificate with CA."
shred -u "$ca_key_file" 2>/dev/null || rm -f "$ca_key_file"
return 1
fi

# Create PKCS#12 bundle (used by pyHanko for PDF signing)
local p12_pass
p12_pass=$(gum input --password --header "Set a password for the .p12 certificate (leave empty for no password):")

if [[ -n "$p12_pass" ]]; then
openssl pkcs12 -export \
-out "$p12_file" \
-inkey "$cert_dir/signing-key.pem" \
-in "$cert_dir/signing-cert.pem" \
-certfile "$ca_cert_file" \
-name "$name" \
-passout "pass:$p12_pass" \
2>/dev/null
else
openssl pkcs12 -export \
-out "$p12_file" \
-inkey "$cert_dir/signing-key.pem" \
-in "$cert_dir/signing-cert.pem" \
-certfile "$ca_cert_file" \
-name "$name" \
-passout "pass:" \
2>/dev/null
fi

# ── Cleanup ────────────────────────────────────────────────────────
# Remove CA private key
shred -u "$ca_key_file" 2>/dev/null || rm -f "$ca_key_file"

# Remove CSR and serial file (no longer needed)
rm -f "$cert_dir/signing.csr"
rm -f "$MANJIKAZE_DIR/assets/certs/.srl" 2>/dev/null

# Keep signing-key.pem and signing-cert.pem for reference
chmod 600 "$cert_dir/signing-key.pem"
chmod 644 "$cert_dir/signing-cert.pem"
chmod 600 "$p12_file"

# ── Backup to Bitwarden ────────────────────────────────────────────
if ! unlock_bitwarden; then
status "Warning: Could not unlock Bitwarden. Skipping backup."
echo "Make sure to backup your signing certificate manually from: $cert_dir"
else
local org_id
org_id=$(get_org_id "10KB")
if [[ -z "$org_id" ]]; then
status "Warning: 10KB organization not found in Bitwarden. Skipping backup."
else
local bw_user_name
bw_user_name=$(gum input --header "Your name as it appears in Bitwarden (Medewerkers/...):" --value "$name")
local collection_id
collection_id=$(get_user_collection_id "$org_id" "$bw_user_name")

if [[ -z "$collection_id" ]]; then
status "Warning: Collection 'Medewerkers/$bw_user_name' not found. Skipping backup."
else
local note_name="Document Signing Certificate - $name"
local note_content="Name: $name
Email: $email
Created: $(date +%F)
Issuer: 10KB Document Signing CA
Valid: 5 years
P12 password: ${p12_pass:-<none>}"

local item_id
item_id=$(upsert_org_note "$org_id" "$collection_id" "$note_name" "$note_content")
if [[ -n "$item_id" ]]; then
add_attachment "$item_id" "$p12_file"
add_attachment "$item_id" "$cert_dir/signing-key.pem"
add_attachment "$item_id" "$cert_dir/signing-cert.pem"
status "Document signing certificate backed up to Bitwarden (Medewerkers/$bw_user_name)"
fi
fi
fi
fi

# ── Summary ────────────────────────────────────────────────────────
echo ""
gum style \
--border double --border-foreground 76 \
--padding "1 2" --margin "0 1" \
"✅ Document signing certificate created" \
"" \
"Certificate: $cert_dir/signing-cert.pem" \
"PKCS#12: $p12_file" \
"Subject: $name <$email>" \
"Issuer: 10KB Document Signing CA" \
"Valid: 5 years"
echo ""
echo "To sign a PDF (in the documenten repo):"
echo " ./sign-pdf.sh document.pdf"
echo ""
}

setup_document_signing_cert
32 changes: 32 additions & 0 deletions assets/certs/10kb-document-signing-ca.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFizCCA3OgAwIBAgIUU5aFgiODrPIirlugQjYUO7EdPAowDQYJKoZIhvcNAQEL
BQAwVTELMAkGA1UEBhMCTkwxEjAQBgNVBAoMCTEwS0IgQi5WLjEVMBMGA1UEAwwM
MTBLQiBSb290IENBMRswGQYJKoZIhvcNAQkBFgxpbmZvQDEwa2IubmwwHhcNMjYw
MjE5MDk1NDI3WhcNMzYwMjE3MDk1NDI3WjBVMQswCQYDVQQGEwJOTDESMBAGA1UE
CgwJMTBLQiBCLlYuMRUwEwYDVQQDDAwxMEtCIFJvb3QgQ0ExGzAZBgkqhkiG9w0B
CQEWDGluZm9AMTBrYi5ubDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
AJ4LC5PEKzY3eNG1nYwuNvJTtMX9BgVaYpv3AnaiVQNru8LJlqNFvOdsgI9NXSey
VMWNua2GRNZ3ivdPbtoPZDfWMSNAfBI18z3Rq5Uv+9JgWWE+sTYGd7rrlO1zpiHA
gAWSvrD/EHCDpxtEZW9XIYQsm42GrzNuyQJfRXSdyqXdrUsLWSysJW9Ph002DTz3
Zqne4PRn6qPGx1KXFFG4Cmd1TGVKhWPWoFJYvXnFY8DAHs06JYkcjm7lkAUSVO21
lWabg0yHxhfV/OC4wvQQwY1nfY72A22OEAueVnU247zLEs72tdkHu7Ulzy9sMNHG
f6T5hKLlGeo94tHe3BEpma5W8SMZwXXaHdxxi5M6WMT7Odb4bQivOwAmquQhOVPK
mAm7UgbOB91Un0pcn0dJBd+x5/voXC4gFIF4+kmJdIOHhQnPvDKpqsa7jIDL74ud
vppwNjX65DBEPKFGaLLgLFyzmW8KwAeS82n6C+zmgvFjFTsZ24aeeEFlD/FNnXoJ
ND/4FKf3Uj09sYdKGm96cZ4IngpBY+IoU6J3KJ/d4D3QI83TNAtBkYSjVceVY7v6
PFlCr9BdEXaHF/k3cnHIxJrtvx5gMKSorb5Khxm9nRDFDTIfxqGzs62oOKeWdh1w
reNQmbbImGWDSjQHq+90J3hryXHQ5zLKR/tpnq/fXXYTAgMBAAGjUzBRMB0GA1Ud
DgQWBBSiZWOicR1ksUBuk5te1lm1qihZOjAfBgNVHSMEGDAWgBSiZWOicR1ksUBu
k5te1lm1qihZOjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAF
mSWXF3T8r/9sa8Hp7vCYKcFJL2lwIEnbPKTCsNBVIhtE0FSivwZSgOz78Uhi+uJj
4fAz8PnYwPHEZXomWxO2oT4bxLTDJUmubGMZjhwohp+j2lR7cWwbnYoyONkVM0VK
tDi13J7Nx04HtgVJ8xsUqCD2454ZH8cCTI70V7q0zu6uLcsf7Izdo+Npznbrjy81
vIeU5j5px/BSNtqQR1kLv2+6g2OkCWiKZQg12BiVyjv5cNI5Hbxaqvr+Q/jCn9pR
rFwi6n8BkGQe3gtJq2Yoqj/B9YBz2OxxVKsyUdCt0OE+VTzU0/y30ScME7IDSxX/
EK30Fm8rMzzhDG0G91bVBFsUj0BMjj6mm+tvEPCiw1mx/JT/Sf7QYSMUwHxPdHkq
dd3Qr5GCyZNP6IlFN8fFgfu7TvOoH1f4SMCU341u9d4BLwJ7p+gBYOk7QKVH6kOo
zJEXvWixbx8wvKCG9G82rIAp1uRVHzvNddqeZQ9zDL+SyLLYwy6+zR51wfKijmh0
XVNxbUfG2u8K5fj622YnxiXJ0IsDiJSsWMi+TyI0nbi86ULzufiLha/jKRyr1yb3
N5IXSaEL6EHp2dOhDS+EjeMW5cgfwovjEtoYeDQBUCRjqXtZ0jKzlV85vABI1Bq7
/9YKsQaGyJWFP46+zM1RdGFyLUvF89cxghzYRnEo2Q==
-----END CERTIFICATE-----
1 change: 1 addition & 0 deletions lib/menus.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ declare -A security_menu=(
["8:Restore GPG keys to new YubiKey"]="load_module security/yubikey/yubikey-gpg-restore.sh"
["9:Configure YubiKey for AWS Vault MFA"]="load_module security/yubikey/yubikey-aws-vault.sh"
["10:Configure weekly update checks"]="load_module security/updates/configure-update-checker.sh"
["11:Create document signing certificate"]="load_module security/document-signing/setup-document-signing.sh"
)

handle_menu() {
Expand Down
25 changes: 25 additions & 0 deletions migrations/1771855244_setup_document_signing_ca.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash
set -e

# Install the 10KB Document Signing CA certificate into the system trust
# store. This allows the system to verify documents and TLS certificates
# signed by the 10KB Document Signing CA.

CA_CERT="$MANJIKAZE_DIR/assets/certs/10kb-document-signing-ca.crt"
TRUST_ANCHOR="/etc/ca-certificates/trust-source/anchors/10kb-document-signing-ca.crt"

if [[ ! -f "$CA_CERT" ]]; then
status "Warning: Document Signing CA certificate not found at $CA_CERT. Skipping."
return 0
fi

if [[ -f "$TRUST_ANCHOR" ]]; then
status "10KB Document Signing CA already installed in system trust store."
return 0
fi

status "Installing 10KB Document Signing CA into system trust store..."
sudo cp "$CA_CERT" "$TRUST_ANCHOR"
sudo update-ca-trust

status "10KB Document Signing CA installed."