Sign with GlobalSign DSS
GlobalSign’s Digital Signing Service issues a short-lived signing certificate, called an identity, and signs the digest on request, so a signature handler built on it does three things the KMS handlers do not: it asks for an identity, embeds the OCSP response that came with that identity, and adds a timestamp token. The result is close to a PAdES B-T signature with LTV data in the same revision.
The client comes from github.com/unidoc/globalsign-dss and needs the API key
and secret plus the mTLS certificate and key pair for the API endpoint
https://emea.api.dss.globalsign.com:8443:
c, err := globalsign.NewClient(apiKey, apiSecret, certFilepath, keyFilepath)GlobalSign issues the API credentials and the mTLS files with a DSS account. The external signing README lists what the example needs.
Requesting an identity
InitSignature requests an identity, which returns the signing certificate, the
CA chain and an OCSP response for the signing certificate:
identity, err := globalsign.DSSService.DSSGetIdentity(gsClient.DSSService, es.ctx, signerName,
&globalsign.IdentityRequest{SubjectDn: globalsign.SubjectDn{}})signerName is the key the client caches the identity under, for 10 minutes. The
signing request in Sign passes the same signerName, so it signs with the
identity fetched here instead of requesting a new certificate. The certificate
and CA are PEM, decoded into a chain, put on the handler for later use and also
written to the signature’s Cert array.
InitSignature finishes with es.Sign(sig, nil). A nil digest takes the
early return in Sign and reserves sigLen zero bytes for the contents without
calling the service, so exactly one signing request is made per signature, during
appender.Write.
The OCSP response and the timestamp
Because the certificate is minted for this signature, its OCSP response is already at hand and gets embedded as an Adobe revocation attribute rather than fetched again later:
_, err := ocsp.ParseResponseForCert(es.ocsp, certs[0], certs[1])
if err != nil {
return err
}
siConfig.ExtraSignedAttributes = []pkcs7.Attribute{{
Type: pkcs7.OIDAttributeAdobeRevocation,
Value: RevocationInfoArchival{
Ocsp: []asn1.RawValue{{FullBytes: es.ocsp}},
},
}}This only happens when the chain has more than one certificate, since verifying
the response needs the issuer. signedData.RequestSignerTimestampToken(0, ...)
then adds a timestamp token to the first signer, obtained from the DSS timestamp
endpoint rather than a public TSA.
LTV in the same revision
generateSignedFile signs and LTV enables in one write, which means
EnableChain: the VRI entry cannot be built in the revision that creates the
signature, because its key is derived from the signature contents. The clients
are tuned before enabling:
ltv, err := model.NewLTV(appender)
if err != nil {
return nil, err
}
ltv.CertClient.HTTPClient.Timeout = 30 * time.Second
ltv.OCSPClient.HTTPClient.Timeout = 30 * time.Second
ltv.CRLClient = nil
err = ltv.EnableChain(certChain)The default timeout on all three clients is 5 seconds per request, which is
tight for CRL downloads. Setting CRLClient to nil skips CRLs entirely, and
setting OCSPClient to nil would skip OCSP the same way. See
LTV in one revision for what EnableChain writes.
Limitations
Signer.Public() returns nil. That works because pkcs7 only needs the signer
to sign here, but it means the same crypto.Signer cannot be used with
x509.CreateCertificate, unlike the KMS examples.
The signature is padded to sigLen, 16384 in the example. The PKCS7 package
carries the signing certificate, the GlobalSign CA chain, the OCSP response and a
timestamp token with its TSA certificates, which together can exceed 8192 bytes.
Sign returns an error if the package does not fit, so raise sigLen when you
see that error.
signerName is only the cache key for the identity, and the name and location in
SignOption are display strings from the GlobalSign test account. Replace the name
and location with your own.
Run the example
NewGlobalSignPdfSignature builds the handler, createSignatureField turns a
SignOption into the field and appearance, and generateSignedFile does the
signing, LTV and write.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures/external
go run pdf_sign_external_globalsign.go <INPUT_PDF_PATH> <OUTPUT_PDF_PATH> <API_KEY> <API_SECRET> <CERT_FILE_PATH> <KEY_FILE_PATH>If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.