#!/bin/sh # Sign with a key held in an HSM, through PKCS#11. # # hsm-sign detached IN OUT a detached signature of IN (RSA PKCS#1 v1.5 over # SHA-256), written to OUT # hsm-sign apk IN.apk OUT.apk sign an APK (signature schemes v2 and v3), then verify # hsm-sign make-cert SUBJECT one-time setup for apk: a self-signed certificate for # the key, written to the token beside it, and printed # as PEM # hsm-sign list what Java sees on the token, for diagnosing an alias # # Environment: # PKCS11_PIN the user PIN (required) # PKCS11_MODULE the PKCS#11 library; default: opensc's # PKCS11_SLOT_INDEX which slot, by position in the slot list; default 0 # KEY_ID the key's CKA_ID in hex; default 03 # KEY_LABEL the label make-cert gives the certificate; default apk-cert # KEY_ALIAS which key apksigner uses, when the token holds more than one # # Why apk needs a certificate on the token: an APK signature embeds the signer's # certificate, and Java's PKCS#11 key store only offers a private key that has a # certificate with the same CKA_ID. A key used only for detached signatures has none # until make-cert writes one. Android pins an app to its signing certificate for good, # so make-cert runs once and its certificate is kept. set -eu die() { echo "hsm-sign: $*" >&2 exit 1 } [ -n "${PKCS11_PIN:-}" ] || die "PKCS11_PIN is not set" module="${PKCS11_MODULE:-$(find /usr/lib -name opensc-pkcs11.so | head -n 1)}" [ -f "$module" ] || die "no PKCS#11 module at '$module'" slot_index="${PKCS11_SLOT_INDEX:-0}" key_id="${KEY_ID:-03}" work="$(mktemp -d)" trap 'rm -rf "$work"' EXIT chmod 700 "$work" # SunPKCS11's configuration: which library, which slot. cat > "$work/pkcs11.cfg" < "$work/java.security" echo "-Djava.security.properties=$work/java.security" } case "${1:-}" in detached) [ $# -eq 3 ] || die "usage: hsm-sign detached IN OUT" # The signature action-hsm-sign has always made, so signatures verify (and match # sigstore entries) the same across versions: PKCS#1 v1.5 is deterministic. pkcs11-tool --module "$module" --slot-index "$slot_index" --login --pin env:PKCS11_PIN \ --sign --id "$key_id" --mechanism SHA256-RSA-PKCS --input-file "$2" --output-file "$3" ;; apk) [ $# -eq 3 ] || die "usage: hsm-sign apk IN.apk OUT.apk" set -- "$2" "$3" alias_args="" [ -n "${KEY_ALIAS:-}" ] && alias_args="--ks-key-alias ${KEY_ALIAS}" # shellcheck disable=SC2086 # alias_args is two words or none, by construction JAVA_TOOL_OPTIONS="$(java_security)" apksigner sign \ --ks NONE --ks-type PKCS11 --ks-pass env:PKCS11_PIN \ $alias_args \ --in "$1" --out "$2" # Proof, in the log, of what was signed and by whom. apksigner verify --verbose --print-certs "$2" ;; make-cert) [ $# -eq 2 ] || die "usage: hsm-sign make-cert '/CN=...'" subject="$2" # OpenSSL's PKCS#11 engine, pointed at the same module, signs the certificate with # the HSM's key. The PIN is in a file only this process can read, not an argument. cat > "$work/openssl.cnf" <