Skip to content

Signing

A SignedData message carries content, one or more signatures over it and usually the certificates a receiver needs to check them. This page builds such messages and the certificate and CRL bundles that use the same structure without signers.

Sign content

sd, err := pkcs7.NewSignedData([]byte("hello, CMS"))
if err != nil {
    log.Fatal(err)
}
if err := sd.AddSigner(cert, key, pkcs7.SignerInfoConfig{}); err != nil {
    log.Fatal(err)
}
signed, err := sd.Finish()
if err != nil {
    log.Fatal(err)
}

NewSignedData takes the content. AddSigner adds one signer with its certificate, its private key and a SignerInfoConfig. Finish returns the encoded ContentInfo. Every message this library signs has the encapsulated content type id-data.

Signer keys

The key is a crypto.Signer whose public key is an *rsa.PublicKey or an *ecdsa.PublicKey. Any other key type fails with pkcs7: unsupported signer key type. The library hashes the data and calls Sign with the digest and the hash as options:

  • For RSA it expects a PKCS #1 v1.5 signature, which *rsa.PrivateKey returns for these options. RSASSA-PSS signing is not supported.
  • For ECDSA it expects the ASN.1 encoded signature that *ecdsa.PrivateKey returns.

A key held in an HSM or a remote signing service works when its crypto.Signer follows the same contract. The library does not check that the key matches the certificate, so a mismatch shows up only when the message is verified.

Digest algorithm

Signers use SHA-256 unless SetDigestAlgorithm names another digest. The setting applies to signers added after the call, so each signer of one message may use a different digest. For example, sd.SetDigestAlgorithm(pkcs7.OIDDigestAlgorithmSHA384) selects SHA-384.

RSA signers accept MD5, SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512. ECDSA signers accept SHA-1, SHA-256, SHA-384 and SHA-512. Another combination fails when the signer is added. MD5 and SHA-1 remain available for old peers, but Verify refuses MD5 unless the receiver allows it.

Signature algorithm identifier

The signer info names the signature algorithm. By default it is derived from the key and the digest, for example sha256WithRSAEncryption or ecdsa-with-SHA256. Some peers expect the plain rsaEncryption identifier for RSA signatures, which is also what OpenSSL writes. sd.SetEncryptionAlgorithm(pkcs7.OIDEncryptionAlgorithmRSA) selects it for signers added afterwards. Any other value must equal the derived identifier. Otherwise AddSigner fails.

Signed attributes

AddSigner signs a set of attributes rather than the content itself. The set always holds:

  • content-type, with the value id-data
  • message-digest, the digest of the content
  • signing-time, the current time or SignerInfoConfig.SigningTime, stored in UTC

SignerInfoConfig.ExtraSignedAttributes adds more. ExtraUnsignedAttributes adds attributes outside the signature. Each Attribute value must be encodable by encoding/asn1. The set is sorted into DER order before it is signed. The receiver reads an attribute back with UnmarshalSignedAttribute after verifying the message:

// An attribute type from a private arc. Use the OID your protocol defines.
oidTransactionID := asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 99999, 1}

sd, err := pkcs7.NewSignedData([]byte("content"))
if err != nil {
    log.Fatal(err)
}
config := pkcs7.SignerInfoConfig{
    ExtraSignedAttributes: []pkcs7.Attribute{
        {Type: oidTransactionID, Value: "transaction-42"},
    },
}
if err := sd.AddSigner(cert, key, config); err != nil {
    log.Fatal(err)
}
signed, err := sd.Finish()
if err != nil {
    log.Fatal(err)
}

p7, err := pkcs7.Parse(signed)
if err != nil {
    log.Fatal(err)
}
if err := p7.Verify(pkcs7.VerifyOptions{}); err != nil {
    log.Fatal(err)
}
var transactionID string
if err := p7.UnmarshalSignedAttribute(oidTransactionID, &transactionID); err != nil {
    log.Fatal(err)
}
fmt.Println(transactionID)

Do not put content-type, message-digest or signing-time into ExtraSignedAttributes. AddSigner accepts the duplicate, but Verify then fails because each of them must appear only once.

GetSignedData returns the structure being built. The SetUnauthenticatedAttributes method of an entry in its SignerInfos replaces the unsigned attributes of a signer that is already added, for example sd.GetSignedData().SignerInfos[0].SetUnauthenticatedAttributes(attrs). The signature does not cover them, so it stays valid.

Certificate chains

AddSigner puts the signer certificate into the message. AddSignerChain also adds the issuing CA certificates, so a receiver that trusts only the root can build the chain:

sd, err := pkcs7.NewSignedData([]byte("signed through an intermediate"))
if err != nil {
    log.Fatal(err)
}
parents := []*x509.Certificate{intermediateCert}
if err := sd.AddSignerChain(cert, key, parents, pkcs7.SignerInfoConfig{}); err != nil {
    log.Fatal(err)
}
signed, err := sd.Finish()
if err != nil {
    log.Fatal(err)
}

p7, err := pkcs7.Parse(signed)
if err != nil {
    log.Fatal(err)
}
roots := x509.NewCertPool()
roots.AddCert(rootCert)
if err := p7.Verify(pkcs7.VerifyOptions{Roots: roots}); err != nil {
    log.Fatal(err)
}
fmt.Println(len(p7.Certificates), "certificates in the message")

List the parents in order, starting with the issuer of the signer certificate. AddSignerChain checks that each certificate is signed by the next one and fails otherwise. It does not require the last parent to be a root. AddCertificate adds any other certificate to the message.

Messages without certificates

RFC 5652 allows a message without certificates when the receiver already holds the signer certificate. SCEP clients, for example, have the CA certificate from GetCACert. RemoveCertificates leaves out every certificate added so far:

sd, err := pkcs7.NewSignedData([]byte("no certificates attached"))
if err != nil {
    log.Fatal(err)
}
if err := sd.AddSigner(cert, key, pkcs7.SignerInfoConfig{}); err != nil {
    log.Fatal(err)
}
sd.RemoveCertificates()
signed, err := sd.Finish()
if err != nil {
    log.Fatal(err)
}

p7, err := pkcs7.Parse(signed)
if err != nil {
    log.Fatal(err)
}
opts := pkcs7.VerifyOptions{SignerCertificates: []*x509.Certificate{cert}}
if err := p7.Verify(opts); err != nil {
    log.Fatal(err)
}
fmt.Println("verified with a known signer certificate")

Call it after the last AddSigner, because signers added later put their certificates back. The receiver passes the expected signer certificate in VerifyOptions.SignerCertificates.

Detached signatures

A detached signature leaves the content out of the message. The receiver gets the content another way and sets p7.Content before calling Verify:

content := []byte("content sent next to the signature")
sd, err := pkcs7.NewSignedData(content)
if err != nil {
    log.Fatal(err)
}
if err := sd.AddSigner(cert, key, pkcs7.SignerInfoConfig{}); err != nil {
    log.Fatal(err)
}
sd.Detach()
signature, err := sd.Finish()
if err != nil {
    log.Fatal(err)
}

p7, err := pkcs7.Parse(signature)
if err != nil {
    log.Fatal(err)
}
fmt.Println("content in the message:", len(p7.Content))
p7.Content = content
if err := p7.Verify(pkcs7.VerifyOptions{}); err != nil {
    log.Fatal(err)
}
fmt.Println("detached signature verified")

Call Detach after the signers are added and right before Finish.

Several signers

Call AddSigner once per signer. Each signer has its own certificate, digest and attributes. Verify requires every signature to be valid.

Signatures without signed attributes

SignWithoutAttr signs the digest of the content directly, without signed attributes. Old Android APK signatures use this form. It ignores its SignerInfoConfig.

RemoveAuthenticatedAttributes deletes the signed attributes from signers that already exist. Their signatures cover those attributes, so the resulting message fails verification in this library and in OpenSSL. Use SignWithoutAttr to build a message without signed attributes. RemoveUnauthenticatedAttributes deletes the unsigned attributes, which the signatures do not cover.

Certificate and CRL bundles

A SignedData message without signers is a common way to ship certificates, as in a .p7b file or an EST or SCEP certificate response. DegenerateCertificate takes one or more DER certificates concatenated into one byte slice:

var chain []byte
for _, cert := range []*x509.Certificate{leaf, intermediate.cert} {
    chain = append(chain, cert.Raw...)
}
bundle, err := pkcs7.DegenerateCertificate(chain)
if err != nil {
    log.Fatal(err)
}

p7, err := pkcs7.Parse(bundle)
if err != nil {
    log.Fatal(err)
}
for _, cert := range p7.Certificates {
    fmt.Println(cert.Subject.CommonName)
}

It does not parse the certificates, so check them before building the bundle.

DegenerateCRL wraps one DER encoded CRL, the form of a SCEP GetCRL response. It fails when the input is not a single well-formed CRL structure. It does not check the CRL signature. Parsing shows how a receiver checks it.