Skip to content

Commit

Permalink
fix: added VerifySigner
Browse files Browse the repository at this point in the history
Signed-off-by: Junjie Gao <[email protected]>
  • Loading branch information
JeyJeyGao committed Jan 11, 2024
1 parent eeaef58 commit 323dec0
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 11 deletions.
63 changes: 57 additions & 6 deletions internal/crypto/cms/signed.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,12 @@ func (d *ParsedSignedData) Verify(opts x509.VerifyOptions) ([]*x509.Certificate,
return nil, VerificationError{Message: fmt.Sprintf("invalid signer info version: only version 1 is supported; got %d", signerInfo.Version)}
}

cert, err := d.verify(&signerInfo, &opts)
signingCertificate := d.getCertificate(signerInfo.SignerIdentifier)
if signingCertificate == nil {
return nil, ErrCertificateNotFound
}

cert, err := d.verify(&signerInfo, signingCertificate, &opts)
if err != nil {
lastErr = err
continue
Expand All @@ -137,18 +142,58 @@ func (d *ParsedSignedData) Verify(opts x509.VerifyOptions) ([]*x509.Certificate,
return verifiedSigningCertificates, nil
}

// VerifySigner verifies the signerInfo against the signingCertificate.
//
// SignedData's certificates field is optional, so this function can be used
// to verify the signerInfo without the certificates field. Or the user doesn't
// trust the certificates of SignedData and the signer identifier of
// the SignerInfo (they are unsigned fields), this function can be used to
// verify the signerInfo against the user provided signingCertificate.
//
// References:
// - RFC 5652 5 Signed-data Content Type
// - RFC 5652 5.4 Message Digest Calculation Process
// - RFC 5652 5.6 Signature Verification Process
//
// WARNING: this function doesn't do any revocation checking.
func (d *ParsedSignedData) VerifySigner(signerInfo *SignerInfo, signingCertificate *x509.Certificate, opts x509.VerifyOptions) (*x509.Certificate, error) {
if signerInfo.Version != 1 {
// Only IssuerAndSerialNumber is supported currently
return nil, VerificationError{Message: fmt.Sprintf("invalid signer info version: only version 1 is supported; got %d", signerInfo.Version)}
}

intermediates := x509.NewCertPool()
for _, cert := range d.Certificates {
intermediates.AddCert(cert)
}
opts.Intermediates = intermediates

if signingCertificate == nil {
// user didn't provide signing certificate, find it from the signed data
// certificates
signingCertificate := d.getCertificate(signerInfo.SignerIdentifier)
if signingCertificate == nil {
return nil, ErrCertificateNotFound
}
} else {
// user provided signing certificate must match the one in signer info
if !bytes.Equal(signingCertificate.RawIssuer, signerInfo.SignerIdentifier.Issuer.FullBytes) || signingCertificate.SerialNumber.Cmp(signerInfo.SignerIdentifier.SerialNumber) != 0 {
return nil, SyntaxError{Message: "signing certificate does not match signer info"}
}
}

return d.verify(signerInfo, signingCertificate, &opts)
}

// verify verifies the trust in a top-down manner.
//
// References:
// - RFC 5652 5.4 Message Digest Calculation Process
// - RFC 5652 5.6 Signature Verification Process
func (d *ParsedSignedData) verify(signerInfo *SignerInfo, opts *x509.VerifyOptions) (*x509.Certificate, error) {
// find signer certificate
cert := d.getCertificate(signerInfo.SignerIdentifier)
func (d *ParsedSignedData) verify(signerInfo *SignerInfo, cert *x509.Certificate, opts *x509.VerifyOptions) (*x509.Certificate, error) {
if cert == nil {
return nil, ErrCertificateNotFound
}

// verify signer certificate
certChains, err := cert.Verify(*opts)
if err != nil {
Expand Down Expand Up @@ -197,11 +242,17 @@ func (d *ParsedSignedData) verifySignature(signerInfo *SignerInfo, cert *x509.Ce
// verifyAttributes verifies the signed attributes.
//
// References:
// - RFC 5652 5.3 SignerInfo Type
// - RFC 5652 5.6 Signature Verification Process
func (d *ParsedSignedData) verifyAttributes(signerInfo *SignerInfo, chains [][]*x509.Certificate) error {
// verify attributes if present
if len(signerInfo.SignedAttributes) == 0 {
return nil
if d.ContentType.Equal(oid.Data) {
return nil
}
// signed attributes MUST be present if the content type of the
// EncapsulatedContentInfo value being signed is not id-data.
return VerificationError{Message: "missing signed attributes"}
}

var contentType asn1.ObjectIdentifier
Expand Down
101 changes: 100 additions & 1 deletion internal/crypto/cms/signed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func TestVerify(t *testing.T) {
wantErr: true,
},
{
name: "without signed attributes",
name: "id-data content type without signed attributes",
filePath: "testdata/SignedDataWithoutSignedAttributes.p7s",
wantErr: false,
},
Expand Down Expand Up @@ -196,6 +196,100 @@ func TestVerify(t *testing.T) {
}
}

func TestVerifySigner(t *testing.T) {
testData := []struct {
name string
filePath string
wantErr bool
}{
{
name: "access certificate with signer identifier",
filePath: "testdata/TimeStampToken.p7s",
wantErr: false,
},
{
name: "signer version is not 1",
filePath: "testdata/TimeStampTokenWithSignerVersion2.p7s",
wantErr: true,
},
{
name: "certificates is empty",
filePath: "testdata/TimeStampTokenWithoutCertificate.p7s",
wantErr: true,
},
}
for _, testcase := range testData {
t.Run(testcase.name, func(t *testing.T) {
// parse signed data
sigBytes, err := os.ReadFile(testcase.filePath)
if err != nil {
t.Fatal("failed to read test signature:", err)
}
signed, err := ParseSignedData(sigBytes)
if err != nil {
t.Fatal("ParseSignedData() error =", err)
}

// verify with no root CAs and should fail
roots := x509.NewCertPool()
certLen := len(signed.Certificates)
if certLen > 0 {
roots.AddCert(signed.Certificates[certLen-1])
}
opts := x509.VerifyOptions{
Roots: roots,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping},
CurrentTime: time.Date(2024, 1, 9, 0, 0, 0, 0, time.UTC),
}
_, err = signed.VerifySigner(&signed.SignerInfos[0], nil, opts)
if testcase.wantErr && err == nil {
t.Errorf("ParseSignedData.Verify() error = %v, wantErr %v", err, true)
} else if !testcase.wantErr && err != nil {
t.Errorf("ParseSignedData.Verify() error = %v, wantErr %v", err, false)
}
})
}
}

func TestVerifySignerWithUserProvidedCertificate(t *testing.T) {
// parse signed data
sigBytes, err := os.ReadFile("testdata/TimeStampToken.p7s")
if err != nil {
t.Fatal("failed to read test signature:", err)
}
signed, err := ParseSignedData(sigBytes)
if err != nil {
t.Fatal("ParseSignedData() error =", err)
}

roots := x509.NewCertPool()
certLen := len(signed.Certificates)
if certLen > 0 {
roots.AddCert(signed.Certificates[certLen-1])
}
opts := x509.VerifyOptions{
Roots: roots,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping},
CurrentTime: time.Date(2024, 1, 9, 0, 0, 0, 0, time.UTC),
}

t.Run("valid user provided signing certificate", func(t *testing.T) {
// verify with no root CAs and should fail
_, err = signed.VerifySigner(&signed.SignerInfos[0], signed.Certificates[0], opts)
if err != nil {
t.Errorf("ParseSignedData.Verify() error = %v, wantErr %v", err, false)
}
})

t.Run("invalid user provided signing certificate", func(t *testing.T) {
// verify with no root CAs and should fail
_, err = signed.VerifySigner(&signed.SignerInfos[0], signed.Certificates[1], opts)
if err != nil {
t.Errorf("ParseSignedData.Verify() error = %v, wantErr %v", err, false)
}
})
}

func TestVerifyAttributes(t *testing.T) {
testData := []struct {
name string
Expand Down Expand Up @@ -237,6 +331,11 @@ func TestVerifyAttributes(t *testing.T) {
filePath: "testdata/TimeStampTokenWithSigningTimeBeforeExpected.p7s",
wantErr: true,
},
{
name: "timestamp token without signed attributes",
filePath: "testdata/TimeStampTokenWithoutSignedAttributes.p7s",
wantErr: true,
},
}

for _, testcase := range testData {
Expand Down
Binary file not shown.
4 changes: 0 additions & 4 deletions internal/crypto/hashutil/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ import (
"testing"
)

type mockHash struct {
crypto.Hash
}

func TestComputeHash(t *testing.T) {
message := []byte("test message")
expectedHash := sha256.Sum256(message)
Expand Down
3 changes: 3 additions & 0 deletions internal/crypto/oid/oid.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ var (

// OIDs defined in RFC 5652 Cryptographic Message Syntax (CMS)
var (
// Data (id-data) is defined in RFC 5652 4 Data Content Type
Data = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}

// SignedData (id-signedData) is defined in RFC 5652 5.1 SignedData Type
SignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}

Expand Down

0 comments on commit 323dec0

Please sign in to comment.