Skip to content
Sign and LTV Enable in a Second Revision

Sign and LTV Enable in a Second Revision

Adding the validation data in its own revision is the fuller form of LTV: the signature exists by then, so its contents can be hashed into a VRI key and the data can be attached to that one signature rather than to the document at large. The cost is a second revision and a second reader.

Two passes over the bytes

An appender can be written once. A second revision therefore means reading back what the first one produced:

signedBytes, err := signFile(inputPath, priv.(*rsa.PrivateKey), cert)
if err != nil {
    return err
}

err = ltvEnable(bytes.NewReader(signedBytes), outputPath, certChain)

signFile signs and writes to a bytes.Buffer; ltvEnable builds a fresh reader and appender over those bytes and adds the DSS:

func ltvEnable(r *bytes.Reader, outputPath string, certChain []*x509.Certificate) error {
    reader, err := model.NewPdfReader(r)
    if err != nil {
        return err
    }

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

    ltv, err := model.NewLTV(appender)
    if err != nil {
        return err
    }

    if err := ltv.EnableAll(certChain); err != nil {
        return err
    }

    return appender.WriteToFile(outputPath)
}

The parameter is a *bytes.Reader, not an io.ReadSeeker. In UniPDF v5 the reader takes an io.ReaderAt, and model.NewPdfAppender returns an error unless the reader’s source satisfies both io.ReadSeeker and io.ReaderAt. Concrete types like *os.File and *bytes.Reader satisfy both; a value typed as io.ReadSeeker does not necessarily, and a reader built with NewPdfReaderFromParser cannot be appended at all. See the v5 migration guide for the details.

EnableAll pulls the chain out of each signature dictionary it finds and adds whatever you pass in on top, which is where the optional PEM file of issuer certificates goes.

Limitations

The validation data lands in a revision the signature does not cover. That is inherent to the approach, and the way to protect it is a document timestamp over the new revision, which is what the timestamp revision guide adds. Signing and LTV enabling in one revision is the other way out, at the cost of losing the VRI entry.

EnableAll returns an “invalid signature field” error if the document contains a signature field with empty contents, so a form with a prepared but unsigned signature field will not LTV enable.

The rest of the behavior, including the silently skipped OCSP and CRL requests and the 5 second default timeout, is covered in the LTV enable guide.

Run the example

signFile produces the signed revision in memory and ltvEnable writes the final file. Neither touches disk in between, so the intermediate signed document is never saved.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures/ltv
go run pdf_sign_ltv_extra_revision.go cert.p12 password input.pdf output.pdf [extra_certs.pem]

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