Skip to content
Signing a PAdES B-B Signature

Signing a PAdES B-B Signature

PAdES describes four baseline levels, each one adding material that a validator will need later on. B-B is the floor: a CAdES signature embedded in the PDF and nothing else. It shows who signed and that the bytes have not changed since, and it stays verifiable only as long as somebody can still reach the certificate chain and revocation data on their own.

Choosing a baseline level

LevelHandlerWhat it adds over the level below
B-Bsighandler.NewEtsiPAdESLevelBNothing. The signature on its own.
B-Tsighandler.NewEtsiPAdESLevelTA timestamp token from a TSA over the signature, so the signing time no longer rests on the signer’s clock.
B-LTsighandler.NewEtsiPAdESLevelLTThe certificate chain, OCSP responses and CRLs, collected at signing time and written into the document’s DSS.
B-LTANewEtsiPAdESLevelLT, then a NewDocTimeStamp revisionA document timestamp covering the whole file, which can be renewed before the algorithms protecting it weaken.

The SDK has no level constant for B-LTA. sighandler.EtsiPAdESLevel defines LevelB, LevelT and LevelLT only, and a B-LTA file is a B-LT file with a document timestamp appended in a further revision. See PAdES B-LTA for that.

B-B is enough when the document is checked shortly after it is signed and the verifier is expected to have their own trust store and network access. Go to B-T as soon as the signing time matters to anyone, since without a timestamp there is nothing but the signer’s own clock to date the signature. B-LT and B-LTA are about archival: they make the document self-contained, so it still validates after the issuing CA retires its OCSP responder.

All four levels write the same Adobe.PPKLite filter and ETSI.CAdES.detached subfilter, and all of them digest with SHA-256. The level only changes what gets attached alongside.

Signing at B-B

appender, err := model.NewPdfAppender(reader)
if err != nil {
    return err
}

handler, err := sighandler.NewEtsiPAdESLevelB(priv, cert, cacert)
if err != nil {
    return err
}

signature := model.NewPdfSignature(handler)
signature.SetName("John Doe")
signature.SetReason("Approval")
signature.SetDate(time.Now(), "")

if err := signature.Initialize(); err != nil {
    return err
}

Initialize fills in the signature dictionary and reserves space in its Contents entry by running the handler through a mock signing pass. It also takes a copy of the handler, so configure the handler fully before you call it; changing the handler afterwards has no effect on the signature.

The signing key must be an *rsa.PrivateKey here. For an ECDSA key use NewEtsiPAdESLevelBEcdsa, which needs a PDF 2.0 or later document.

Once the signature exists, wrap it in a field and sign a page:

field, err := annotator.NewSignatureField(signature, lines, opts)
if err != nil {
    return err
}
field.T = core.MakeString("Self signed PDF")

if err := appender.Sign(1, field); err != nil {
    return err
}

The page number is 1-based and Sign returns page N not found if it falls outside the document. Because the work goes through a PdfAppender, the output is an incremental update: the original bytes are preserved, so signatures already in the file stay valid.

Limitations

The CA certificate argument may be nil. The signature then carries no issuer certificate beyond the signing one, and a verifier has to build the chain itself.

Initialize returns certificate must not be nil or private key must not be nil when either is missing, which is the usual reason a working validation handler cannot be reused for signing.

Nothing in the B-B path touches the network, and nothing is written to the DSS. If you need revocation data inside the file, that is B-LT.

NewEtsiPAdES(level) is the alternative to the fixed-argument constructors. It returns a handler configured through setters (SetPrivateKey, SetCertificate, SetCA, SetTimestampServerURL, SetTimestampClient, SetCertClient, SetOCSPClient, SetCRLClient, SetAppender, SetDSS), which is what you want when the HTTP clients need custom transports or proxies.

Run the example

The example loads a key pair from a PFX file and a CA certificate from a PEM file, then signs page 1 and writes the result. Everything happens in main.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures
go run pdf_sign_pades_b_b.go <FILE.PFX> <PASSWORD> <FILE.PEM> <INPUT_PDF_PATH> <OUTPUT_PDF_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.

View the full source
Last updated on