From 7d0e4676d6dbba60cec64fc0c0ada3d504d78b66 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Fri, 19 Aug 2022 15:16:53 +0800 Subject: [PATCH] test: add signature unit tests (#43) * test: add signature unit tests Signed-off-by: Binbin Li * test: add tests for base envelope Signed-off-by: Binbin Li * test: add more valid cases Signed-off-by: Binbin Li Signed-off-by: Binbin Li Co-authored-by: Binbin Li --- signature/algorithm_test.go | 231 +++++++ signature/envelope_test.go | 199 ++++++ signature/errors_test.go | 202 ++++++ signature/internal/base/envelope.go | 2 +- signature/internal/base/envelope_test.go | 792 +++++++++++++++++++++++ signature/signer_test.go | 220 +++++++ testhelper/certificatetest.go | 71 +- 7 files changed, 1711 insertions(+), 6 deletions(-) create mode 100644 signature/algorithm_test.go create mode 100644 signature/envelope_test.go create mode 100644 signature/errors_test.go create mode 100644 signature/internal/base/envelope_test.go create mode 100644 signature/signer_test.go diff --git a/signature/algorithm_test.go b/signature/algorithm_test.go new file mode 100644 index 00000000..40066772 --- /dev/null +++ b/signature/algorithm_test.go @@ -0,0 +1,231 @@ +package signature + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "reflect" + "strconv" + "testing" + + "github.com/notaryproject/notation-core-go/testhelper" +) + +func TestHash(t *testing.T) { + tests := []struct { + name string + alg Algorithm + expect crypto.Hash + }{ + { + name: "PS256", + alg: AlgorithmPS256, + expect: crypto.SHA256, + }, + { + name: "ES256", + alg: AlgorithmES256, + expect: crypto.SHA256, + }, + { + name: "PS384", + alg: AlgorithmPS384, + expect: crypto.SHA384, + }, + { + name: "ES384", + alg: AlgorithmES384, + expect: crypto.SHA384, + }, + { + name: "PS512", + alg: AlgorithmPS512, + expect: crypto.SHA512, + }, + { + name: "ES512", + alg: AlgorithmES512, + expect: crypto.SHA512, + }, + { + name: "UnsupportedAlgorithm", + alg: 0, + expect: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hash := tt.alg.Hash() + if hash != tt.expect { + t.Fatalf("Expected %v, got %v", tt.expect, hash) + } + }) + } +} + +func TestExtractKeySpec(t *testing.T) { + type testCase struct { + name string + cert *x509.Certificate + expect KeySpec + expectErr bool + } + // invalid cases + tests := []testCase{ + { + name: "RSA wrong size", + cert: testhelper.GetUnsupportedRSACert().Cert, + expect: KeySpec{}, + expectErr: true, + }, + { + name: "ECDSA wrong size", + cert: testhelper.GetUnsupportedECCert().Cert, + expect: KeySpec{}, + expectErr: true, + }, + { + name: "Unsupported type", + cert: &x509.Certificate{ + PublicKey: ed25519.PublicKey{}, + }, + expect: KeySpec{}, + expectErr: true, + }, + } + + // append valid RSA cases + for _, k := range []int{2048, 3072, 4096} { + rsaRoot := testhelper.GetRSARootCertificate() + priv, _ := rsa.GenerateKey(rand.Reader, k) + + certTuple := testhelper.GetRSACertTupleWithPK( + priv, + "Test RSA_"+strconv.Itoa(priv.Size()), + &rsaRoot, + ) + tests = append(tests, testCase{ + name: "RSA " + strconv.Itoa(k), + cert: certTuple.Cert, + expect: KeySpec{ + Type: KeyTypeRSA, + Size: k, + }, + expectErr: false, + }) + } + + // append valid EDCSA cases + for _, curve := range []elliptic.Curve{elliptic.P256(), elliptic.P384(), elliptic.P521()} { + ecdsaRoot := testhelper.GetECRootCertificate() + priv, _ := ecdsa.GenerateKey(curve, rand.Reader) + bitSize := priv.Params().BitSize + + certTuple := testhelper.GetECDSACertTupleWithPK( + priv, + "Test EC_"+strconv.Itoa(bitSize), + &ecdsaRoot, + ) + tests = append(tests, testCase{ + name: "EC " + strconv.Itoa(bitSize), + cert: certTuple.Cert, + expect: KeySpec{ + Type: KeyTypeEC, + Size: bitSize, + }, + expectErr: false, + }) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + keySpec, err := ExtractKeySpec(tt.cert) + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(keySpec, tt.expect) { + t.Errorf("expect %+v, got %+v", tt.expect, keySpec) + } + }) + } +} + +func TestSignatureAlgorithm(t *testing.T) { + tests := []struct { + name string + keySpec KeySpec + expect Algorithm + }{ + { + name: "EC 256", + keySpec: KeySpec{ + Type: KeyTypeEC, + Size: 256, + }, + expect: AlgorithmES256, + }, + { + name: "EC 384", + keySpec: KeySpec{ + Type: KeyTypeEC, + Size: 384, + }, + expect: AlgorithmES384, + }, + { + name: "EC 521", + keySpec: KeySpec{ + Type: KeyTypeEC, + Size: 521, + }, + expect: AlgorithmES512, + }, + { + name: "RSA 2048", + keySpec: KeySpec{ + Type: KeyTypeRSA, + Size: 2048, + }, + expect: AlgorithmPS256, + }, + { + name: "RSA 3072", + keySpec: KeySpec{ + Type: KeyTypeRSA, + Size: 3072, + }, + expect: AlgorithmPS384, + }, + { + name: "RSA 4096", + keySpec: KeySpec{ + Type: KeyTypeRSA, + Size: 4096, + }, + expect: AlgorithmPS512, + }, + { + name: "Unsupported key spec", + keySpec: KeySpec{ + Type: 0, + Size: 0, + }, + expect: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + alg := tt.keySpec.SignatureAlgorithm() + if alg != tt.expect { + t.Errorf("unexpected signature algorithm: %v, expect: %v", alg, tt.expect) + } + }) + } +} diff --git a/signature/envelope_test.go b/signature/envelope_test.go new file mode 100644 index 00000000..a2e9c1e9 --- /dev/null +++ b/signature/envelope_test.go @@ -0,0 +1,199 @@ +package signature + +import ( + "reflect" + "testing" +) + +// mock an envelope that implements signature.Envelope. +type testEnvelope struct { +} + +// Sign implements Sign of signature.Envelope. +func (e testEnvelope) Sign(req *SignRequest) ([]byte, error) { + return nil, nil +} + +// Verify implements Verify of signature.Envelope. +func (e testEnvelope) Verify() (*Payload, *SignerInfo, error) { + return nil, nil, nil +} + +// Payload implements Payload of signature.Envelope. +func (e testEnvelope) Payload() (*Payload, error) { + return nil, nil +} + +// SignerInfo implements SignerInfo of signature.Envelope. +func (e testEnvelope) SignerInfo() (*SignerInfo, error) { + return nil, nil +} + +var ( + testNewFunc = func() Envelope { + return testEnvelope{} + } + testParseFunc = func([]byte) (Envelope, error) { + return testEnvelope{}, nil + } +) + +func TestRegisterEnvelopeType(t *testing.T) { + tests := []struct { + name string + mediaType string + newFunc NewEnvelopeFunc + parseFunc ParseEnvelopeFunc + expectErr bool + }{ + { + name: "nil newFunc", + mediaType: testMediaType, + newFunc: nil, + parseFunc: testParseFunc, + expectErr: true, + }, + { + name: "nil newParseFunc", + mediaType: testMediaType, + newFunc: testNewFunc, + parseFunc: nil, + expectErr: true, + }, + { + name: "valid funcs", + mediaType: testMediaType, + newFunc: testNewFunc, + parseFunc: testParseFunc, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := RegisterEnvelopeType(tt.mediaType, tt.newFunc, tt.parseFunc) + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + }) + } +} + +func TestRegisteredEnvelopeTypes(t *testing.T) { + tests := []struct { + name string + envelopeFuncs map[string]envelopeFunc + expect []string + }{ + { + name: "empty map", + envelopeFuncs: make(map[string]envelopeFunc), + expect: []string{}, + }, + { + name: "nonempty map", + envelopeFuncs: map[string]envelopeFunc{ + testMediaType: {}, + }, + expect: []string{testMediaType}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + envelopeFuncs = tt.envelopeFuncs + types := RegisteredEnvelopeTypes() + + if !reflect.DeepEqual(types, tt.expect) { + t.Errorf("got types: %v, expect types; %v", types, tt.expect) + } + }) + } +} + +func TestNewEnvelope(t *testing.T) { + tests := []struct { + name string + mediaType string + envelopeFuncs map[string]envelopeFunc + expect Envelope + expectErr bool + }{ + { + name: "unsupported media type", + mediaType: testMediaType, + envelopeFuncs: make(map[string]envelopeFunc), + expect: nil, + expectErr: true, + }, + { + name: "valid media type", + mediaType: testMediaType, + envelopeFuncs: map[string]envelopeFunc{ + testMediaType: { + newFunc: testNewFunc, + }, + }, + expect: testEnvelope{}, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + envelopeFuncs = tt.envelopeFuncs + envelope, err := NewEnvelope(tt.mediaType) + + if (err != nil) != tt.expectErr { + t.Errorf("got error: %v, expected error? %v", err, tt.expectErr) + } + if envelope != tt.expect { + t.Errorf("got envelope: %v, expected envelope? %v", envelope, tt.expect) + } + }) + } +} + +func TestParseEnvelope(t *testing.T) { + tests := []struct { + name string + mediaType string + envelopeFuncs map[string]envelopeFunc + expect Envelope + expectErr bool + }{ + { + name: "unsupported media type", + mediaType: testMediaType, + envelopeFuncs: make(map[string]envelopeFunc), + expect: nil, + expectErr: true, + }, + { + name: "valid media type", + mediaType: testMediaType, + envelopeFuncs: map[string]envelopeFunc{ + testMediaType: { + parseFunc: testParseFunc, + }, + }, + expect: testEnvelope{}, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + envelopeFuncs = tt.envelopeFuncs + envelope, err := ParseEnvelope(tt.mediaType, nil) + + if (err != nil) != tt.expectErr { + t.Errorf("got error: %v, expected error? %v", err, tt.expectErr) + } + if envelope != tt.expect { + t.Errorf("got envelope: %v, expected envelope? %v", envelope, tt.expect) + } + }) + } +} diff --git a/signature/errors_test.go b/signature/errors_test.go new file mode 100644 index 00000000..c80cdf10 --- /dev/null +++ b/signature/errors_test.go @@ -0,0 +1,202 @@ +package signature + +import ( + "errors" + "fmt" + "testing" +) + +const ( + errMsg = "error msg" + testParam = "test param" + testAlg = "test algorithm" + testMediaType = "test media type" +) + +func TestMalformedSignatureError(t *testing.T) { + tests := []struct { + name string + err *MalformedSignatureError + expect string + }{ + { + name: "err msg set", + err: &MalformedSignatureError{Msg: errMsg}, + expect: errMsg, + }, + { + name: "err msg not set", + err: &MalformedSignatureError{}, + expect: "signature envelope format is malformed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + msg := tt.err.Error() + if msg != tt.expect { + t.Errorf("Expected %s but got %s", tt.expect, msg) + } + }) + } +} + +func TestUnsupportedSigningKeyError(t *testing.T) { + tests := []struct { + name string + err *UnsupportedSigningKeyError + expect string + }{ + { + name: "err msg set", + err: &UnsupportedSigningKeyError{Msg: errMsg}, + expect: errMsg, + }, + { + name: "err msg not set", + err: &UnsupportedSigningKeyError{}, + expect: "signing key is not supported", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + msg := tt.err.Error() + if msg != tt.expect { + t.Errorf("Expected %s but got %s", tt.expect, msg) + } + }) + } +} + +func TestMalformedArgumentError(t *testing.T) { + tests := []struct { + name string + err *MalformedArgumentError + expect string + }{ + { + name: "err set", + err: &MalformedArgumentError{ + Param: testParam, + Err: errors.New(errMsg), + }, + expect: fmt.Sprintf("%q param is malformed. Error: %s", testParam, errMsg), + }, + { + name: "err not set", + err: &MalformedArgumentError{Param: testParam}, + expect: fmt.Sprintf("%q param is malformed", testParam), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + msg := tt.err.Error() + if msg != tt.expect { + t.Errorf("Expected %s but got %s", tt.expect, msg) + } + }) + } +} + +func TestMalformedArgumentError_Unwrap(t *testing.T) { + err := &MalformedArgumentError{ + Param: testParam, + Err: errors.New(errMsg), + } + unwrappedErr := err.Unwrap() + if unwrappedErr.Error() != errMsg { + t.Errorf("Expected %s but got %s", errMsg, unwrappedErr.Error()) + } +} + +func TestMalformedSignRequestError(t *testing.T) { + tests := []struct { + name string + err *MalformedSignRequestError + expect string + }{ + { + name: "err msg set", + err: &MalformedSignRequestError{Msg: errMsg}, + expect: errMsg, + }, + { + name: "err msg not set", + err: &MalformedSignRequestError{}, + expect: "SignRequest is malformed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + msg := tt.err.Error() + if msg != tt.expect { + t.Errorf("Expected %s but got %s", tt.expect, msg) + } + }) + } +} + +func TestSignatureAlgoNotSupportedError(t *testing.T) { + err := &SignatureAlgoNotSupportedError{ + Alg: testAlg, + } + + expectMsg := fmt.Sprintf("signature algorithm %q is not supported", testAlg) + if err.Error() != expectMsg { + t.Errorf("Expected %s but got %s", expectMsg, err.Error()) + } +} + +func TestSignatureIntegrityError(t *testing.T) { + unwrappedErr := errors.New(errMsg) + err := &SignatureIntegrityError{ + Err: unwrappedErr, + } + + expectMsg := fmt.Sprintf("signature is invalid. Error: %s", errMsg) + if err.Error() != expectMsg { + t.Errorf("Expected %s but got %s", expectMsg, err.Error()) + } + if err.Unwrap() != unwrappedErr { + t.Errorf("Expected %v but got %v", unwrappedErr, err.Unwrap()) + } +} + +func TestSignatureNotFoundError(t *testing.T) { + err := &SignatureNotFoundError{} + expectMsg := "signature envelope is not present" + + if err.Error() != expectMsg { + t.Errorf("Expected %v but got %v", expectMsg, err.Error()) + } +} + +func TestSignatureAuthenticityError(t *testing.T) { + err := &SignatureAuthenticityError{} + expectMsg := "signature is not produced by a trusted signer" + + if err.Error() != expectMsg { + t.Errorf("Expected %v but got %v", expectMsg, err.Error()) + } +} + +func TestUnsupportedSignatureFormatError(t *testing.T) { + err := &UnsupportedSignatureFormatError{MediaType: testMediaType} + expectMsg := fmt.Sprintf("signature envelope format with media type %q is not supported", testMediaType) + + if err.Error() != expectMsg { + t.Errorf("Expected %v but got %v", expectMsg, err.Error()) + } +} + +func TestEnvelopeKeyRepeatedError(t *testing.T) { + err := &EnvelopeKeyRepeatedError{Key: errMsg} + expectMsg := fmt.Sprintf("repeated key: %q exists in the envelope.", errMsg) + + if err.Error() != expectMsg { + t.Errorf("Expected %v but got %v", expectMsg, err.Error()) + } +} \ No newline at end of file diff --git a/signature/internal/base/envelope.go b/signature/internal/base/envelope.go index e39165c7..a025b195 100644 --- a/signature/internal/base/envelope.go +++ b/signature/internal/base/envelope.go @@ -212,7 +212,7 @@ func validateCertificateChain(certChain []*x509.Certificate, signTime time.Time, } if signingAlg != expectedAlg { return &signature.MalformedSignatureError{ - Msg: "mismatch between signature algorithm derived from signing certificate and signing algorithm specified", + Msg: fmt.Sprintf("mismatch between signature algorithm derived from signing certificate (%v) and signing algorithm specified (%vs)", signingAlg, expectedAlg), } } diff --git a/signature/internal/base/envelope_test.go b/signature/internal/base/envelope_test.go new file mode 100644 index 00000000..39eab77c --- /dev/null +++ b/signature/internal/base/envelope_test.go @@ -0,0 +1,792 @@ +package base + +import ( + "crypto/x509" + "errors" + "reflect" + "testing" + "time" + + "github.com/notaryproject/notation-core-go/signature" + "github.com/notaryproject/notation-core-go/testhelper" +) + +var ( + errMsg = "error msg" + invalidSigningAgent = "test/1" + validSigningAgent = "test/0" + invalidContentType = "text/plain" + validContentType = "application/vnd.cncf.notary.payload.v1+json" + validContent = "test content" + validBytes = []byte(validContent) + time08_02 time.Time + time08_03 time.Time + timeLayout = "2006-01-02" + validSignerInfo = &signature.SignerInfo{ + Signature: validBytes, + SignatureAlgorithm: signature.AlgorithmPS384, + SignedAttributes: signature.SignedAttributes{ + SigningTime: testhelper.GetRSALeafCertificate().Cert.NotBefore, + Expiry: testhelper.GetECLeafCertificate().Cert.NotAfter, + }, + CertificateChain: []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, + testhelper.GetRSARootCertificate().Cert, + }, + } + validPayload = &signature.Payload{ + ContentType: validContentType, + Content: validBytes, + } + validReq = &signature.SignRequest{ + Payload: signature.Payload{ + ContentType: validContentType, + Content: validBytes, + }, + SigningTime: testhelper.GetRSALeafCertificate().Cert.NotBefore, + Expiry: testhelper.GetRSALeafCertificate().Cert.NotAfter, + Signer: &mockSigner{ + keySpec: signature.KeySpec{ + Type: signature.KeyTypeRSA, + Size: 3072, + }, + certs: []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, + testhelper.GetRSARootCertificate().Cert, + }, + }, + SigningAgent: validSigningAgent, + } + signReq1 = &signature.SignRequest{ + Payload: signature.Payload{ + ContentType: validContentType, + Content: validBytes, + }, + SigningTime: testhelper.GetRSALeafCertificate().Cert.NotBefore, + Expiry: testhelper.GetRSALeafCertificate().Cert.NotAfter, + Signer: &mockSigner{ + keySpec: signature.KeySpec{ + Type: signature.KeyTypeRSA, + Size: 3072, + }, + certs: []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, + testhelper.GetRSARootCertificate().Cert, + }, + }, + SigningAgent: invalidSigningAgent, + } +) + +func init() { + time08_02, _ = time.Parse(timeLayout, "2020-08-02") + time08_03, _ = time.Parse(timeLayout, "2020-08-03") +} + +// Mock an internal envelope that implements signature.Envelope. +type mockEnvelope struct { + payload *signature.Payload + verifiedPayload *signature.Payload + signerInfo *signature.SignerInfo + verifiedSignerInfo *signature.SignerInfo + failVerify bool +} + +// Sign implements Sign of signature.Envelope. +func (e mockEnvelope) Sign(req *signature.SignRequest) ([]byte, error) { + switch req.SigningAgent { + case invalidSigningAgent: + return nil, errors.New(errMsg) + case validSigningAgent: + return validBytes, nil + } + return nil, nil +} + +// Verify implements Verify of signature.Envelope. +func (e mockEnvelope) Verify() (*signature.Payload, *signature.SignerInfo, error) { + if e.failVerify { + return nil, nil, errors.New(errMsg) + } + return e.verifiedPayload, e.verifiedSignerInfo, nil +} + +// Payload implements Payload of signature.Envelope. +func (e mockEnvelope) Payload() (*signature.Payload, error) { + if e.payload == nil { + return nil, errors.New(errMsg) + } + return e.payload, nil +} + +// SignerInfo implements SignerInfo of signature.Envelope. +func (e mockEnvelope) SignerInfo() (*signature.SignerInfo, error) { + if e.signerInfo == nil { + return nil, errors.New(errMsg) + } + return e.signerInfo, nil +} + +// Mock a signer implements signature.Signer. +type mockSigner struct { + certs []*x509.Certificate + keySpec signature.KeySpec +} + +// CertificateChain implements CertificateChain of signature.Signer. +func (s *mockSigner) CertificateChain() ([]*x509.Certificate, error) { + if len(s.certs) == 0 { + return nil, errors.New(errMsg) + } + return s.certs, nil +} + +// Sign implements Sign of signature.Signer. +func (s *mockSigner) Sign(digest []byte) ([]byte, error) { + return nil, nil +} + +// KeySpec implements KeySpec of signature.Signer. +func (s *mockSigner) KeySpec() (signature.KeySpec, error) { + var emptyKeySpec signature.KeySpec + if s.keySpec == emptyKeySpec { + return s.keySpec, errors.New(errMsg) + } + return s.keySpec, nil +} + +func TestSign(t *testing.T) { + tests := []struct { + name string + req *signature.SignRequest + env *Envelope + expect []byte + expectErr bool + }{ + { + name: "invalid request", + req: &signature.SignRequest{ + SigningTime: time08_02, + Expiry: time08_02, + }, + env: &Envelope{ + Raw: nil, + Envelope: mockEnvelope{}, + }, + expect: nil, + expectErr: true, + }, + { + name: "err returned by internal envelope", + req: signReq1, + env: &Envelope{ + Raw: nil, + Envelope: mockEnvelope{}, + }, + expect: nil, + expectErr: true, + }, + { + name: "sign successfully", + req: validReq, + env: &Envelope{ + Raw: nil, + Envelope: mockEnvelope{}, + }, + expect: validBytes, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sig, err := tt.env.Sign(tt.req) + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(sig, tt.expect) { + t.Errorf("expect %+v, got %+v", tt.expect, sig) + } + }) + } +} + +func TestVerify(t *testing.T) { + tests := []struct { + name string + env *Envelope + expectPayload *signature.Payload + expectSignerInfo *signature.SignerInfo + expectErr bool + }{ + { + name: "empty raw", + env: &Envelope{}, + expectPayload: nil, + expectSignerInfo: nil, + expectErr: true, + }, + { + name: "invalid payload", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{}, + }, + expectPayload: nil, + expectSignerInfo: nil, + expectErr: true, + }, + { + name: "invalid payload", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{}, + }, + expectPayload: nil, + expectSignerInfo: nil, + expectErr: true, + }, + { + name: "err returned by internal envelope", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + failVerify: true, + payload: validPayload, + }, + }, + expectPayload: nil, + expectSignerInfo: nil, + expectErr: true, + }, + { + name: "payload validation failed after internal envelope verfication", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + failVerify: false, + payload: validPayload, + verifiedPayload: &signature.Payload{}, + }, + }, + expectPayload: nil, + expectSignerInfo: nil, + expectErr: true, + }, + { + name: "signerInfo validation failed after internal envelope verfication", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + failVerify: false, + payload: validPayload, + verifiedPayload: validPayload, + verifiedSignerInfo: &signature.SignerInfo{}, + }, + }, + expectPayload: nil, + expectSignerInfo: nil, + expectErr: true, + }, + { + name: "verify successfully", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + failVerify: false, + payload: validPayload, + verifiedPayload: validPayload, + verifiedSignerInfo: validSignerInfo, + }, + }, + expectPayload: validPayload, + expectSignerInfo: validSignerInfo, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + payload, signerInfo, err := tt.env.Verify() + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(payload, tt.expectPayload) { + t.Errorf("expect payload: %+v, got %+v", tt.expectPayload, payload) + } + if !reflect.DeepEqual(signerInfo, tt.expectSignerInfo) { + t.Errorf("expect signerInfo: %+v, got %+v", tt.expectSignerInfo, signerInfo) + } + }) + } +} + +func TestPayload(t *testing.T) { + tests := []struct { + name string + env *Envelope + expect *signature.Payload + expectErr bool + }{ + { + name: "empty raw", + env: &Envelope{}, + expect: nil, + expectErr: true, + }, + { + name: "err returned by internal envelope", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{}, + }, + expect: nil, + expectErr: true, + }, + { + name: "invalid payload", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + payload: &signature.Payload{ + ContentType: invalidContentType, + }, + }, + }, + expect: nil, + expectErr: true, + }, + { + name: "valid payload", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + payload: validPayload, + }, + }, + expect: validPayload, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + payload, err := tt.env.Payload() + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(payload, tt.expect) { + t.Errorf("expect %+v, got %+v", tt.expect, payload) + } + }) + } +} + +func TestSignerInfo(t *testing.T) { + tests := []struct { + name string + env *Envelope + expect *signature.SignerInfo + expectErr bool + }{ + { + name: "empty raw", + env: &Envelope{}, + expect: nil, + expectErr: true, + }, + { + name: "err returned by internal envelope", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{}, + }, + expect: nil, + expectErr: true, + }, + { + name: "invalid signerInfo", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + signerInfo: &signature.SignerInfo{}, + }, + }, + expect: nil, + expectErr: true, + }, + { + name: "valid signerInfo", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + signerInfo: validSignerInfo, + }, + }, + expect: validSignerInfo, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + signerInfo, err := tt.env.SignerInfo() + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(signerInfo, tt.expect) { + t.Errorf("expect %+v, got %+v", tt.expect, signerInfo) + } + }) + } +} + +func TestEnvelopeValidatePayload(t *testing.T) { + tests := []struct { + name string + env *Envelope + expectErr bool + }{ + { + name: "err returned by internal payload call", + env: &Envelope{ + Envelope: mockEnvelope{}, + }, + expectErr: true, + }, + { + name: "valid payload", + env: &Envelope{ + Envelope: mockEnvelope{ + payload: &signature.Payload{ + ContentType: validContentType, + Content: validBytes, + }, + }, + }, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.env.validatePayload() + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + }) + } +} + +func TestValidateSignRequest(t *testing.T) { + tests := []struct { + name string + req *signature.SignRequest + expectErr bool + }{ + { + name: "invalid payload", + req: &signature.SignRequest{}, + expectErr: true, + }, + { + name: "invalid signing time", + req: &signature.SignRequest{ + Payload: signature.Payload{ + ContentType: validContentType, + Content: validBytes, + }, + }, + expectErr: true, + }, + { + name: "signer is nil", + req: &signature.SignRequest{ + Payload: signature.Payload{ + ContentType: validContentType, + Content: validBytes, + }, + SigningTime: time08_02, + Expiry: time08_03, + }, + expectErr: true, + }, + { + name: "empty certificates", + req: &signature.SignRequest{ + Payload: signature.Payload{ + ContentType: validContentType, + Content: validBytes, + }, + SigningTime: time08_02, + Expiry: time08_03, + Signer: &mockSigner{}, + }, + expectErr: true, + }, + { + name: "keySpec is empty", + req: &signature.SignRequest{ + Payload: signature.Payload{ + ContentType: validContentType, + Content: validBytes, + }, + SigningTime: time08_02, + Expiry: time08_03, + Signer: &mockSigner{ + certs: []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, + testhelper.GetRSARootCertificate().Cert, + }, + keySpec: signature.KeySpec{}, + }, + }, + expectErr: true, + }, + { + name: "valid request", + req: validReq, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateSignRequest(tt.req) + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + }) + } +} + +func TestValidateSignerInfo(t *testing.T) { + tests := []struct { + name string + info *signature.SignerInfo + expectErr bool + }{ + { + name: "empty signature", + info: &signature.SignerInfo{}, + expectErr: true, + }, + { + name: "missing signature algorithm", + info: &signature.SignerInfo{ + Signature: validBytes, + }, + expectErr: true, + }, + { + name: "invalid signing time", + info: &signature.SignerInfo{ + Signature: validBytes, + SignatureAlgorithm: signature.AlgorithmPS256, + }, + expectErr: true, + }, + { + name: "valid signerInfo", + info: validSignerInfo, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateSignerInfo(tt.info) + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + }) + } +} + +func TestValidateSigningTime(t *testing.T) { + tests := []struct { + name string + signingTime time.Time + expireTime time.Time + expectErr bool + }{ + { + name: "zero signing time", + signingTime: time.Time{}, + expireTime: time.Now(), + expectErr: true, + }, + { + name: "no expire time", + signingTime: time.Now(), + expireTime: time.Time{}, + expectErr: false, + }, + { + name: "expireTime set but invalid", + signingTime: time08_03, + expireTime: time08_02, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateSigningTime(tt.signingTime, tt.expireTime) + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + }) + } +} + +func TestValidatePayload(t *testing.T) { + tests := []struct { + name string + payload *signature.Payload + expectErr bool + }{ + { + name: "invalid payload content type", + payload: &signature.Payload{ + ContentType: invalidContentType, + }, + expectErr: true, + }, + { + name: "payload content is empty", + payload: &signature.Payload{ + ContentType: validContentType, + Content: []byte{}, + }, + expectErr: true, + }, + { + name: "valid payload", + payload: validPayload, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validatePayload(tt.payload) + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + }) + } +} + +func TestValidateCertificateChain(t *testing.T) { + tests := []struct { + name string + certs []*x509.Certificate + signTime time.Time + alg signature.Algorithm + expectErr bool + }{ + { + name: "empty certs", + certs: []*x509.Certificate{}, + signTime: time.Now(), + alg: signature.AlgorithmES256, + expectErr: true, + }, + { + name: "invalid certificates", + certs: []*x509.Certificate{ + testhelper.GetECLeafCertificate().Cert, + }, + signTime: time.Now(), + alg: signature.AlgorithmES256, + expectErr: true, + }, + { + name: "unsupported algorithm", + certs: []*x509.Certificate{ + testhelper.GetED25519LeafCertificate().Cert, + testhelper.GetED25519RootCertificate().Cert, + }, + signTime: testhelper.GetED25519LeafCertificate().Cert.NotBefore, + alg: signature.AlgorithmES256, + expectErr: true, + }, + { + name: "unmacthed signing algorithm", + certs: []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, + testhelper.GetRSARootCertificate().Cert, + }, + signTime: testhelper.GetRSALeafCertificate().Cert.NotBefore, + alg: signature.AlgorithmPS256, + expectErr: true, + }, + { + name: "valid certificate chain", + certs: []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, + testhelper.GetRSARootCertificate().Cert, + }, + signTime: testhelper.GetRSALeafCertificate().Cert.NotBefore, + alg: signature.AlgorithmPS384, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateCertificateChain(tt.certs, tt.signTime, tt.alg) + + if (err != nil) != tt.expectErr { + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + } + }) + } +} + +func TestGetSignatureAlgorithm(t *testing.T) { + tests := []struct { + name string + cert *x509.Certificate + expect signature.Algorithm + expectErr bool + }{ + { + name: "unsupported cert", + cert: testhelper.GetUnsupportedCertificate().Cert, + expect: 0, + expectErr: true, + }, + { + name: "valid cert", + cert: testhelper.GetRSALeafCertificate().Cert, + expect: signature.AlgorithmPS384, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + alg, err := getSignatureAlgorithm(tt.cert) + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(alg, tt.expect) { + t.Errorf("expect %+v, got %+v", tt.expect, alg) + } + }) + } +} diff --git a/signature/signer_test.go b/signature/signer_test.go new file mode 100644 index 00000000..27d5e586 --- /dev/null +++ b/signature/signer_test.go @@ -0,0 +1,220 @@ +package signature + +import ( + "crypto" + "crypto/ed25519" + "crypto/x509" + "reflect" + "testing" + + "github.com/notaryproject/notation-core-go/testhelper" +) + +func TestNewLocalSigner(t *testing.T) { + tests := []struct { + name string + certs []*x509.Certificate + key crypto.PrivateKey + expect LocalSigner + expectErr bool + }{ + { + name: "empty certs", + certs: make([]*x509.Certificate, 0), + key: nil, + expect: nil, + expectErr: true, + }, + { + name: "unsupported leaf cert", + certs: []*x509.Certificate{ + {PublicKey: ed25519.PublicKey{}}, + }, + key: nil, + expect: nil, + expectErr: true, + }, + { + name: "keys not match", + certs: []*x509.Certificate{ + testhelper.GetECLeafCertificate().Cert, + }, + key: testhelper.GetRSARootCertificate().PrivateKey, + expect: nil, + expectErr: true, + }, + { + name: "keys not match", + certs: []*x509.Certificate{ + testhelper.GetRSARootCertificate().Cert, + }, + key: testhelper.GetECLeafCertificate().PrivateKey, + expect: nil, + expectErr: true, + }, + { + name: "RSA keys match", + certs: []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, + }, + key: testhelper.GetRSALeafCertificate().PrivateKey, + expect: &signer{ + keySpec: KeySpec{ + Type: KeyTypeRSA, + Size: 3072, + }, + key: testhelper.GetRSALeafCertificate().PrivateKey, + certs: []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, + }, + }, + expectErr: false, + }, + { + name: "EC keys match", + certs: []*x509.Certificate{ + testhelper.GetECLeafCertificate().Cert, + }, + key: testhelper.GetECLeafCertificate().PrivateKey, + expect: &signer{ + keySpec: KeySpec{ + Type: KeyTypeEC, + Size: 384, + }, + key: testhelper.GetECLeafCertificate().PrivateKey, + certs: []*x509.Certificate{ + testhelper.GetECLeafCertificate().Cert, + }, + }, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + signer, err := NewLocalSigner(tt.certs, tt.key) + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(signer, tt.expect) { + t.Errorf("expect %+v, got %+v", tt.expect, signer) + } + }) + } +} + +func TestSign(t *testing.T) { + signer := &signer{} + + _, err := signer.Sign(make([]byte, 0)) + if err == nil { + t.Errorf("expect error but got nil") + } +} + +func TestCertificateChain(t *testing.T) { + expectCerts := []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, + } + signer := &signer{certs: expectCerts} + + certs, err := signer.CertificateChain() + + if err != nil { + t.Errorf("expect no error but got %v", err) + } + if !reflect.DeepEqual(certs, expectCerts) { + t.Errorf("expect certs %+v, got %+v", expectCerts, certs) + } +} + +func TestKeySpec(t *testing.T) { + expectKeySpec := KeySpec{ + Type: KeyTypeRSA, + Size: 256, + } + signer := &signer{keySpec: expectKeySpec} + + keySpec, err := signer.KeySpec() + + if err != nil { + t.Errorf("expect no error but got %v", err) + } + if !reflect.DeepEqual(keySpec, expectKeySpec) { + t.Errorf("expect keySpec %+v, got %+v", expectKeySpec, keySpec) + } +} + +func TestPrivateKey(t *testing.T) { + expectKey := testhelper.GetRSALeafCertificate().PrivateKey + signer := &signer{key: expectKey} + + key := signer.PrivateKey() + + if !reflect.DeepEqual(key, expectKey) { + t.Errorf("expect key %+v, got %+v", expectKey, key) + } +} + +func TestVerifyAuthenticity(t *testing.T) { + tests := []struct { + name string + signerInfo *SignerInfo + certs []*x509.Certificate + expect *x509.Certificate + expectErr bool + }{ + { + name: "empty certs", + signerInfo: nil, + certs: make([]*x509.Certificate, 0), + expect: nil, + expectErr: true, + }, + { + name: "nil signerInfo", + signerInfo: nil, + certs: []*x509.Certificate{ + testhelper.GetECLeafCertificate().Cert, + }, + expect: nil, + expectErr: true, + }, + { + name: "no cert matches", + signerInfo: &SignerInfo{}, + certs: []*x509.Certificate{ + testhelper.GetECLeafCertificate().Cert, + }, + expect: nil, + expectErr: true, + }, + { + name: "cert matches", + signerInfo: &SignerInfo{ + CertificateChain: []*x509.Certificate{ + testhelper.GetECLeafCertificate().Cert, + }, + }, + certs: []*x509.Certificate{ + testhelper.GetECLeafCertificate().Cert, + }, + expect: testhelper.GetECLeafCertificate().Cert, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cert, err := VerifyAuthenticity(tt.signerInfo, tt.certs) + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(cert, tt.expect) { + t.Errorf("expect cert %+v, got %+v", tt.expect, cert) + } + }) + } +} diff --git a/testhelper/certificatetest.go b/testhelper/certificatetest.go index 6b88449f..23584157 100644 --- a/testhelper/certificatetest.go +++ b/testhelper/certificatetest.go @@ -4,6 +4,7 @@ package testhelper import ( "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" @@ -14,11 +15,14 @@ import ( ) var ( - rsaRoot RSACertTuple - rsaLeaf RSACertTuple - ecdsaRoot ECCertTuple - ecdsaLeaf ECCertTuple - unsupported RSACertTuple + rsaRoot RSACertTuple + rsaLeaf RSACertTuple + ecdsaRoot ECCertTuple + ecdsaLeaf ECCertTuple + unsupportedEcdsaRoot ECCertTuple + ed25519Leaf ED25519CertTuple + ed25519Root ED25519CertTuple + unsupported RSACertTuple ) type RSACertTuple struct { @@ -31,6 +35,11 @@ type ECCertTuple struct { PrivateKey *ecdsa.PrivateKey } +type ED25519CertTuple struct { + Cert *x509.Certificate + PrivateKey *ed25519.PrivateKey +} + // init runs before any other part of this package. func init() { setupCertificates() @@ -56,17 +65,42 @@ func GetECLeafCertificate() ECCertTuple { return ecdsaLeaf } +// GetED25519RootCertificate returns root certificate signed using ED25519 algorithm +func GetED25519RootCertificate() ED25519CertTuple { + return ed25519Root +} + +// GetED25519LeafCertificate returns leaf certificate signed using ED25519 algorithm +func GetED25519LeafCertificate() ED25519CertTuple { + return ed25519Leaf +} + // GetUnsupportedCertificate returns certificate signed using RSA algorithm with key size of 1024 bits // which is not supported by notary. func GetUnsupportedCertificate() RSACertTuple { return unsupported } +// GetUnsupportedRSACert returns certificate signed using RSA algorithm with key +// size of 1024 bits which is not supported by notary. +func GetUnsupportedRSACert() RSACertTuple { + return unsupported +} + +// GetUnsupportedECCert returns certificate signed using EC algorithm with P-224 +// curve which is not supported by notary. +func GetUnsupportedECCert() ECCertTuple { + return unsupportedEcdsaRoot +} + func setupCertificates() { rsaRoot = getCertTuple("Notation Test Root", nil) rsaLeaf = getCertTuple("Notation Test Leaf Cert", &rsaRoot) ecdsaRoot = getECCertTuple("Notation Test Root2", nil) ecdsaLeaf = getECCertTuple("Notation Test Leaf Cert", &ecdsaRoot) + unsupportedEcdsaRoot = getECCertTupleWithCurve("Notation Test Invalid ECDSA Cert", nil, elliptic.P224()) + ed25519Root = getED25519CertTutple("Notation Test ED25519 root", nil) + ed25519Leaf = getED25519CertTutple("Notation Test ED25519 leaf", &ed25519Root) // This will be flagged by the static code analyzer as 'Use of a weak cryptographic key' but its intentional // and is used only for testing. @@ -79,11 +113,21 @@ func getCertTuple(cn string, issuer *RSACertTuple) RSACertTuple { return GetRSACertTupleWithPK(pk, cn, issuer) } +func getECCertTupleWithCurve(cn string, issuer *ECCertTuple, curve elliptic.Curve) ECCertTuple { + k, _ := ecdsa.GenerateKey(curve, rand.Reader) + return GetECDSACertTupleWithPK(k, cn, issuer) +} + func getECCertTuple(cn string, issuer *ECCertTuple) ECCertTuple { k, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) return GetECDSACertTupleWithPK(k, cn, issuer) } +func getED25519CertTutple(cn string, issuer *ED25519CertTuple) ED25519CertTuple { + _, priv, _ := ed25519.GenerateKey(rand.Reader) + return GetED25519CertTupleWithPK(&priv, cn, issuer) +} + func GetRSACertTupleWithPK(privKey *rsa.PrivateKey, cn string, issuer *RSACertTuple) RSACertTuple { template := getCertTemplate(issuer == nil, cn) @@ -118,6 +162,23 @@ func GetECDSACertTupleWithPK(privKey *ecdsa.PrivateKey, cn string, issuer *ECCer } } +func GetED25519CertTupleWithPK(privKey *ed25519.PrivateKey, cn string, issuer *ED25519CertTuple) ED25519CertTuple { + template := getCertTemplate(issuer == nil, cn) + + var certBytes []byte + if issuer != nil { + certBytes, _ = x509.CreateCertificate(rand.Reader, template, issuer.Cert, privKey.Public(), issuer.PrivateKey) + } else { + certBytes, _ = x509.CreateCertificate(rand.Reader, template, template, privKey.Public(), privKey) + } + + cert, _ := x509.ParseCertificate(certBytes) + return ED25519CertTuple{ + Cert: cert, + PrivateKey: privKey, + } +} + func getCertTemplate(isRoot bool, cn string) *x509.Certificate { template := &x509.Certificate{ Subject: pkix.Name{