package pkcs7 import ( "crypto" "crypto/dsa" "crypto/subtle" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "errors" "fmt" "time" ) // Verify is a wrapper around VerifyWithChain() that initializes an empty // trust store, effectively disabling certificate verification when validating // a signature. func (p7 *PKCS7) Verify() (err error) { return p7.VerifyWithChain(nil) } // VerifyWithChain checks the signatures of a PKCS7 object. // // If truststore is not nil, it also verifies the chain of trust of // the end-entity signer cert to one of the roots in the // truststore. When the PKCS7 object includes the signing time // authenticated attr verifies the chain at that time and UTC now // otherwise. func (p7 *PKCS7) VerifyWithChain(truststore *x509.CertPool) (err error) { if len(p7.Signers) == 0 { return errors.New("pkcs7: Message has no signers") } for _, signer := range p7.Signers { if err := verifySignature(p7, signer, truststore); err != nil { return err } } return nil } // VerifyWithChainAtTime checks the signatures of a PKCS7 object. // // If truststore is not nil, it also verifies the chain of trust of // the end-entity signer cert to a root in the truststore at // currentTime. It does not use the signing time authenticated // attribute. func (p7 *PKCS7) VerifyWithChainAtTime(truststore *x509.CertPool, currentTime time.Time) (err error) { if len(p7.Signers) == 0 { return errors.New("pkcs7: Message has no signers") } for _, signer := range p7.Signers { if err := verifySignatureAtTime(p7, signer, truststore, currentTime); err != nil { return err } } return nil } func verifySignatureAtTime(p7 *PKCS7, signer signerInfo, truststore *x509.CertPool, currentTime time.Time) (err error) { signedData := p7.Content ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) if ee == nil { return errors.New("pkcs7: No certificate for signer") } if len(signer.AuthenticatedAttributes) > 0 { // TODO(fullsailor): First check the content type match var ( digest []byte signingTime time.Time ) err := unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeMessageDigest, &digest) if err != nil { return err } hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm) if err != nil { return err } h := hash.New() h.Write(p7.Content) computed := h.Sum(nil) if subtle.ConstantTimeCompare(digest, computed) != 1 { return &MessageDigestMismatchError{ ExpectedDigest: digest, ActualDigest: computed, } } signedData, err = marshalAttributes(signer.AuthenticatedAttributes) if err != nil { return err } err = unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeSigningTime, &signingTime) if err == nil { // signing time found, performing validity check if signingTime.After(ee.NotAfter) || signingTime.Before(ee.NotBefore) { return fmt.Errorf("pkcs7: signing time %q is outside of certificate validity %q to %q", signingTime.Format(time.RFC3339), ee.NotBefore.Format(time.RFC3339), ee.NotAfter.Format(time.RFC3339)) } } } if truststore != nil { _, err = verifyCertChain(ee, p7.Certificates, truststore, currentTime) if err != nil { return err } } sigalg, err := getSignatureAlgorithm(signer.DigestEncryptionAlgorithm, signer.DigestAlgorithm) if err != nil { return err } switch sigalg { case x509.DSAWithSHA1, x509.DSAWithSHA256: return dsaCheckSignature(sigalg, signedData, signer.EncryptedDigest, ee.PublicKey) default: return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest) } } // dsaSignature verifies the DSA signature on a PKCS7 document. DSA support was // removed from Go's crypto/x509 support prior to Go 1.16. This allows // verifying legacy signatures until affected applications can be migrated off // of DSA. func dsaCheckSignature(algo x509.SignatureAlgorithm, signed, signature []byte, publicKey crypto.PublicKey) error { dsaKey, ok := publicKey.(*dsa.PublicKey) if !ok { return ErrUnsupportedAlgorithm } var hashType crypto.Hash switch algo { case x509.DSAWithSHA1: hashType = crypto.SHA1 case x509.DSAWithSHA256: hashType = crypto.SHA256 default: return ErrUnsupportedAlgorithm } h := hashType.New() h.Write(signed) signed = h.Sum(nil) dsaSig := new(dsaSignature) if rest, err := asn1.Unmarshal(signature, dsaSig); err != nil { return err } else if len(rest) != 0 { return errors.New("x509: trailing data after DSA signature") } if dsaSig.R.Sign() <= 0 || dsaSig.S.Sign() <= 0 { return errors.New("x509: DSA signature contained zero or negative values") } // According to FIPS 186-3, section 4.6, the hash must be truncated if it is longer // than the key length, but crypto/dsa doesn't do it automatically. if maxHashLen := dsaKey.Q.BitLen() / 8; maxHashLen < len(signed) { signed = signed[:maxHashLen] } if !dsa.Verify(dsaKey, signed, dsaSig.R, dsaSig.S) { return errors.New("x509: DSA verification failure") } return nil } func verifySignature(p7 *PKCS7, signer signerInfo, truststore *x509.CertPool) (err error) { signedData := p7.Content ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) if ee == nil { return errors.New("pkcs7: No certificate for signer") } signingTime := time.Now().UTC() if len(signer.AuthenticatedAttributes) > 0 { // TODO(fullsailor): First check the content type match var digest []byte err := unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeMessageDigest, &digest) if err != nil { return err } hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm) if err != nil { return err } h := hash.New() h.Write(p7.Content) computed := h.Sum(nil) if subtle.ConstantTimeCompare(digest, computed) != 1 { return &MessageDigestMismatchError{ ExpectedDigest: digest, ActualDigest: computed, } } signedData, err = marshalAttributes(signer.AuthenticatedAttributes) if err != nil { return err } err = unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeSigningTime, &signingTime) if err == nil { // signing time found, performing validity check if signingTime.After(ee.NotAfter) || signingTime.Before(ee.NotBefore) { return fmt.Errorf("pkcs7: signing time %q is outside of certificate validity %q to %q", signingTime.Format(time.RFC3339), ee.NotBefore.Format(time.RFC3339), ee.NotAfter.Format(time.RFC3339)) } } } if truststore != nil { _, err = verifyCertChain(ee, p7.Certificates, truststore, signingTime) if err != nil { return err } } sigalg, err := getSignatureAlgorithm(signer.DigestEncryptionAlgorithm, signer.DigestAlgorithm) if err != nil { return err } switch sigalg { case x509.DSAWithSHA1, x509.DSAWithSHA256: return dsaCheckSignature(sigalg, signedData, signer.EncryptedDigest, ee.PublicKey) default: return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest) } } // 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) } // 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) } func parseSignedData(data []byte) (*PKCS7, error) { var sd signedData asn1.Unmarshal(data, &sd) certs, err := sd.Certificates.Parse() if err != nil { return nil, err } // fmt.Printf("--> Signed Data Version %d\n", sd.Version) var compound asn1.RawValue var content unsignedData // The Content.Bytes maybe empty on PKI responses. if len(sd.ContentInfo.Content.Bytes) > 0 { if _, err := asn1.Unmarshal(sd.ContentInfo.Content.Bytes, &compound); err != nil { return nil, err } } // Compound octet string if compound.IsCompound { if compound.Tag == 4 { if _, err = asn1.Unmarshal(compound.Bytes, &content); err != nil { return nil, err } } else { content = compound.Bytes } } else { // assuming this is tag 04 content = compound.Bytes } return &PKCS7{ Content: content, Certificates: certs, CRLs: sd.CRLs, Signers: sd.SignerInfos, raw: sd, }, nil } // verifyCertChain takes an end-entity certs, a list of potential intermediates and a // truststore, and built all potential chains between the EE and a trusted root. // // When verifying chains that may have expired, currentTime can be set to a past date // to allow the verification to pass. If unset, currentTime is set to the current UTC time. func verifyCertChain(ee *x509.Certificate, certs []*x509.Certificate, truststore *x509.CertPool, currentTime time.Time) (chains [][]*x509.Certificate, err error) { intermediates := x509.NewCertPool() for _, intermediate := range certs { intermediates.AddCert(intermediate) } verifyOptions := x509.VerifyOptions{ Roots: truststore, Intermediates: intermediates, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, CurrentTime: currentTime, } chains, err = ee.Verify(verifyOptions) if err != nil { return chains, fmt.Errorf("pkcs7: failed to verify certificate chain: %v", err) } return } // 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) } func getSignatureAlgorithm(digestEncryption, digest pkix.AlgorithmIdentifier) (x509.SignatureAlgorithm, error) { switch { case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA1): return x509.ECDSAWithSHA1, nil case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA256): return x509.ECDSAWithSHA256, nil case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA384): return x509.ECDSAWithSHA384, nil case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA512): return x509.ECDSAWithSHA512, nil case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSA), digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA1), digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA256), digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA384), digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA512): switch { case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): return x509.SHA1WithRSA, nil case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): return x509.SHA256WithRSA, nil case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384): return x509.SHA384WithRSA, nil case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512): return x509.SHA512WithRSA, nil default: return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", digest.Algorithm.String(), digestEncryption.Algorithm.String()) } case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSA), digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSASHA1): switch { case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): return x509.DSAWithSHA1, nil case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): return x509.DSAWithSHA256, nil default: return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", digest.Algorithm.String(), digestEncryption.Algorithm.String()) } case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP256), digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP384), digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP521): switch { case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): return x509.ECDSAWithSHA1, nil case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): return x509.ECDSAWithSHA256, nil case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384): return x509.ECDSAWithSHA384, nil case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512): return x509.ECDSAWithSHA512, nil default: return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", digest.Algorithm.String(), digestEncryption.Algorithm.String()) } default: return -1, fmt.Errorf("pkcs7: unsupported algorithm %q", digestEncryption.Algorithm.String()) } } func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate { for _, cert := range certs { if isCertMatchForIssuerAndSerial(cert, ias) { return cert } } return nil } 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") }