Skip to content

Commit

Permalink
Merge pull request #56 from mozilla-services/verify-at-time
Browse files Browse the repository at this point in the history
add VerifyWithChainAtTime
  • Loading branch information
g-k authored Jul 30, 2021
2 parents 947daa5 + b1b0a34 commit 7259124
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 3 deletions.
83 changes: 81 additions & 2 deletions verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ func (p7 *PKCS7) Verify() (err error) {
}

// 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 root in the truststore.
//
// 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")
Expand All @@ -32,6 +36,81 @@ func (p7 *PKCS7) VerifyWithChain(truststore *x509.CertPool) (err error) {
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
}
return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest)
}

func verifySignature(p7 *PKCS7, signer signerInfo, truststore *x509.CertPool) (err error) {
signedData := p7.Content
ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)
Expand Down
23 changes: 23 additions & 0 deletions verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,32 @@ func TestVerifyFirefoxAddon(t *testing.T) {
p7.Content = FirefoxAddonContent
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(FirefoxAddonRootCert)
// verifies at the signingTime authenticated attr
if err := p7.VerifyWithChain(certPool); err != nil {
t.Errorf("Verify failed with error: %v", err)
}

// TODO: update to check for an expiration error when the EE
// expires on 2021-08-16 20:04:58 +0000 UTC
//
// The chain has validity:
//
// EE: 2016-08-17 20:04:58 +0000 UTC 2021-08-16 20:04:58 +0000 UTC
// Intermediate: 2015-03-17 23:52:42 +0000 UTC 2025-03-14 23:52:42 +0000 UTC
// Root: 2015-03-17 22:53:57 +0000 UTC 2025-03-14 22:53:57 +0000 UTC
if err = p7.VerifyWithChainAtTime(certPool, time.Now().UTC()); err != nil {
t.Errorf("Verify at UTC now failed with error: %v", err)
}

expiredTime := time.Date(2030, time.January, 1, 0, 0, 0, 0, time.UTC)
if err = p7.VerifyWithChainAtTime(certPool, expiredTime); err == nil {
t.Errorf("Verify at expired time %s did not error", expiredTime)
}
notYetValidTime := time.Date(1999, time.July, 5, 0, 13, 0, 0, time.UTC)
if err = p7.VerifyWithChainAtTime(certPool, notYetValidTime); err == nil {
t.Errorf("Verify at not yet valid time %s did not error", notYetValidTime)
}

// Verify the certificate chain to make sure the identified root
// is the one we expect
ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, p7.Signers[0].IssuerAndSerialNumber)
Expand Down
1 change: 0 additions & 1 deletion verify_test_dsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ CWCGSAFlAwQDAgUAAzAAMC0CFQCIgQtrZZ9hdZG1ROhR5hc8nYEmbgIUAIlgC688
qzy/7yePTlhlpj+ahMM=
-----END CERTIFICATE-----`)


type DSATestFixture struct {
Input []byte
Certificate *x509.Certificate
Expand Down

0 comments on commit 7259124

Please sign in to comment.