Sign with an HSM via PKCS11
A PKCS11 token exposes a crypto.Signer and nothing else: the private key never
leaves the device, and signing only works while a session is open. That fits
sighandler.NewAdobeX509RSASHA1Custom, which takes a certificate and a signing
function instead of a key.
The signing function
signFunc := func(sig *model.PdfSignature, digest model.Hasher) ([]byte, error) {
h, ok := digest.(hash.Hash)
if !ok {
return nil, errors.New("hash type error")
}
return signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA1)
}
handler, err := sighandler.NewAdobeX509RSASHA1Custom(certificate, signFunc)The model.Hasher handed to the function is a hash.Hash for this handler, not
a *bytes.Buffer as it is for the PKCS7 handlers, so the byte range has already
been hashed by the time you see it. The algorithm is SHA-1 by default, which is
why crypto.SHA1 is passed to the signer. Those two have to agree.
To use something stronger, or to avoid the sizing round trip described below, build the handler with options instead:
handler, err := sighandler.NewAdobeX509RSASHA1CustomWithOpts(certificate, signFunc,
&sighandler.AdobeX509RSASHA1Opts{
Algorithm: crypto.SHA256,
EstimateSize: true,
})Changing Algorithm changes what NewDigest returns, so the crypto.SHA1
argument inside signFunc has to change with it.
Session lifetime
crypto11.Configure opens the session and the signer is only usable until it is
closed. The handler’s signing function runs during appender.Write, not during
appender.Sign, so the session has to still be open at write time. The example
defers ctx.Close() in main and does everything inside that scope.
Limitations
By default the handler makes a mock Sign call while initializing the signature,
purely to find out how many bytes to reserve for the contents. That is a second
operation on the token for every signature. EstimateSize: true computes the
size from the modulus of the certificate’s public key instead and skips it.
adbe.x509.rsa_sha1 carries a single certificate in the signature’s Cert
entry, not an array, so this handler cannot publish a chain the way the PKCS7
handlers do. Validation software has to build the path itself, or you have to add
the chain through the DSS.
The example generates a self-signed certificate for the token key each time it runs, which is only useful for testing. Use a certificate issued for the key instead.
The example needs cgo and a PKCS11 library at the path in the PathSoftHSM
constant, which is set for a Linux SoftHSM install and differs elsewhere.
Run the example
The example has two subcommands. add generates a 2048-bit RSA key pair in the
token under a label, sign looks that pair up, builds a certificate for it and
signs. initPKCS11Session is the place to change the library path or how the pin
is supplied. The signatures/README.md in the examples repository has the full
SoftHSM setup, including creating the test token used below.
Create the key pair first:
go run pdf_sign_hsm_pkcs11_cgo.go add test PIN keypair_labelThen sign:
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures
go run pdf_sign_hsm_pkcs11_cgo.go sign test PIN keypair_label input.pdf input_signed.pdfIf this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.