Sign with GlobalSign DSS
GlobalSign’s Digital Signing Service issues a short-lived certificate per signature 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:
c, err := globalsign.NewClient(apiKey, apiSecret, certFilepath, keyFilepath)Identity per signature
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,
"GLOBALSIGN TEST ACCOUNT - FOR TESTING PURPOSE ONLY",
&globalsign.IdentityRequest{SubjectDn: globalsign.SubjectDn{}})The identity name is account-specific and has to be the same one used later for
the signing request. 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, 8192 in the example. A DSS signature with a
timestamp token and an embedded OCSP response is considerably larger than a bare
PKCS7 package, and copy truncates rather than failing, so raise sigLen before
assuming a short signature is a service problem.
Every value the example passes as an identity name is a GlobalSign test account string. Replace them with your own before anything will validate.
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
go run pdf_sign_external_globalsign.go input.pdf output.pdf API_KEY API_SECRET cert.pem key.pemIf this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.