400 lines
13 KiB
Go
400 lines
13 KiB
Go
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")
|
|
}
|