| package pkcs7 |
| |
| import ( |
| "bytes" |
| "crypto" |
| "crypto/dsa" |
| "crypto/rand" |
| "crypto/x509" |
| "crypto/x509/pkix" |
| "encoding/asn1" |
| "errors" |
| "fmt" |
| "math/big" |
| "time" |
| |
| "github.com/hashicorp/vault/internal" |
| ) |
| |
| func init() { |
| internal.PatchSha1() |
| } |
| |
| // SignedData is an opaque data structure for creating signed data payloads |
| type SignedData struct { |
| sd signedData |
| certs []*x509.Certificate |
| data, messageDigest []byte |
| digestOid asn1.ObjectIdentifier |
| encryptionOid asn1.ObjectIdentifier |
| } |
| |
| // NewSignedData takes data and initializes a PKCS7 SignedData struct that is |
| // ready to be signed via AddSigner. The digest algorithm is set to SHA-256 by default |
| // and can be changed by calling SetDigestAlgorithm. |
| func NewSignedData(data []byte) (*SignedData, error) { |
| content, err := asn1.Marshal(data) |
| if err != nil { |
| return nil, err |
| } |
| ci := contentInfo{ |
| ContentType: OIDData, |
| Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, |
| } |
| sd := signedData{ |
| ContentInfo: ci, |
| Version: 1, |
| } |
| return &SignedData{sd: sd, data: data, digestOid: OIDDigestAlgorithmSHA256}, nil |
| } |
| |
| // SignerInfoConfig are optional values to include when adding a signer |
| type SignerInfoConfig struct { |
| ExtraSignedAttributes []Attribute |
| ExtraUnsignedAttributes []Attribute |
| } |
| |
| type signedData struct { |
| Version int `asn1:"default:1"` |
| DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"` |
| ContentInfo contentInfo |
| Certificates rawCertificates `asn1:"optional,tag:0"` |
| CRLs []pkix.CertificateList `asn1:"optional,tag:1"` |
| SignerInfos []signerInfo `asn1:"set"` |
| } |
| |
| type signerInfo struct { |
| Version int `asn1:"default:1"` |
| IssuerAndSerialNumber issuerAndSerial |
| DigestAlgorithm pkix.AlgorithmIdentifier |
| AuthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:0"` |
| DigestEncryptionAlgorithm pkix.AlgorithmIdentifier |
| EncryptedDigest []byte |
| UnauthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:1"` |
| } |
| |
| type attribute struct { |
| Type asn1.ObjectIdentifier |
| Value asn1.RawValue `asn1:"set"` |
| } |
| |
| func marshalAttributes(attrs []attribute) ([]byte, error) { |
| encodedAttributes, err := asn1.Marshal(struct { |
| A []attribute `asn1:"set"` |
| }{A: attrs}) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Remove the leading sequence octets |
| var raw asn1.RawValue |
| asn1.Unmarshal(encodedAttributes, &raw) |
| return raw.Bytes, nil |
| } |
| |
| type rawCertificates struct { |
| Raw asn1.RawContent |
| } |
| |
| type issuerAndSerial struct { |
| IssuerName asn1.RawValue |
| SerialNumber *big.Int |
| } |
| |
| // SetDigestAlgorithm sets the digest algorithm to be used in the signing process. |
| // |
| // This should be called before adding signers |
| func (sd *SignedData) SetDigestAlgorithm(d asn1.ObjectIdentifier) { |
| sd.digestOid = d |
| } |
| |
| // SetEncryptionAlgorithm sets the encryption algorithm to be used in the signing process. |
| // |
| // This should be called before adding signers |
| func (sd *SignedData) SetEncryptionAlgorithm(d asn1.ObjectIdentifier) { |
| sd.encryptionOid = d |
| } |
| |
| // AddSigner is a wrapper around AddSignerChain() that adds a signer without any parent. |
| func (sd *SignedData) AddSigner(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error { |
| var parents []*x509.Certificate |
| return sd.AddSignerChain(ee, pkey, parents, config) |
| } |
| |
| // AddSignerChain signs attributes about the content and adds certificates |
| // and signers infos to the Signed Data. The certificate and private key |
| // of the end-entity signer are used to issue the signature, and any |
| // parent of that end-entity that need to be added to the list of |
| // certifications can be specified in the parents slice. |
| // |
| // The signature algorithm used to hash the data is the one of the end-entity |
| // certificate. |
| func (sd *SignedData) AddSignerChain(ee *x509.Certificate, pkey crypto.PrivateKey, parents []*x509.Certificate, config SignerInfoConfig) error { |
| // Following RFC 2315, 9.2 SignerInfo type, the distinguished name of |
| // the issuer of the end-entity signer is stored in the issuerAndSerialNumber |
| // section of the SignedData.SignerInfo, alongside the serial number of |
| // the end-entity. |
| var ias issuerAndSerial |
| ias.SerialNumber = ee.SerialNumber |
| if len(parents) == 0 { |
| // no parent, the issuer is the end-entity cert itself |
| ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer} |
| } else { |
| err := verifyPartialChain(ee, parents) |
| if err != nil { |
| return err |
| } |
| // the first parent is the issuer |
| ias.IssuerName = asn1.RawValue{FullBytes: parents[0].RawSubject} |
| } |
| sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, |
| pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, |
| ) |
| hash, err := getHashForOID(sd.digestOid) |
| if err != nil { |
| return err |
| } |
| h := hash.New() |
| h.Write(sd.data) |
| sd.messageDigest = h.Sum(nil) |
| encryptionOid, err := getOIDForEncryptionAlgorithm(pkey, sd.digestOid) |
| if err != nil { |
| return err |
| } |
| attrs := &attributes{} |
| attrs.Add(OIDAttributeContentType, sd.sd.ContentInfo.ContentType) |
| attrs.Add(OIDAttributeMessageDigest, sd.messageDigest) |
| attrs.Add(OIDAttributeSigningTime, time.Now().UTC()) |
| for _, attr := range config.ExtraSignedAttributes { |
| attrs.Add(attr.Type, attr.Value) |
| } |
| finalAttrs, err := attrs.ForMarshalling() |
| if err != nil { |
| return err |
| } |
| unsignedAttrs := &attributes{} |
| for _, attr := range config.ExtraUnsignedAttributes { |
| unsignedAttrs.Add(attr.Type, attr.Value) |
| } |
| finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling() |
| if err != nil { |
| return err |
| } |
| // create signature of signed attributes |
| signature, err := signAttributes(finalAttrs, pkey, hash) |
| if err != nil { |
| return err |
| } |
| signer := signerInfo{ |
| AuthenticatedAttributes: finalAttrs, |
| UnauthenticatedAttributes: finalUnsignedAttrs, |
| DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, |
| DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: encryptionOid}, |
| IssuerAndSerialNumber: ias, |
| EncryptedDigest: signature, |
| Version: 1, |
| } |
| sd.certs = append(sd.certs, ee) |
| if len(parents) > 0 { |
| sd.certs = append(sd.certs, parents...) |
| } |
| sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer) |
| return nil |
| } |
| |
| // SignWithoutAttr issues a signature on the content of the pkcs7 SignedData. |
| // Unlike AddSigner/AddSignerChain, it calculates the digest on the data alone |
| // and does not include any signed attributes like timestamp and so on. |
| // |
| // This function is needed to sign old Android APKs, something you probably |
| // shouldn't do unless you're maintaining backward compatibility for old |
| // applications. |
| func (sd *SignedData) SignWithoutAttr(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error { |
| var signature []byte |
| sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}) |
| hash, err := getHashForOID(sd.digestOid) |
| if err != nil { |
| return err |
| } |
| h := hash.New() |
| h.Write(sd.data) |
| sd.messageDigest = h.Sum(nil) |
| switch pkey := pkey.(type) { |
| case *dsa.PrivateKey: |
| // dsa doesn't implement crypto.Signer so we make a special case |
| // https://github.com/golang/go/issues/27889 |
| r, s, err := dsa.Sign(rand.Reader, pkey, sd.messageDigest) |
| if err != nil { |
| return err |
| } |
| signature, err = asn1.Marshal(dsaSignature{r, s}) |
| if err != nil { |
| return err |
| } |
| default: |
| key, ok := pkey.(crypto.Signer) |
| if !ok { |
| return errors.New("pkcs7: private key does not implement crypto.Signer") |
| } |
| signature, err = key.Sign(rand.Reader, sd.messageDigest, hash) |
| if err != nil { |
| return err |
| } |
| } |
| var ias issuerAndSerial |
| ias.SerialNumber = ee.SerialNumber |
| // no parent, the issue is the end-entity cert itself |
| ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer} |
| if sd.encryptionOid == nil { |
| // if the encryption algorithm wasn't set by SetEncryptionAlgorithm, |
| // infer it from the digest algorithm |
| sd.encryptionOid, err = getOIDForEncryptionAlgorithm(pkey, sd.digestOid) |
| } |
| if err != nil { |
| return err |
| } |
| signer := signerInfo{ |
| DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, |
| DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.encryptionOid}, |
| IssuerAndSerialNumber: ias, |
| EncryptedDigest: signature, |
| Version: 1, |
| } |
| // create signature of signed attributes |
| sd.certs = append(sd.certs, ee) |
| sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer) |
| return nil |
| } |
| |
| func (si *signerInfo) SetUnauthenticatedAttributes(extraUnsignedAttrs []Attribute) error { |
| unsignedAttrs := &attributes{} |
| for _, attr := range extraUnsignedAttrs { |
| unsignedAttrs.Add(attr.Type, attr.Value) |
| } |
| finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling() |
| if err != nil { |
| return err |
| } |
| |
| si.UnauthenticatedAttributes = finalUnsignedAttrs |
| |
| return nil |
| } |
| |
| // AddCertificate adds the certificate to the payload. Useful for parent certificates |
| func (sd *SignedData) AddCertificate(cert *x509.Certificate) { |
| sd.certs = append(sd.certs, cert) |
| } |
| |
| // Detach removes content from the signed data struct to make it a detached signature. |
| // This must be called right before Finish() |
| func (sd *SignedData) Detach() { |
| sd.sd.ContentInfo = contentInfo{ContentType: OIDData} |
| } |
| |
| // GetSignedData returns the private Signed Data |
| func (sd *SignedData) GetSignedData() *signedData { |
| return &sd.sd |
| } |
| |
| // Finish marshals the content and its signers |
| func (sd *SignedData) Finish() ([]byte, error) { |
| sd.sd.Certificates = marshalCertificates(sd.certs) |
| inner, err := asn1.Marshal(sd.sd) |
| if err != nil { |
| return nil, err |
| } |
| outer := contentInfo{ |
| ContentType: OIDSignedData, |
| Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true}, |
| } |
| return asn1.Marshal(outer) |
| } |
| |
| // RemoveAuthenticatedAttributes removes authenticated attributes from signedData |
| // similar to OpenSSL's PKCS7_NOATTR or -noattr flags |
| func (sd *SignedData) RemoveAuthenticatedAttributes() { |
| for i := range sd.sd.SignerInfos { |
| sd.sd.SignerInfos[i].AuthenticatedAttributes = nil |
| } |
| } |
| |
| // RemoveUnauthenticatedAttributes removes unauthenticated attributes from signedData |
| func (sd *SignedData) RemoveUnauthenticatedAttributes() { |
| for i := range sd.sd.SignerInfos { |
| sd.sd.SignerInfos[i].UnauthenticatedAttributes = nil |
| } |
| } |
| |
| // verifyPartialChain checks that a given cert is issued by the first parent in the list, |
| // then continue down the path. It doesn't require the last parent to be a root CA, |
| // or to be trusted in any truststore. It simply verifies that the chain provided, albeit |
| // partial, makes sense. |
| func verifyPartialChain(cert *x509.Certificate, parents []*x509.Certificate) error { |
| if len(parents) == 0 { |
| return fmt.Errorf("pkcs7: zero parents provided to verify the signature of certificate %q", cert.Subject.CommonName) |
| } |
| err := cert.CheckSignatureFrom(parents[0]) |
| if err != nil { |
| return fmt.Errorf("pkcs7: certificate signature from parent is invalid: %v", err) |
| } |
| if len(parents) == 1 { |
| // there is no more parent to check, return |
| return nil |
| } |
| return verifyPartialChain(parents[0], parents[1:]) |
| } |
| |
| func cert2issuerAndSerial(cert *x509.Certificate) (issuerAndSerial, error) { |
| var ias issuerAndSerial |
| // The issuer RDNSequence has to match exactly the sequence in the certificate |
| // We cannot use cert.Issuer.ToRDNSequence() here since it mangles the sequence |
| ias.IssuerName = asn1.RawValue{FullBytes: cert.RawIssuer} |
| ias.SerialNumber = cert.SerialNumber |
| |
| return ias, nil |
| } |
| |
| // signs the DER encoded form of the attributes with the private key |
| func signAttributes(attrs []attribute, pkey crypto.PrivateKey, digestAlg crypto.Hash) ([]byte, error) { |
| attrBytes, err := marshalAttributes(attrs) |
| if err != nil { |
| return nil, err |
| } |
| h := digestAlg.New() |
| h.Write(attrBytes) |
| hash := h.Sum(nil) |
| |
| // dsa doesn't implement crypto.Signer so we make a special case |
| // https://github.com/golang/go/issues/27889 |
| switch pkey := pkey.(type) { |
| case *dsa.PrivateKey: |
| r, s, err := dsa.Sign(rand.Reader, pkey, hash) |
| if err != nil { |
| return nil, err |
| } |
| return asn1.Marshal(dsaSignature{r, s}) |
| } |
| |
| key, ok := pkey.(crypto.Signer) |
| if !ok { |
| return nil, errors.New("pkcs7: private key does not implement crypto.Signer") |
| } |
| return key.Sign(rand.Reader, hash, digestAlg) |
| } |
| |
| type dsaSignature struct { |
| R, S *big.Int |
| } |
| |
| // concats and wraps the certificates in the RawValue structure |
| func marshalCertificates(certs []*x509.Certificate) rawCertificates { |
| var buf bytes.Buffer |
| for _, cert := range certs { |
| buf.Write(cert.Raw) |
| } |
| rawCerts, _ := marshalCertificateBytes(buf.Bytes()) |
| return rawCerts |
| } |
| |
| // Even though, the tag & length are stripped out during marshalling the |
| // RawContent, we have to encode it into the RawContent. If its missing, |
| // then `asn1.Marshal()` will strip out the certificate wrapper instead. |
| func marshalCertificateBytes(certs []byte) (rawCertificates, error) { |
| val := asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true} |
| b, err := asn1.Marshal(val) |
| if err != nil { |
| return rawCertificates{}, err |
| } |
| return rawCertificates{Raw: b}, nil |
| } |
| |
| // DegenerateCertificate creates a signed data structure containing only the |
| // provided certificate or certificate chain. |
| func DegenerateCertificate(cert []byte) ([]byte, error) { |
| rawCert, err := marshalCertificateBytes(cert) |
| if err != nil { |
| return nil, err |
| } |
| emptyContent := contentInfo{ContentType: OIDData} |
| sd := signedData{ |
| Version: 1, |
| ContentInfo: emptyContent, |
| Certificates: rawCert, |
| CRLs: []pkix.CertificateList{}, |
| } |
| content, err := asn1.Marshal(sd) |
| if err != nil { |
| return nil, err |
| } |
| signedContent := contentInfo{ |
| ContentType: OIDSignedData, |
| Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, |
| } |
| return asn1.Marshal(signedContent) |
| } |