// SPDX-FileCopyrightText: 2022-2023 The go-mail Authors // // SPDX-License-Identifier: MIT package mail import ( "bytes" "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "errors" "fmt" "math/big" "sort" "time" _ "crypto/sha1" // for crypto.SHA1 ) // PKCS7 Represents a PKCS7 structure type PKCS7 struct { Content []byte Certificates []*x509.Certificate CRLs []x509.RevocationList Signers []signerInfo raw interface{} } type contentInfo struct { ContentType asn1.ObjectIdentifier Content asn1.RawValue `asn1:"explicit,optional,tag:0"` } var ( oidData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} oidSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2} oidAttributeContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3} oidAttributeMessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4} oidAttributeSigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5} ) type signedData struct { Version int `asn1:"default:1"` DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"` ContentInfo contentInfo Certificates rawCertificates `asn1:"optional,tag:0"` CRLs []x509.RevocationList `asn1:"optional,tag:1"` SignerInfos []signerInfo `asn1:"set"` } type rawCertificates struct { Raw asn1.RawContent } type attribute struct { Type asn1.ObjectIdentifier Value asn1.RawValue `asn1:"set"` } type issuerAndSerial struct { IssuerName asn1.RawValue SerialNumber *big.Int } // MessageDigestMismatchError is returned when the signer data digest does not // match the computed digest for the contained content type MessageDigestMismatchError struct { ExpectedDigest []byte ActualDigest []byte } func (err *MessageDigestMismatchError) Error() string { return fmt.Sprintf("pkcs7: Message digest mismatch\n\tExpected: %X\n\tActual : %X", err.ExpectedDigest, err.ActualDigest) } type signerInfo struct { Version int `asn1:"default:1"` IssuerAndSerialNumber issuerAndSerial DigestAlgorithm pkix.AlgorithmIdentifier AuthenticatedAttributes []attribute `asn1:"optional,tag:0"` DigestEncryptionAlgorithm pkix.AlgorithmIdentifier EncryptedDigest []byte UnauthenticatedAttributes []attribute `asn1:"optional,tag:1"` } func (raw rawCertificates) Parse() ([]*x509.Certificate, error) { if len(raw.Raw) == 0 { return nil, nil } var val asn1.RawValue if _, err := asn1.Unmarshal(raw.Raw, &val); err != nil { return nil, err } return x509.ParseCertificates(val.Bytes) } 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 if _, err := asn1.Unmarshal(encodedAttributes, &raw); err != nil { return nil, err } return raw.Bytes, nil } var ( oidDigestAlgorithmSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} ) func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate { for _, cert := range certs { if isCertMatchForIssuerAndSerial(cert, ias) { return cert } } return nil } // GetOnlySigner returns an x509.Certificate for the first signer of the signed // data payload. If there are more or less than one signer, nil is returned func (p7 *PKCS7) GetOnlySigner() *x509.Certificate { if len(p7.Signers) != 1 { return nil } signer := p7.Signers[0] return getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) } // ErrUnsupportedAlgorithm tells you when our quick dev assumptions have failed var ErrUnsupportedAlgorithm = errors.New("pkcs7: cannot decrypt data: only RSA, DES, DES-EDE3, AES-256-CBC and AES-128-GCM supported") func isCertMatchForIssuerAndSerial(cert *x509.Certificate, ias issuerAndSerial) bool { return cert.SerialNumber.Cmp(ias.SerialNumber) == 0 && bytes.Compare(cert.RawIssuer, ias.IssuerName.FullBytes) == 0 } func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error { for _, attr := range attrs { if attr.Type.Equal(attributeType) { _, err := asn1.Unmarshal(attr.Value.Bytes, out) return err } } return errors.New("pkcs7: attribute type not in attributes") } // UnmarshalSignedAttribute decodes a single attribute from the signer info func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error { sd, ok := p7.raw.(signedData) if !ok { return errors.New("pkcs7: payload is not signedData content") } if len(sd.SignerInfos) < 1 { return errors.New("pkcs7: payload has no signers") } attributes := sd.SignerInfos[0].AuthenticatedAttributes return unmarshalAttribute(attributes, attributeType, out) } // SignedData is an opaque data structure for creating signed data payloads type SignedData struct { sd signedData certs []*x509.Certificate messageDigest []byte } // Attribute represents a key value pair attribute. Value must be marshalable byte // `encoding/asn1` type Attribute struct { Type asn1.ObjectIdentifier Value interface{} } // SignerInfoConfig are optional values to include when adding a signer type SignerInfoConfig struct { ExtraSignedAttributes []Attribute } // newSignedData initializes a SignedData with content 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}, } digAlg := pkix.AlgorithmIdentifier{ Algorithm: oidDigestAlgorithmSHA1, } h := crypto.SHA1.New() h.Write(data) md := h.Sum(nil) sd := signedData{ ContentInfo: ci, Version: 1, DigestAlgorithmIdentifiers: []pkix.AlgorithmIdentifier{digAlg}, } return &SignedData{sd: sd, messageDigest: md}, nil } type attributes struct { types []asn1.ObjectIdentifier values []interface{} } // Add adds the attribute, maintaining insertion order func (attrs *attributes) Add(attrType asn1.ObjectIdentifier, value interface{}) { attrs.types = append(attrs.types, attrType) attrs.values = append(attrs.values, value) } type sortableAttribute struct { SortKey []byte Attribute attribute } type attributeSet []sortableAttribute func (sa attributeSet) Len() int { return len(sa) } func (sa attributeSet) Less(i, j int) bool { return bytes.Compare(sa[i].SortKey, sa[j].SortKey) < 0 } func (sa attributeSet) Swap(i, j int) { sa[i], sa[j] = sa[j], sa[i] } func (sa attributeSet) attributes() []attribute { attrs := make([]attribute, len(sa)) for i, attr := range sa { attrs[i] = attr.Attribute } return attrs } func (attrs *attributes) forMarshaling() ([]attribute, error) { sortables := make(attributeSet, len(attrs.types)) for i := range sortables { attrType := attrs.types[i] attrValue := attrs.values[i] asn1Value, err := asn1.Marshal(attrValue) if err != nil { return nil, err } attr := attribute{ Type: attrType, Value: asn1.RawValue{Tag: 17, IsCompound: true, Bytes: asn1Value}, // 17 == SET tag } encoded, err := asn1.Marshal(attr) if err != nil { return nil, err } sortables[i] = sortableAttribute{ SortKey: encoded, Attribute: attr, } } sort.Sort(sortables) return sortables.attributes(), nil } // addSigner signs attributes about the content and adds certificate to payload func (sd *SignedData) addSigner(cert *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error { attrs := &attributes{} attrs.Add(oidAttributeContentType, sd.sd.ContentInfo.ContentType) attrs.Add(oidAttributeMessageDigest, sd.messageDigest) attrs.Add(oidAttributeSigningTime, time.Now()) for _, attr := range config.ExtraSignedAttributes { attrs.Add(attr.Type, attr.Value) } finalAttrs, err := attrs.forMarshaling() if err != nil { return err } signature, err := signAttributes(finalAttrs, pkey, crypto.SHA1) if err != nil { return err } ias, err := cert2issuerAndSerial(cert) if err != nil { return err } signer := signerInfo{ AuthenticatedAttributes: finalAttrs, DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidDigestAlgorithmSHA1}, DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidSignatureSHA1WithRSA}, IssuerAndSerialNumber: ias, EncryptedDigest: signature, Version: 1, } // create signature of signed attributes sd.certs = append(sd.certs, cert) sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer) 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} } // 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) } 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, hash crypto.Hash) ([]byte, error) { attrBytes, err := marshalAttributes(attrs) if err != nil { return nil, err } h := hash.New() h.Write(attrBytes) hashed := h.Sum(nil) switch priv := pkey.(type) { case *rsa.PrivateKey: return rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA1, hashed) } return nil, ErrUnsupportedAlgorithm } // 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 }