From 84b7579bbee49d517b611aca8a3d9a599634c807 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Fri, 9 Sep 2022 06:16:25 +0000 Subject: [PATCH] refactor: refactor envelope and signer to support cose Signed-off-by: Binbin Li --- signature/algorithm.go | 109 ++++ signature/algorithm_test.go | 231 ++++++++ signature/envelope.go | 92 +++ signature/envelope_test.go | 194 +++++++ signature/errors.go | 97 +++- signature/errors_test.go | 144 ++++- signature/internal/base/envelope.go | 213 +++++++ signature/internal/base/envelope_test.go | 698 +++++++++++++++++++++++ signature/jws.go | 28 +- signature/jwt.go | 14 +- signature/signer.go | 316 +++------- signature/signer_test.go | 619 ++++++-------------- signature/types.go | 208 ++++--- signature/utils.go | 40 +- testhelper/certificatetest.go | 59 +- 15 files changed, 2202 insertions(+), 860 deletions(-) create mode 100644 signature/algorithm.go create mode 100644 signature/algorithm_test.go create mode 100644 signature/envelope.go create mode 100644 signature/envelope_test.go create mode 100644 signature/internal/base/envelope.go create mode 100644 signature/internal/base/envelope_test.go diff --git a/signature/algorithm.go b/signature/algorithm.go new file mode 100644 index 00000000..32ee7c17 --- /dev/null +++ b/signature/algorithm.go @@ -0,0 +1,109 @@ +package signature + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "fmt" +) + +// Algorithm defines the signature algorithm. +type Algorithm int + +// Signature algorithms supported by this library. +// +// Reference: https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection +const ( + AlgorithmPS256 Algorithm = 1 + iota // RSASSA-PSS with SHA-256 + AlgorithmPS384 // RSASSA-PSS with SHA-384 + AlgorithmPS512 // RSASSA-PSS with SHA-512 + AlgorithmES256 // ECDSA on secp256r1 with SHA-256 + AlgorithmES384 // ECDSA on secp384r1 with SHA-384 + AlgorithmES512 // ECDSA on secp521r1 with SHA-512 +) + +// KeyType defines the key type. +type KeyType int + +const ( + KeyTypeRSA KeyType = 1 + iota // KeyType RSA + KeyTypeEC // KeyType EC +) + +// KeySpec defines a key type and size. +type KeySpec struct { + Type KeyType + Size int +} + +// Hash returns the hash function of the algorithm. +func (alg Algorithm) Hash() crypto.Hash { + switch alg { + case AlgorithmPS256, AlgorithmES256: + return crypto.SHA256 + case AlgorithmPS384, AlgorithmES384: + return crypto.SHA384 + case AlgorithmPS512, AlgorithmES512: + return crypto.SHA512 + } + return 0 +} + +// ExtractKeySpec extracts KeySpec from the signing certificate. +func ExtractKeySpec(signingCert *x509.Certificate) (KeySpec, error) { + switch key := signingCert.PublicKey.(type) { + case *rsa.PublicKey: + switch bitSize := key.Size() << 3; bitSize { + case 2048, 3072, 4096: + return KeySpec{ + Type: KeyTypeRSA, + Size: bitSize, + }, nil + default: + return KeySpec{}, &UnsupportedSigningKeyError{ + Msg: fmt.Sprintf("rsa key size %d is not supported", bitSize), + } + } + case *ecdsa.PublicKey: + switch bitSize := key.Curve.Params().BitSize; bitSize { + case 256, 384, 521: + return KeySpec{ + Type: KeyTypeEC, + Size: bitSize, + }, nil + default: + return KeySpec{}, &UnsupportedSigningKeyError{ + Msg: fmt.Sprintf("ecdsa key size %d is not supported", bitSize), + } + } + } + return KeySpec{}, &UnsupportedSigningKeyError{ + Msg: "invalid public key type", + } +} + +// SignatureAlgorithm returns the signing algorithm associated with the KeySpec. +func (k KeySpec) SignatureAlgorithm() Algorithm { + switch k.Type { + case KeyTypeEC: + switch k.Size { + case 256: + return AlgorithmES256 + case 384: + return AlgorithmES384 + case 521: + return AlgorithmES512 + } + case KeyTypeRSA: + switch k.Size { + case 2048: + return AlgorithmPS256 + case 3072: + return AlgorithmPS384 + case 4096: + return AlgorithmPS512 + } + } + return 0 +} 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.go b/signature/envelope.go new file mode 100644 index 00000000..eb8ad2e7 --- /dev/null +++ b/signature/envelope.go @@ -0,0 +1,92 @@ +// Package signature provides operations for types that implement +// signature.Envelope or signature.Signer. +// +// An Envelope is a structure that creates and verifies a signature using the +// specified signing algorithm with required validation. To register a new +// envelope, call RegisterEnvelopeType first during the initialization. +// +// A Signer is a structure used to sign payload generated after signature +// envelope created. The underlying signing logic is provided by the underlying +// local crypto library or the external signing plugin. +package signature + +import "fmt" + +// Envelope provides functions to basic functions to manipulate signatures. +type Envelope interface { + // Sign generates and sign the envelope according to the sign request. + Sign(req *SignRequest) ([]byte, error) + + // Verify verifies the envelope and returns its enclosed payload and signer + // info. + Verify() (*EnvelopeContent, error) + + // Content returns the payload and signer information of the envelope. + // Content is trusted only after the successful call to `Verify()`. + Content() (*EnvelopeContent, error) +} + +// NewEnvelopeFunc defines a function to create a new Envelope. +type NewEnvelopeFunc func() Envelope + +// ParseEnvelopeFunc defines a function that takes envelope bytes to create +// an Envelope. +type ParseEnvelopeFunc func([]byte) (Envelope, error) + +// envelopeFunc wraps functions to create and parsenew envelopes. +type envelopeFunc struct { + newFunc NewEnvelopeFunc + parseFunc ParseEnvelopeFunc +} + +// envelopeFuncs maps envelope media type to corresponding constructors and +// parsers. +var envelopeFuncs map[string]envelopeFunc + +// RegisterEnvelopeType registers newFunc and parseFunc for the given mediaType. +// Those functions are intended to be called when creating a new envelope. +// It will be called while inializing the built-in envelopes(JWS/COSE). +func RegisterEnvelopeType(mediaType string, newFunc NewEnvelopeFunc, parseFunc ParseEnvelopeFunc) error { + if newFunc == nil || parseFunc == nil { + return fmt.Errorf("required functions not provided") + } + if envelopeFuncs == nil { + envelopeFuncs = make(map[string]envelopeFunc) + } + + envelopeFuncs[mediaType] = envelopeFunc{ + newFunc: newFunc, + parseFunc: parseFunc, + } + return nil +} + +// RegisteredEnvelopeTypes lists registered envelope media types. +func RegisteredEnvelopeTypes() []string { + var types []string + + for envelopeType := range envelopeFuncs { + types = append(types, envelopeType) + } + + return types +} + +// NewEnvelope generates an envelope of given media type. +func NewEnvelope(mediaType string) (Envelope, error) { + envelopeFunc, ok := envelopeFuncs[mediaType] + if !ok { + return nil, &UnsupportedSignatureFormatError{MediaType: mediaType} + } + return envelopeFunc.newFunc(), nil +} + +// ParseEnvelope generates an envelope by given envelope bytes with specified +// media type. +func ParseEnvelope(mediaType string, envelopeBytes []byte) (Envelope, error) { + envelopeFunc, ok := envelopeFuncs[mediaType] + if !ok { + return nil, &UnsupportedSignatureFormatError{MediaType: mediaType} + } + return envelopeFunc.parseFunc(envelopeBytes) +} diff --git a/signature/envelope_test.go b/signature/envelope_test.go new file mode 100644 index 00000000..8a6ef628 --- /dev/null +++ b/signature/envelope_test.go @@ -0,0 +1,194 @@ +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() (*EnvelopeContent, error) { + return nil, nil +} + +// Content implements Content of signature.Envelope. +func (e testEnvelope) Content() (*EnvelopeContent, 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: nil, + }, + { + 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.go b/signature/errors.go index d6ed1e8c..2016c654 100644 --- a/signature/errors.go +++ b/signature/errors.go @@ -2,23 +2,31 @@ package signature import "fmt" -// SignatureIntegrityError is used when the Signature associated is no longer valid. +// SignatureIntegrityError is used when the signature associated is no longer +// valid. type SignatureIntegrityError struct { - err error + Err error } -func (e SignatureIntegrityError) Error() string { - return fmt.Sprintf("signature is invalid. Error: %s", e.err.Error()) +// Error returns the formatted error message. +func (e *SignatureIntegrityError) Error() string { + return fmt.Sprintf("signature is invalid. Error: %s", e.Err.Error()) +} + +// Unwrap unwraps the internal error. +func (e *SignatureIntegrityError) Unwrap() error { + return e.Err } // MalformedSignatureError is used when Signature envelope is malformed. type MalformedSignatureError struct { - msg string + Msg string } +// Error returns the error message or the default message if not provided. func (e MalformedSignatureError) Error() string { - if e.msg != "" { - return e.msg + if e.Msg != "" { + return e.Msg } return "signature envelope format is malformed" @@ -26,11 +34,12 @@ func (e MalformedSignatureError) Error() string { // UnsupportedSignatureFormatError is used when Signature envelope is not supported. type UnsupportedSignatureFormatError struct { - mediaType string + MediaType string } -func (e UnsupportedSignatureFormatError) Error() string { - return fmt.Sprintf("signature envelope format with media type %q is not supported", e.mediaType) +// Error returns the formatted error message. +func (e *UnsupportedSignatureFormatError) Error() string { + return fmt.Sprintf("signature envelope format with media type %q is not supported", e.MediaType) } // SignatureNotFoundError is used when signature envelope is not present. @@ -40,56 +49,84 @@ func (e SignatureNotFoundError) Error() string { return "signature envelope is not present" } -// SignatureAuthenticityError is used when signature is not generated using trusted certificates. +// SignatureAuthenticityError is used when signature is not generated using +// trusted certificates. type SignatureAuthenticityError struct{} -func (e SignatureAuthenticityError) Error() string { +// Error returns the default error message. +func (e *SignatureAuthenticityError) Error() string { return "signature is not produced by a trusted signer" } -// UnsupportedSigningKeyError is used when a signing key is not supported +// UnsupportedSigningKeyError is used when a signing key is not supported. type UnsupportedSigningKeyError struct { - keyType string - keyLength int + Msg string } +// Error returns the error message or the default message if not provided. func (e UnsupportedSigningKeyError) Error() string { - if e.keyType != "" && e.keyLength != 0 { - return fmt.Sprintf("%q signing key of size %d is not supported", e.keyType, e.keyLength) + if e.Msg != "" { + return e.Msg } return "signing key is not supported" } // MalformedArgumentError is used when an argument to a function is malformed. type MalformedArgumentError struct { - param string - err error + Param string + Err error } -func (e MalformedArgumentError) Error() string { - if e.err != nil { - return fmt.Sprintf("%q param is malformed. Error: %s", e.param, e.err.Error()) +// Error returns the error message. +func (e *MalformedArgumentError) Error() string { + if e.Err != nil { + return fmt.Sprintf("%q param is malformed. Error: %s", e.Param, e.Err.Error()) } - return fmt.Sprintf("%q param is malformed", e.param) + return fmt.Sprintf("%q param is malformed", e.Param) +} + +// Unwrap returns the unwrapped error +func (e *MalformedArgumentError) Unwrap() error { + return e.Err } // MalformedSignRequestError is used when SignRequest is malformed. type MalformedSignRequestError struct { - msg string + Msg string } -func (e MalformedSignRequestError) Error() string { - if e.msg != "" { - return e.msg +// Error returns the error message or the default message if not provided. +func (e *MalformedSignRequestError) Error() string { + if e.Msg != "" { + return e.Msg } return "SignRequest is malformed" } // SignatureAlgoNotSupportedError is used when signing algo is not supported. type SignatureAlgoNotSupportedError struct { - alg string + Alg string +} + +// Error returns the formatted error message. +func (e *SignatureAlgoNotSupportedError) Error() string { + return fmt.Sprintf("signature algorithm %q is not supported", e.Alg) +} + +// SignatureEnvelopeNotFoundError is used when signature envelope is not present. +type SignatureEnvelopeNotFoundError struct{} + +// Error returns the default error message. +func (e *SignatureEnvelopeNotFoundError) Error() string { + return "signature envelope is not present" +} + +// EnvelopeKeyRepeatedError is used when repeated key name found in the envelope. +type EnvelopeKeyRepeatedError struct { + Key string } -func (e SignatureAlgoNotSupportedError) Error() string { - return fmt.Sprintf("signature algorithm %q is not supported", e.alg) +// Error returns the formatted error message. +func (e *EnvelopeKeyRepeatedError) Error() string { + return fmt.Sprintf("repeated key: %q exists in the envelope.", e.Key) } diff --git a/signature/errors_test.go b/signature/errors_test.go index 943835a0..b4a55b3c 100644 --- a/signature/errors_test.go +++ b/signature/errors_test.go @@ -1,57 +1,123 @@ package signature import ( + "errors" "fmt" "testing" ) +const ( + errMsg = "error msg" + testParam = "test param" + testAlg = "test algorithm" + testMediaType = "test media type" +) + func TestSignatureIntegrityError(t *testing.T) { - expectedMsg := "signature is invalid. Error: se produjo un error" - validateErrorMsg(SignatureIntegrityError{err: fmt.Errorf("se produjo un error")}, expectedMsg, 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 TestMalformedSignatureError(t *testing.T) { - expectedMsg := "signature envelope format is malformed" - validateErrorMsg(MalformedSignatureError{}, expectedMsg, 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", + }, + } - expectedMsg = "Se produjo un error" - validateErrorMsg(MalformedSignatureError{msg: expectedMsg}, expectedMsg, t) + 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 TestUnsupportedSignatureFormatError(t *testing.T) { - expectedMsg := "signature envelope format with media type \"hola\" is not supported" - validateErrorMsg(UnsupportedSignatureFormatError{mediaType: "hola"}, expectedMsg, 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 TestUnsupportedSigningKeyError(t *testing.T) { - expectedMsg := "signing key is not supported" - validateErrorMsg(UnsupportedSigningKeyError{}, expectedMsg, t) - validateErrorMsg(UnsupportedSigningKeyError{keyType: "RSA"}, expectedMsg, t) - validateErrorMsg(UnsupportedSigningKeyError{keyLength: 1024}, expectedMsg, 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", + }, + } - expectedMsg = "\"RSA\" signing key of size 1024 is not supported" - validateErrorMsg(UnsupportedSigningKeyError{keyType: "RSA", keyLength: 1024}, expectedMsg, t) + 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) { expectedMsg := "\"hola\" param is malformed" - validateErrorMsg(MalformedArgumentError{param: "hola"}, expectedMsg, t) + validateErrorMsg(&MalformedArgumentError{Param: "hola"}, expectedMsg, t) expectedMsg = "\"hola\" param is malformed. Error: se produjo un error" - validateErrorMsg(MalformedArgumentError{param: "hola", err: fmt.Errorf("se produjo un error")}, expectedMsg, t) + validateErrorMsg(&MalformedArgumentError{Param: "hola", Err: fmt.Errorf("se produjo un error")}, expectedMsg, t) } func TestSignatureAlgoNotSupportedError(t *testing.T) { - expectedMsg := "signature algorithm \"hola\" is not supported" - validateErrorMsg(SignatureAlgoNotSupportedError{alg: "hola"}, expectedMsg, 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 TestMalformedSignRequestError(t *testing.T) { expectedMsg := "SignRequest is malformed" - validateErrorMsg(MalformedSignRequestError{}, expectedMsg, t) + validateErrorMsg(&MalformedSignRequestError{}, expectedMsg, t) expectedMsg = "Se produjo un error" - validateErrorMsg(MalformedSignRequestError{msg: expectedMsg}, expectedMsg, t) + validateErrorMsg(&MalformedSignRequestError{Msg: expectedMsg}, expectedMsg, t) } func validateErrorMsg(err error, expectedMsg string, t *testing.T) { @@ -60,3 +126,41 @@ func validateErrorMsg(err error, expectedMsg string, t *testing.T) { t.Errorf("Expected %q but found %q", expectedMsg, foundMsg) } } + +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 TestSignatureEnvelopeNotFoundError(t *testing.T) { + err := &SignatureEnvelopeNotFoundError{} + 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 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()) + } +} diff --git a/signature/internal/base/envelope.go b/signature/internal/base/envelope.go new file mode 100644 index 00000000..b035568e --- /dev/null +++ b/signature/internal/base/envelope.go @@ -0,0 +1,213 @@ +package base + +import ( + "crypto/x509" + "fmt" + "time" + + "github.com/notaryproject/notation-core-go/signature" + nx509 "github.com/notaryproject/notation-core-go/x509" +) + +// Envelope represents a general envelope wrapping a raw signature and envelope +// in specific format. +// Envelope manipulates the common validation shared by internal envelopes. +type Envelope struct { + signature.Envelope // internal envelope in a specific format(e.g. Cose, JWS) + Raw []byte // raw signature +} + +// Sign generates signature in terms of given SignRequest. +// +// Reference: https://github.com/notaryproject/notaryproject/blob/main/signing-and-verification-workflow.md#signing-steps +func (e *Envelope) Sign(req *signature.SignRequest) ([]byte, error) { + // Canonicalize request. + req.SigningTime = req.SigningTime.Truncate(time.Second) + req.Expiry = req.Expiry.Truncate(time.Second) + err := validateSignRequest(req) + if err != nil { + return nil, err + } + + raw, err := e.Envelope.Sign(req) + if err != nil { + return nil, err + } + + // validate certificate chain + content, err := e.Envelope.Content() + if err != nil { + return nil, err + } + signerInfo := content.SignerInfo + + if err := validateCertificateChain( + signerInfo.CertificateChain, + signerInfo.SignedAttributes.SigningTime, + signerInfo.SignatureAlgorithm, + ); err != nil { + return nil, err + } + + e.Raw = raw + return e.Raw, nil +} + +// Verify performs integrity and other signature specification related +// validations. +// It returns envelope content containing the payload to be signed and +// SignerInfo object containing the information about the signature. +// +// Reference: https://github.com/notaryproject/notaryproject/blob/main/trust-store-trust-policy-specification.md#steps +func (e *Envelope) Verify() (*signature.EnvelopeContent, error) { + // validation before the core verify process. + if len(e.Raw) == 0 { + return nil, &signature.MalformedSignatureError{} + } + + // core verify process. + content, err := e.Envelope.Verify() + if err != nil { + return nil, err + } + + // validation after the core verify process. + if err = validateEnvelopeContent(content); err != nil { + return nil, err + } + + return content, nil +} + +// Content returns the validated signature information and payload. +func (e *Envelope) Content() (*signature.EnvelopeContent, error) { + if len(e.Raw) == 0 { + return nil, &signature.MalformedSignatureError{Msg: "raw signature is empty"} + } + + content, err := e.Envelope.Content() + if err != nil { + return nil, err + } + + if err = validateEnvelopeContent(content); err != nil { + return nil, err + } + + return content, nil +} + +// validateSignRequest performs basic set of validations on SignRequest struct. +func validateSignRequest(req *signature.SignRequest) error { + if err := validatePayload(&req.Payload); err != nil { + return err + } + + if err := validateSigningTime(req.SigningTime, req.Expiry); err != nil { + return err + } + + if req.Signer == nil { + return &signature.MalformedSignatureError{Msg: "signer is nil"} + } + + _, err := req.Signer.KeySpec() + return err +} + +// validateEnvelopeContent validates the content which includes signerInfo and +// payload. +func validateEnvelopeContent(content *signature.EnvelopeContent) error { + if err := validatePayload(&content.Payload); err != nil { + return err + } + return validateSignerInfo(&content.SignerInfo) +} + +// validateSignerInfo performs basic set of validations on SignerInfo struct. +func validateSignerInfo(info *signature.SignerInfo) error { + if len(info.Signature) == 0 { + return &signature.MalformedSignatureError{Msg: "signature not present or is empty"} + } + + if info.SignatureAlgorithm == 0 { + return &signature.MalformedSignatureError{Msg: "SignatureAlgorithm is not present"} + } + + signingTime := info.SignedAttributes.SigningTime + if err := validateSigningTime(signingTime, info.SignedAttributes.Expiry); err != nil { + return err + } + + return validateCertificateChain( + info.CertificateChain, + signingTime, + info.SignatureAlgorithm, + ) +} + +// validateSigningTime checks that signing time is within the valid range of +// time duration. +func validateSigningTime(signingTime, expireTime time.Time) error { + if signingTime.IsZero() { + return &signature.MalformedSignatureError{Msg: "signing-time not present"} + } + + if !expireTime.IsZero() && (expireTime.Before(signingTime) || expireTime.Equal(signingTime)) { + return &signature.MalformedSignatureError{Msg: "expiry cannot be equal or before the signing time"} + } + return nil +} + +// validatePayload performs validation of the payload. +func validatePayload(payload *signature.Payload) error { + switch payload.ContentType { + case signature.MediaTypePayloadV1: + if len(payload.Content) == 0 { + return &signature.MalformedSignatureError{Msg: "content not present"} + } + default: + return &signature.MalformedSignatureError{ + Msg: fmt.Sprintf("payload content type: {%s} not supported", payload.ContentType), + } + } + + return nil +} + +// validateCertificateChain performs the validation of the certificate chain. +func validateCertificateChain(certChain []*x509.Certificate, signTime time.Time, expectedAlg signature.Algorithm) error { + if len(certChain) == 0 { + return &signature.MalformedSignatureError{Msg: "certificate-chain not present or is empty"} + } + + err := nx509.ValidateCodeSigningCertChain(certChain, signTime) + if err != nil { + return &signature.MalformedSignatureError{ + Msg: fmt.Sprintf("certificate-chain is invalid, %s", err), + } + } + + signingAlg, err := getSignatureAlgorithm(certChain[0]) + if err != nil { + return &signature.MalformedSignatureError{Msg: err.Error()} + } + if signingAlg != expectedAlg { + return &signature.MalformedSignatureError{ + Msg: fmt.Sprintf("mismatch between signature algorithm derived from signing certificate (%v) and signing algorithm specified (%vs)", signingAlg, expectedAlg), + } + } + + return nil +} + +// getSignatureAlgorithm picks up a recommended signing algorithm for given +// certificate. +func getSignatureAlgorithm(signingCert *x509.Certificate) (signature.Algorithm, error) { + keySpec, err := signature.ExtractKeySpec(signingCert) + if err != nil { + return 0, err + } + + return keySpec.SignatureAlgorithm(), nil +} diff --git a/signature/internal/base/envelope_test.go b/signature/internal/base/envelope_test.go new file mode 100644 index 00000000..54395750 --- /dev/null +++ b/signature/internal/base/envelope_test.go @@ -0,0 +1,698 @@ +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, + } + validEnvelopeContent = &signature.EnvelopeContent{ + SignerInfo: *validSignerInfo, + Payload: *validPayload, + } + 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 + signerInfo *signature.SignerInfo + content *signature.EnvelopeContent + 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.EnvelopeContent, error) { + if e.failVerify { + return nil, errors.New(errMsg) + } + return e.content, nil +} + +// SignerInfo implements SignerInfo of signature.Envelope. +func (e mockEnvelope) Content() (*signature.EnvelopeContent, error) { + if e.content == nil { + return nil, errors.New(errMsg) + } + return e.content, 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(payload []byte) ([]byte, []*x509.Certificate, error) { + return nil, 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: "internal envelope fails to sign", + req: signReq1, + env: &Envelope{ + Raw: nil, + Envelope: mockEnvelope{}, + }, + expect: nil, + expectErr: true, + }, + { + name: "internal envelope fails to get content", + req: validReq, + env: &Envelope{ + Raw: nil, + Envelope: mockEnvelope{}, + }, + expect: nil, + expectErr: true, + }, + { + name: "invalid certificate chain", + req: validReq, + env: &Envelope{ + Raw: nil, + Envelope: mockEnvelope{ + content: &signature.EnvelopeContent{}, + }, + }, + expect: nil, + expectErr: true, + }, + { + name: "successfully signed", + req: validReq, + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + content: &signature.EnvelopeContent{ + SignerInfo: *validSignerInfo, + }, + }, + }, + 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 + expectContent *signature.EnvelopeContent + expectErr bool + }{ + { + name: "empty raw", + env: &Envelope{}, + expectContent: nil, + expectErr: true, + }, + { + name: "err returned by internal envelope", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + failVerify: true, + payload: validPayload, + }, + }, + expectContent: nil, + expectErr: true, + }, + { + name: "payload validation failed after internal envelope verfication", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + content: &signature.EnvelopeContent{ + Payload: signature.Payload{ + ContentType: invalidContentType, + }, + }, + }, + }, + expectContent: nil, + expectErr: true, + }, + { + name: "signerInfo validation failed after internal envelope verfication", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + content: &signature.EnvelopeContent{ + Payload: *validPayload, + SignerInfo: signature.SignerInfo{}, + }, + }, + }, + expectContent: nil, + expectErr: true, + }, + { + name: "verify successfully", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + content: &signature.EnvelopeContent{ + Payload: *validPayload, + SignerInfo: *validSignerInfo, + }, + }, + }, + expectContent: &signature.EnvelopeContent{ + Payload: *validPayload, + SignerInfo: *validSignerInfo, + }, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + content, err := tt.env.Verify() + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(content, tt.expectContent) { + t.Errorf("expect content: %+v, got %+v", tt.expectContent, content) + } + }) + } +} + +func TestContent(t *testing.T) { + tests := []struct { + name string + env *Envelope + expect *signature.EnvelopeContent + 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{ + content: &signature.EnvelopeContent{ + Payload: signature.Payload{ + ContentType: invalidContentType, + }, + }, + }, + }, + expect: nil, + expectErr: true, + }, + { + name: "valid payload and invalid signerInfo", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + content: &signature.EnvelopeContent{ + Payload: *validPayload, + }, + signerInfo: &signature.SignerInfo{}, + }, + }, + expect: nil, + expectErr: true, + }, + { + name: "valid payload and valid signerInfo", + env: &Envelope{ + Raw: validBytes, + Envelope: &mockEnvelope{ + content: &signature.EnvelopeContent{ + Payload: *validPayload, + SignerInfo: *validSignerInfo, + }, + }, + }, + expect: validEnvelopeContent, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + content, err := tt.env.Content() + + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(content, tt.expect) { + t.Errorf("expect %+v, got %+v", tt.expect, content) + } + }) + } +} + +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: "unmatched 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 { + 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.GetUnsupportedRSACert().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/jws.go b/signature/jws.go index 1c4f2849..9e105090 100644 --- a/signature/jws.go +++ b/signature/jws.go @@ -57,12 +57,12 @@ func (jws *jwsEnvelope) validateIntegrity() error { } if len(jws.internalEnv.Header.CertChain) == 0 { - return MalformedSignatureError{msg: "malformed leaf certificate"} + return MalformedSignatureError{Msg: "malformed leaf certificate"} } cert, err := x509.ParseCertificate(jws.internalEnv.Header.CertChain[0]) if err != nil { - return MalformedSignatureError{msg: "malformed leaf certificate"} + return MalformedSignatureError{Msg: "malformed leaf certificate"} } // verify JWT @@ -109,8 +109,8 @@ func (jws *jwsEnvelope) signPayload(req SignRequest) ([]byte, error) { return b, nil } -func (jws *jwsEnvelope) getSignerInfo() (*SignerInfo, error) { - signInfo := SignerInfo{} +func (jws *jwsEnvelope) getSignerInfo() (*EnvelopeContent, error) { + signInfo := EnvelopeContent{} if jws.internalEnv == nil { return nil, SignatureNotFoundError{} } @@ -157,7 +157,7 @@ func (jws *jwsEnvelope) getSignerInfo() (*SignerInfo, error) { func parseProtectedHeaders(encoded string) (*jwsProtectedHeader, error) { rawProtected, err := base64.RawURLEncoding.DecodeString(encoded) if err != nil { - return nil, MalformedSignatureError{msg: fmt.Sprintf("jws envelope protected header can't be decoded: %s", err.Error())} + return nil, MalformedSignatureError{Msg: fmt.Sprintf("jws envelope protected header can't be decoded: %s", err.Error())} } // To Unmarshal JSON with some known(jwsProtectedHeader), and some unknown(jwsProtectedHeader.ExtendedAttributes) field names. @@ -165,10 +165,10 @@ func parseProtectedHeaders(encoded string) (*jwsProtectedHeader, error) { // and removing the keys are already been defined in jwsProtectedHeader. var protected jwsProtectedHeader if err = json.Unmarshal(rawProtected, &protected); err != nil { - return nil, MalformedSignatureError{msg: fmt.Sprintf("jws envelope protected header can't be decoded: %s", err.Error())} + return nil, MalformedSignatureError{Msg: fmt.Sprintf("jws envelope protected header can't be decoded: %s", err.Error())} } if err = json.Unmarshal(rawProtected, &protected.ExtendedAttributes); err != nil { - return nil, MalformedSignatureError{msg: fmt.Sprintf("jws envelope protected header can't be decoded: %s", err.Error())} + return nil, MalformedSignatureError{Msg: fmt.Sprintf("jws envelope protected header can't be decoded: %s", err.Error())} } // delete attributes that are already defined in jwsProtectedHeader. @@ -185,7 +185,7 @@ func parseProtectedHeaders(encoded string) (*jwsProtectedHeader, error) { return &protected, nil } -func populateProtectedHeaders(protectedHdr *jwsProtectedHeader, signInfo *SignerInfo) error { +func populateProtectedHeaders(protectedHdr *jwsProtectedHeader, signInfo *EnvelopeContent) error { err := validateProtectedHeaders(protectedHdr) if err != nil { return err @@ -233,14 +233,14 @@ func validateProtectedHeaders(protectedHdr *jwsProtectedHeader) error { switch protectedHdr.SigningScheme { case SigningSchemeX509: if protectedHdr.AuthenticSigningTime != nil { - return MalformedSignatureError{msg: fmt.Sprintf("%q header must not be present for %s signing scheme", headerKeyAuthenticSigningTime, SigningSchemeX509)} + return MalformedSignatureError{Msg: fmt.Sprintf("%q header must not be present for %s signing scheme", headerKeyAuthenticSigningTime, SigningSchemeX509)} } case SigningSchemeX509SigningAuthority: if protectedHdr.SigningTime != nil { - return MalformedSignatureError{msg: fmt.Sprintf("%q header must not be present for %s signing scheme", headerKeySigningTime, SigningSchemeX509SigningAuthority)} + return MalformedSignatureError{Msg: fmt.Sprintf("%q header must not be present for %s signing scheme", headerKeySigningTime, SigningSchemeX509SigningAuthority)} } if protectedHdr.AuthenticSigningTime == nil { - return MalformedSignatureError{msg: fmt.Sprintf("%q header must be present for %s signing scheme", headerKeyAuthenticSigningTime, SigningSchemeX509)} + return MalformedSignatureError{Msg: fmt.Sprintf("%q header must be present for %s signing scheme", headerKeyAuthenticSigningTime, SigningSchemeX509)} } } @@ -275,7 +275,7 @@ func validateCriticalHeaders(protectedHdr *jwsProtectedHeader) error { delete(mustMarkedCrit, val) } else { if _, ok := protectedHdr.ExtendedAttributes[val]; !ok { - return MalformedSignatureError{msg: fmt.Sprintf("%q header is marked critical but not present", val)} + return MalformedSignatureError{Msg: fmt.Sprintf("%q header is marked critical but not present", val)} } } } @@ -370,7 +370,7 @@ type jwsProtectedHeader struct { Algorithm string `json:"alg"` // Media type of the secured content (the payload). - ContentType PayloadContentType `json:"cty"` + ContentType MediaTypePayloadV1 `json:"cty"` // Lists the headers that implementation MUST understand and process. Critical []string `json:"crit,omitempty"` @@ -442,7 +442,7 @@ func generateJws(compact string, req SignRequest, certs []*x509.Certificate) (*j } // sign the given payload and headers using the given signing method and signature provider -func sign(payload []byte, headers map[string]interface{}, sigPro SignatureProvider) (string, []*x509.Certificate, error) { +func sign(payload []byte, headers map[string]interface{}, sigPro Signer) (string, []*x509.Certificate, error) { jsonPHeaders, err := json.Marshal(headers) if err != nil { return "", nil, fmt.Errorf("failed to encode protected headers: %v", err) diff --git a/signature/jwt.go b/signature/jwt.go index 15f5de73..9036eaba 100644 --- a/signature/jwt.go +++ b/signature/jwt.go @@ -25,14 +25,14 @@ func verifyJWT(tokenString string, key crypto.PublicKey) error { if _, err := parser.Parse(tokenString, func(t *jwt.Token) (interface{}, error) { if t.Method.Alg() != signingMethod.Alg() { - return nil, MalformedSignatureError{msg: fmt.Sprintf("unexpected signing method: %v: require %v", t.Method.Alg(), signingMethod.Alg())} + return nil, MalformedSignatureError{Msg: fmt.Sprintf("unexpected signing method: %v: require %v", t.Method.Alg(), signingMethod.Alg())} } // override default signing method with key-specific method t.Method = signingMethod return key, nil }); err != nil { - return SignatureIntegrityError{err: err} + return &SignatureIntegrityError{Err: err} } return nil } @@ -49,10 +49,12 @@ func getSigningMethod(key crypto.PublicKey) (jwt.SigningMethod, error) { case 512: return jwt.SigningMethodPS512, nil default: - return nil, UnsupportedSigningKeyError{keyType: "rsa", keyLength: key.Size()} + return nil, UnsupportedSigningKeyError{ + Msg: fmt.Sprintf("rsa key size %d is not supported", key.Size()), + } } case *ecdsa.PublicKey: - switch key.Curve.Params().BitSize { + switch bitSize := key.Curve.Params().BitSize; bitSize { case jwt.SigningMethodES256.CurveBits: return jwt.SigningMethodES256, nil case jwt.SigningMethodES384.CurveBits: @@ -60,7 +62,9 @@ func getSigningMethod(key crypto.PublicKey) (jwt.SigningMethod, error) { case jwt.SigningMethodES512.CurveBits: return jwt.SigningMethodES512, nil default: - return nil, UnsupportedSigningKeyError{keyType: "ecdsa", keyLength: key.Curve.Params().BitSize} + return nil, UnsupportedSigningKeyError{ + Msg: fmt.Sprintf("ecdsa key size %d is not supported", bitSize), + } } } return nil, UnsupportedSigningKeyError{} diff --git a/signature/signer.go b/signature/signer.go index 21afa805..e9faa8d9 100644 --- a/signature/signer.go +++ b/signature/signer.go @@ -1,71 +1,43 @@ package signature import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" "crypto/x509" + "errors" "fmt" - "regexp" - "strings" - "time" - - nx509 "github.com/notaryproject/notation-core-go/x509" ) -// SignerInfo represents a parsed signature envelope that is agnostic to signature envelope format. -type SignerInfo struct { - Payload []byte - Signature []byte - - // Signed attributes - PayloadContentType PayloadContentType - SignatureAlgorithm SignatureAlgorithm - SigningScheme SigningScheme - SignedAttributes SignedAttributes +// Signer is used to sign bytes generated after signature envelope created. +type Signer interface { + // Sign signs the payload and returns the raw signature and certificates. + Sign(payload []byte) ([]byte, []*x509.Certificate, error) - // Unsigned attributes - CertificateChain []*x509.Certificate - TimestampSignature []byte - UnsignedAttributes UnsignedAttributes -} - -// SignedAttributes represents signed metadata in the Signature envelope -type SignedAttributes struct { - SigningTime time.Time - Expiry time.Time - VerificationPlugin string - VerificationPluginMinVersion string - ExtendedAttributes []Attribute + // KeySpec returns the key specification. + KeySpec() (KeySpec, error) } -// UnsignedAttributes represents unsigned metadata in the Signature envelope -type UnsignedAttributes struct { - SigningAgent string -} +// LocalSigner is used by built-in signers to sign only. +type LocalSigner interface { + Signer -// SignRequest is used to generate Signature. -type SignRequest struct { - Payload []byte - PayloadContentType PayloadContentType - SignatureProvider SignatureProvider - SigningTime time.Time - Expiry time.Time - ExtendedSignedAttrs []Attribute - SigningAgent string - SigningScheme SigningScheme - VerificationPlugin string - VerificationPluginMinVersion string -} + // CertificateChain returns the certificate chain. + CertificateChain() ([]*x509.Certificate, error) -// Attribute represents metadata in the Signature envelope -type Attribute struct { - Key string - Critical bool - Value interface{} + // PrivateKey returns the private key. + PrivateKey() crypto.PrivateKey } -// SignatureProvider is used to sign bytes generated after creating Signature envelope. -type SignatureProvider interface { - Sign([]byte) ([]byte, []*x509.Certificate, error) - KeySpec() (KeySpec, error) +// localSigner implements LocalSigner interface. +// +// Note that localSigner only holds the signing key, keySpec and certificate +// chain. The underlying signing implementation is provided by the underlying +// crypto library for the specific signature format like go-jwt or go-cose. +type localSigner struct { + keySpec KeySpec + key crypto.PrivateKey + certs []*x509.Certificate } // SignatureEnvelope provides functions to generate signature and verify signature. @@ -79,218 +51,100 @@ type internalSignatureEnvelope interface { // validateIntegrity validates the integrity of given Signature envelope. validateIntegrity() error // getSignerInfo returns the information stored in the Signature envelope and doesn't perform integrity verification. - getSignerInfo() (*SignerInfo, error) + getSignerInfo() (*EnvelopeContent, error) // signPayload created Signature envelope. signPayload(SignRequest) ([]byte, error) } -// Verify performs integrity and other signature specification related validations -// Returns the SignerInfo object containing the information about the signature. -func (s *SignatureEnvelope) Verify() (*SignerInfo, error) { - if len(s.rawSignatureEnvelope) == 0 { - return nil, SignatureNotFoundError{} - } - - integrityError := s.internalEnvelope.validateIntegrity() - if integrityError != nil { - return nil, integrityError - } - - singerInfo, singerInfoErr := s.GetSignerInfo() - if singerInfoErr != nil { - return nil, singerInfoErr - } - - return singerInfo, nil -} - -// Sign generates Signature using given SignRequest. -func (s *SignatureEnvelope) Sign(req SignRequest) ([]byte, error) { - // Sanitize request - req.SigningTime = req.SigningTime.Truncate(time.Second) - req.Expiry = req.Expiry.Truncate(time.Second) - - // validate request - if err := validateSignRequest(req); err != nil { - return nil, err - } - - // perform signature generation - sig, err := s.internalEnvelope.signPayload(req) - if err != nil { - return nil, err - } - - s.rawSignatureEnvelope = sig - return sig, nil -} - -// GetSignerInfo returns information about the Signature envelope -func (s SignatureEnvelope) GetSignerInfo() (*SignerInfo, error) { - if len(s.rawSignatureEnvelope) == 0 { - return nil, SignatureNotFoundError{} +// NewLocalSigner returns a new signer with given certificates and private key. +func NewLocalSigner(certs []*x509.Certificate, key crypto.PrivateKey) (LocalSigner, error) { + if len(certs) == 0 { + return nil, &MalformedArgumentError{ + Param: "certs", + Err: errors.New("empty certs"), + } } - signInfo, err := s.internalEnvelope.getSignerInfo() + keySpec, err := ExtractKeySpec(certs[0]) if err != nil { - return nil, MalformedSignatureError{msg: fmt.Sprintf("signature envelope format is malformed. error: %s", err)} - } - - if err := validateSignerInfo(signInfo); err != nil { return nil, err } - return signInfo, nil -} - -// validateSignerInfo performs basic set of validations on SignerInfo struct. -func validateSignerInfo(info *SignerInfo) error { - if len(info.Signature) == 0 { - return MalformedSignatureError{msg: "signature not present or is empty"} - } - - if info.SignatureAlgorithm == "" { - return MalformedSignRequestError{msg: "SignatureAlgorithm is not present"} - } - - errorFunc := func(s string) error { - return MalformedSignatureError{msg: s} - } - - sAttr := info.SignedAttributes - if err := validate(info.Payload, info.PayloadContentType, sAttr.VerificationPlugin, sAttr.VerificationPluginMinVersion, - sAttr.SigningTime, sAttr.Expiry, info.SigningScheme, errorFunc); err != nil { - return err - } - if err := validateCertificateChain(info.CertificateChain, info.SignedAttributes.SigningTime, info.SignatureAlgorithm, errorFunc); err != nil { - return err + if !isKeyPair(key, certs[0].PublicKey, keySpec) { + return nil, &MalformedArgumentError{ + Param: "key and certs", + Err: errors.New("key not matches certificate"), + } } - return nil + return &localSigner{ + keySpec: keySpec, + key: key, + certs: certs, + }, nil } -// validateSignRequest performs basic set of validations on SignRequest struct. -func validateSignRequest(req SignRequest) error { - errorFunc := func(s string) error { - return MalformedSignRequestError{msg: s} - } - - if err := validate(req.Payload, req.PayloadContentType, req.VerificationPlugin, req.VerificationPluginMinVersion, - req.SigningTime, req.Expiry, req.SigningScheme, errorFunc); err != nil { - return err - } - - if len(req.Payload) == 0 { - return MalformedSignRequestError{msg: "payload not present"} - } - - if req.SignatureProvider == nil { - return MalformedSignRequestError{msg: "SignatureProvider is nil"} +// isKeyPair checks if the private key matches the provided public key. +func isKeyPair(priv crypto.PrivateKey, pub crypto.PublicKey, keySpec KeySpec) bool { + switch keySpec.Type { + case KeyTypeRSA: + privateKey, ok := priv.(*rsa.PrivateKey) + if !ok { + return false + } + return privateKey.PublicKey.Equal(pub) + case KeyTypeEC: + privateKey, ok := priv.(*ecdsa.PrivateKey) + if !ok { + return false + } + return privateKey.PublicKey.Equal(pub) + default: + return false } - - return nil } -func validateCertificateChain(certChain []*x509.Certificate, signTime time.Time, expectedAlg SignatureAlgorithm, f func(string) error) error { - if len(certChain) == 0 { - return f("certificate-chain not present or is empty") - } - - err := nx509.ValidateCodeSigningCertChain(certChain, signTime) - if err != nil { - return f(fmt.Sprintf("certificate-chain is invalid, %s", err)) - } - - resSignAlgo, err := getSignatureAlgorithm(certChain[0]) - if err != nil { - return f(err.Error()) - } - if resSignAlgo != expectedAlg { - return f("mismatch between signature algorithm derived from signing certificate and signing algorithm specified") - } - - return nil +// Sign signs the content and returns the raw signature and certificates. +// This implementation should never be used by built-in signers. +func (s *localSigner) Sign(content []byte) ([]byte, []*x509.Certificate, error) { + return nil, nil, fmt.Errorf("local signer doesn't support sign") } -func validate(payload []byte, payloadCty PayloadContentType, verificationPlugin, verificationPluginVersion string, signTime, expTime time.Time, scheme SigningScheme, f func(string) error) error { - if len(payload) == 0 { - return f("payload not present") - } - - if payloadCty == "" { - return f("payload content type not present or is empty") - } - - if signTime.IsZero() { - return f("signing-time not present") - } - - if !expTime.IsZero() && (expTime.Before(signTime) || expTime.Equal(signTime)) { - return f("expiry cannot be equal or before the signing time") - } - - if scheme == "" { - return f("SigningScheme not present") - } - - if verificationPlugin != "" && strings.TrimSpace(verificationPlugin) == "" { - return MalformedSignRequestError{msg: "VerificationPlugin cannot contain only whitespaces"} - } - - // copied from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string - semVerRegEx := regexp.MustCompile("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$") - if verificationPluginVersion != "" && !semVerRegEx.MatchString(verificationPluginVersion) { - return MalformedSignRequestError{msg: fmt.Sprintf("VerificationPluginMinVersion %q is not valid SemVer", verificationPluginVersion)} - } - - if verificationPlugin == "" && verificationPluginVersion != "" { - return MalformedSignRequestError{msg: "VerificationPluginMinVersion cannot be used without VerificationPlugin"} - } - - return nil +// KeySpec returns the key specification. +func (s *localSigner) KeySpec() (KeySpec, error) { + return s.keySpec, nil } -// NewSignatureEnvelopeFromBytes is used for signature verification workflow -func NewSignatureEnvelopeFromBytes(envelopeBytes []byte, envelopeMediaType SignatureMediaType) (*SignatureEnvelope, error) { - switch envelopeMediaType { - case MediaTypeJWSJson: - internal, err := newJWSEnvelopeFromBytes(envelopeBytes) - if err != nil { - return nil, MalformedArgumentError{"envelopeBytes", err} - } - return &SignatureEnvelope{envelopeBytes, internal}, nil - default: - return nil, UnsupportedSignatureFormatError{mediaType: string(envelopeMediaType)} - } +// CertificateChain returns the certificate chain. +func (s *localSigner) CertificateChain() ([]*x509.Certificate, error) { + return s.certs, nil } -// NewSignatureEnvelope is used for signature generation workflow -func NewSignatureEnvelope(envelopeMediaType SignatureMediaType) (*SignatureEnvelope, error) { - switch envelopeMediaType { - case MediaTypeJWSJson: - return &SignatureEnvelope{internalEnvelope: &jwsEnvelope{}}, nil - default: - return nil, UnsupportedSignatureFormatError{mediaType: string(envelopeMediaType)} - } +// PrivateKey returns the private key. +func (s *localSigner) PrivateKey() crypto.PrivateKey { + return s.key } -// VerifyAuthenticity verifies the certificate chain in the given SignerInfo with one of the trusted certificates -// and returns a certificate that matches with one of the certificates in the SignerInfo. +// VerifyAuthenticity verifies the certificate chain in the given SignerInfo +// with one of the trusted certificates and returns a certificate that matches +// with one of the certificates in the SignerInfo. +// +// Reference: https://github.com/notaryproject/notaryproject/blob/main/trust-store-trust-policy-specification.md#steps func VerifyAuthenticity(signerInfo *SignerInfo, trustedCerts []*x509.Certificate) (*x509.Certificate, error) { if len(trustedCerts) == 0 { - return nil, MalformedArgumentError{param: "trustedCerts"} + return nil, &MalformedArgumentError{Param: "trustedCerts"} } if signerInfo == nil { - return nil, MalformedArgumentError{param: "signerInfo"} + return nil, &MalformedArgumentError{Param: "signerInfo"} } for _, trust := range trustedCerts { - for _, sig := range signerInfo.CertificateChain { - if trust.Equal(sig) { + for _, cert := range signerInfo.CertificateChain { + if trust.Equal(cert) { return trust, nil } } } - return nil, SignatureAuthenticityError{} + return nil, &SignatureAuthenticityError{} } diff --git a/signature/signer_test.go b/signature/signer_test.go index 760ae520..b92c0010 100644 --- a/signature/signer_test.go +++ b/signature/signer_test.go @@ -1,475 +1,226 @@ package signature import ( + "crypto" + "crypto/ed25519" "crypto/x509" - "encoding/base64" - "encoding/json" - "errors" - "fmt" "reflect" - "sort" - "strings" "testing" - "time" "github.com/notaryproject/notation-core-go/testhelper" ) -const ( - TestPayload = "{\"targetArtifact\":{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333\",\"size\":16724,\"annotations\":{\"io.wabbit-networks.buildId\":\"123\"}}}" - TestValidSig = "{\"payload\":\"eyJ0YXJnZXRBcnRpZmFjdCI6eyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLm1hbmlmZXN0LnYxK2pzb24iLCJkaWdlc3QiOiJzaGEyNTY6NzNjODAzOTMwZWEzYmExZTU0YmMyNWMyYmRjNTNlZGQwMjg0YzYyZWQ2NTFmZTdiMDAzNjlkYTUxOWEzYzMzMyIsInNpemUiOjE2NzI0LCJhbm5vdGF0aW9ucyI6eyJpby53YWJiaXQtbmV0d29ya3MuYnVpbGRJZCI6IjEyMyJ9fX0\",\"protected\":\"eyJhbGciOiJQUzM4NCIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSIsInNpZ25lZENyaXRLZXkxIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luIiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luTWluVmVyc2lvbiJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IjoiMjAyMi0wOC0wNVQxMDowMzoxMS0wNzowMCIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdTY2hlbWUiOiJub3RhcnkueDUwOSIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdUaW1lIjoiMjAyMi0wOC0wNFQxMDowMzoxMS0wNzowMCIsImlvLmNuY2Yubm90YXJ5LnZlcmlmaWNhdGlvblBsdWdpbiI6IkhvbGEgUGx1Z2luIiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luTWluVmVyc2lvbiI6IjEuMS4xIiwic2lnbmVkQ3JpdEtleTEiOiJzaWduZWRWYWx1ZTEiLCJzaWduZWRLZXkxIjoic2lnbmVkS2V5MiJ9\",\"header\":{\"x5c\":[\"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\"],\"io.cncf.notary.SigningAgent\":\"NotationUnitTest/1.0.0\"},\"signature\":\"q8PiYCcO4zNMrnWQ3fPqum3MdejUAmMpPf71pgIuy-6YqP55NnMmzTqyuA4n3xcjdXRNJBpaV5JtTMyudJzDqxs28m6t6zsvwjOiY3HMZhPnC3-E9fws728wMy2daoSHMxT9fliAyTYE5BMezxM3Boxsb1UfEpwd00CITeEw3Ufr5BsVlLSIT79crV5CAiLdwqcJLs1bH7qtg4cqnLTCpf2avSSFCQ1d0_myjrF0VKNxzoNJABchEa2E7tHrHSMm2BRcOf6EWZYoLrmF0DWkwGnRlWpWFkD1I83LvaGLr48aUuUeRK9MJJDfF9EpclKo8Ondf36r8p6Br3ycuqw7d8i4uNic9RQE7ng7CfOSB0cOICA7vQnIEkOw1vb9pcTYL5r2Hgk8KszFeXmt1YAzt65A3YPoFAEoTPfhG6Cl2xfubcA5iGqu26VVOh_L0kB1gWQ749x4gNBlWiB9uZPfBxsFHbKN6k3Km9wgWPdHEvo8VwPvFL34_7BkVb9dyfcA\"}" -) - -var ( - TestTamperedSig = strings.Replace(TestValidSig, "6eyJt", "1fX0=", 1) -) - -func TestNewSignatureEnvelopeFromBytesError(t *testing.T) { - _, err := NewSignatureEnvelopeFromBytes([]byte("Malformed"), MediaTypeJWSJson) - if !(err != nil && errors.As(err, new(MalformedArgumentError))) { - t.Errorf("Expected MalformedArgumentError but found %q", reflect.TypeOf(err)) - } -} - -// Tests various scenarios around generating a signature envelope -func TestSign(t *testing.T) { - env, err := NewSignatureEnvelope(MediaTypeJWSJson) - if err != nil { - t.Fatalf("NewSignatureEnvelope() error = %v", err) - } - - for _, scheme := range []SigningScheme{SigningSchemeX509, SigningSchemeX509SigningAuthority} { - t.Run(fmt.Sprintf("with %s scheme when all arguments are present", scheme), func(t *testing.T) { - req := newSignRequest(scheme) - verifySignWithRequest(env, req, t) - }) - - t.Run(fmt.Sprintf("with %s scheme when minimal arguments are present", scheme), func(t *testing.T) { - lSigner, _ := NewLocalSignatureProvider(getSigningCerts(), testhelper.GetRSALeafCertificate().PrivateKey) - req := SignRequest{ - Payload: []byte(TestPayload), - PayloadContentType: PayloadContentTypeV1, - SigningScheme: scheme, - SigningTime: time.Now(), - SignatureProvider: lSigner, +func TestNewLocalSigner(t *testing.T) { + tests := []struct { + name string + certs []*x509.Certificate + key crypto.PrivateKey + expect LocalSigner + expectErr bool + }{ + { + name: "empty certs", + certs: []*x509.Certificate{}, + 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: &localSigner{ + 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: &localSigner{ + 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) } - verifySignWithRequest(env, req, t) - }) - - t.Run(fmt.Sprintf("with %s scheme when expiry is not present", scheme), func(t *testing.T) { - req := newSignRequest(scheme) - req.Expiry = time.Time{} - verifySignWithRequest(env, req, t) - }) - - t.Run(fmt.Sprintf("with %s scheme when signing agent is not present", scheme), func(t *testing.T) { - req := newSignRequest(scheme) - req.SigningAgent = "" - verifySignWithRequest(env, req, t) - }) - - t.Run(fmt.Sprintf("with %s scheme when extended attributes are not present", scheme), func(t *testing.T) { - req := newSignRequest(scheme) - req.ExtendedSignedAttrs = nil - verifySignWithRequest(env, req, t) - }) - - t.Run(fmt.Sprintf("with %s scheme when verification plugin is not present", scheme), func(t *testing.T) { - req := newSignRequest(scheme) - req.VerificationPlugin = "" - req.VerificationPluginMinVersion = "" - verifySignWithRequest(env, req, t) - }) - - t.Run(fmt.Sprintf("with %s scheme when verification plugin version is valid", scheme), func(t *testing.T) { - for _, v := range []string{"", "0.0.0", "1.1.1", "123.456.789", "2.1.0-alpha.1+cheers"} { - req := getSignRequest() - req.VerificationPluginMinVersion = v - verifySignWithRequest(env, req, t) + if !reflect.DeepEqual(signer, tt.expect) { + t.Errorf("expect %+v, got %+v", tt.expect, signer) } }) } } -// Tests various error scenarios around generating a signature envelope -func TestSignErrors(t *testing.T) { - env, _ := NewSignatureEnvelope(MediaTypeJWSJson) - req := getSignRequest() - - t.Run("when Payload is absent", func(t *testing.T) { - req.Payload = nil - verifySignErrorWithRequest(env, req, t) - }) - - t.Run("when PayloadContentType is absent", func(t *testing.T) { - req = getSignRequest() - req.PayloadContentType = "" - verifySignErrorWithRequest(env, req, t) - }) - - t.Run("when SigningTime is absent", func(t *testing.T) { - req = getSignRequest() - req.SigningTime = time.Time{} - verifySignErrorWithRequest(env, req, t) - }) - - t.Run("when SignatureProvider is absent", func(t *testing.T) { - req = getSignRequest() - req.SignatureProvider = nil - verifySignErrorWithRequest(env, req, t) - }) - - t.Run("when expiry is before singing time", func(t *testing.T) { - req = getSignRequest() - req.Expiry = req.SigningTime.AddDate(0, 0, -1) - verifySignErrorWithRequest(env, req, t) - }) - - t.Run("when VerificationPlugin is blank string", func(t *testing.T) { - req = getSignRequest() - req.VerificationPlugin = " " - verifySignErrorWithRequest(env, req, t) - }) - - t.Run("when VerificationPluginMinVersion is invalid", func(t *testing.T) { - for _, v := range []string{" ", "1", "1.1", "1.1.1.1", "v1.1.1", "1.alpha.1"} { - req = getSignRequest() - req.VerificationPluginMinVersion = v - verifySignErrorWithRequest(env, req, t) - } - }) - - t.Run("when VerificationPluginMinVersion is specified but not VerificationPlugin", func(t *testing.T) { - req = getSignRequest() - req.VerificationPlugin = "" - verifySignErrorWithRequest(env, req, t) - }) -} - -// Tests various scenarios around signature envelope verification -func TestVerify(t *testing.T) { - certs := "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" - var certsBytes []byte - for _, element := range strings.Split(certs, ",") { - certBytes, _ := base64.StdEncoding.DecodeString(element) - certsBytes = append(certsBytes, certBytes...) - } - signingCerts, _ := x509.ParseCertificates(certsBytes) - - env, err := NewSignatureEnvelopeFromBytes([]byte(TestValidSig), MediaTypeJWSJson) - if err != nil { - t.Fatalf("NewSignatureEnvelopeFromBytes() error = %v", err) - } - - vSignInfo, err := env.Verify() - if err != nil { - t.Fatalf("Verify() error = %v", err) - } +func TestSign(t *testing.T) { + signer := &localSigner{} - info, err := env.GetSignerInfo() - if err != nil { - t.Fatalf("GetSignerInfo() error = %v", err) + raw, certs, err := signer.Sign([]byte{}) + if err == nil { + t.Errorf("expect error but got nil") } - - req := getSignRequest() - req.SigningTime, err = time.Parse(time.RFC3339, "2022-08-04T10:03:11-07:00") - req.Expiry = req.SigningTime.AddDate(0, 0, 1) - req.SignatureProvider, _ = NewLocalSignatureProvider(signingCerts, testhelper.GetECLeafCertificate().PrivateKey) - verifySignerInfo(info, req, t) - - if !areSignInfoEqual(vSignInfo, info) { - t.Fatalf("SignerInfo object returned by Verify() and GetSignerInfo() are different.\n"+ - "Verify=%+v \nGetSignerInfo=%+v", vSignInfo, info) + if raw != nil { + t.Errorf("expect nil raw signature but got %v", raw) } -} - -// Tests various error scenarios around signature envelope verification -func TestVerifyErrors(t *testing.T) { - t.Run("when tempered signature envelope is provided", func(t *testing.T) { - env, _ := NewSignatureEnvelopeFromBytes([]byte(TestTamperedSig), MediaTypeJWSJson) - _, err := env.Verify() - if !(err != nil && errors.As(err, new(SignatureIntegrityError))) { - t.Errorf("Expected SignatureIntegrityError but found %T", err) - } - }) - - t.Run("when malformed signature envelope is provided", func(t *testing.T) { - env, _ := NewSignatureEnvelopeFromBytes([]byte("{}"), MediaTypeJWSJson) - _, err := env.Verify() - if !(err != nil && errors.As(err, new(MalformedSignatureError))) { - t.Errorf("Expected SignatureIntegrityError but found %T", err) - } - }) -} - -// Tests various scenarios around sign first and then verify envelope verification -func TestSignAndVerify(t *testing.T) { - t.Run("with RSA certificate", func(t *testing.T) { - // Sign - env, err := NewSignatureEnvelope(MediaTypeJWSJson) - if err != nil { - t.Fatalf("NewSignatureEnvelope() error = %v", err) - } - - req := getSignRequest() - sig, err := env.Sign(req) - if err != nil || len(sig) == 0 { - t.Fatalf("Sign() error = %v", err) - } - - // Verify using same env struct - _, err = env.Verify() - if err != nil { - t.Fatalf("Verify() error = %v", err) - } - - info, err := env.GetSignerInfo() - if err != nil { - t.Fatalf("GetSignerInfo() error = %v", err) - } - - verifySignerInfo(info, req, t) - }) - - t.Run("with EC certificate", func(t *testing.T) { - // Sign - env, err := NewSignatureEnvelope(MediaTypeJWSJson) - if err != nil { - t.Fatalf("NewSignatureEnvelope() error = %v", err) - } - - req := getSignRequest() - certs := []*x509.Certificate{testhelper.GetECLeafCertificate().Cert, testhelper.GetECRootCertificate().Cert} - req.SignatureProvider, _ = NewLocalSignatureProvider(certs, testhelper.GetECLeafCertificate().PrivateKey) - sig, err := env.Sign(req) - if err != nil || len(sig) == 0 { - t.Fatalf("Sign() error = %v", err) - } - - // Verify using same env struct - _, err = env.Verify() - if err != nil { - t.Fatalf("Verify() error = %v", err) - } - - info, err := env.GetSignerInfo() - if err != nil { - t.Fatalf("GetSignerInfo() error = %v", err) - } - - verifySignerInfo(info, req, t) - }) -} - -// Tests various error scenarios around GetSignerInfo method -func TestGetSignerInfoErrors(t *testing.T) { - env, _ := NewSignatureEnvelope(MediaTypeJWSJson) - t.Run("when called GetSignerInfo before sign or verify.", func(t *testing.T) { - _, err := env.GetSignerInfo() - if !(err != nil && errors.As(err, new(SignatureNotFoundError))) { - t.Errorf("Expected SignatureNotFoundError but found %q", err) - } - }) - - t.Run("when called GetSignerInfo after failed sign or verify call.", func(t *testing.T) { - req := getSignRequest() - req.SignatureProvider = nil - env.Sign(req) - env.Verify() - _, err := env.GetSignerInfo() - if !(err != nil && errors.As(err, new(SignatureNotFoundError))) { - t.Errorf("Expected SignatureNotFoundError but but found %q", reflect.TypeOf(err)) - } - }) -} - -func TestVerifyAuthenticity(t *testing.T) { - env, _ := NewSignatureEnvelope(MediaTypeJWSJson) - req := getSignRequest() - env.Sign(req) - info, _ := env.GetSignerInfo() - - t.Run("when trustedCerts is root cert", func(t *testing.T) { - certs := getSigningCerts() - root := certs[len(certs)-1] - trust, err := VerifyAuthenticity(info, []*x509.Certificate{root, testhelper.GetECRootCertificate().Cert}) - if err != nil { - t.Fatalf("VerifyAuthenticity() error = %v", err) - } - - if !trust.Equal(root) { - t.Fatalf("Expected cert with subject %q but found cert with subject %q", - root.Subject, trust.Subject) - } - }) - - t.Run("when trustedCerts is leaf cert", func(t *testing.T) { - leaf := getSigningCerts()[0] - trust, err := VerifyAuthenticity(info, []*x509.Certificate{leaf, testhelper.GetECRootCertificate().Cert}) - if err != nil { - t.Fatalf("VerifyAuthenticity() error = %v", err) - } - - if !trust.Equal(leaf) { - t.Fatalf("Expected cert with subject %q but found cert with subject %q", - leaf.Subject, trust.Subject) - } - }) -} - -func TestVerifyAuthenticityError(t *testing.T) { - env, _ := NewSignatureEnvelope(MediaTypeJWSJson) - req := getSignRequest() - env.Sign(req) - info, _ := env.GetSignerInfo() - - t.Run("when trustedCerts are not trusted", func(t *testing.T) { - _, err := VerifyAuthenticity(info, []*x509.Certificate{testhelper.GetECRootCertificate().Cert}) - if !(err != nil && errors.As(err, new(SignatureAuthenticityError))) { - t.Errorf("Expected SignatureAuthenticityError but found %T", err) - } - }) - - t.Run("when trustedCerts is absent", func(t *testing.T) { - _, err := VerifyAuthenticity(info, []*x509.Certificate{}) - if !(err != nil && errors.As(err, new(MalformedArgumentError))) { - t.Errorf("Expected MalformedArgumentError but found %T", err) - } - }) - - t.Run("when trustedCerts array is of zero length", func(t *testing.T) { - _, err := VerifyAuthenticity(info, nil) - if !(err != nil && errors.As(err, new(MalformedArgumentError))) { - t.Errorf("Expected MalformedArgumentError but found %T", err) - } - }) - - t.Run("when SignerInfo is absent", func(t *testing.T) { - _, err := VerifyAuthenticity(nil, []*x509.Certificate{testhelper.GetECRootCertificate().Cert}) - if !(err != nil && errors.As(err, new(MalformedArgumentError))) { - t.Errorf("Expected MalformedArgumentError but found %T", err) - } - }) - - t.Run("when cert chain in signer info is absent", func(t *testing.T) { - signInfoCopy := *info - signInfoCopy.CertificateChain = nil - _, err := VerifyAuthenticity(&signInfoCopy, nil) - if !(err != nil && errors.As(err, new(MalformedArgumentError))) { - t.Errorf("Expected MalformedArgumentError but found %T", err) - } - }) - -} - -func newSignRequest(scheme SigningScheme) SignRequest { - lSigner, _ := NewLocalSignatureProvider(getSigningCerts(), testhelper.GetRSALeafCertificate().PrivateKey) - - return SignRequest{ - Payload: []byte(TestPayload), - PayloadContentType: PayloadContentTypeV1, - SigningScheme: scheme, - SigningTime: time.Now(), - Expiry: time.Now().AddDate(0, 0, 1), - ExtendedSignedAttrs: []Attribute{ - {Key: "signedCritKey1", Value: "signedValue1", Critical: true}, - {Key: "signedKey1", Value: "signedKey2", Critical: false}}, - SigningAgent: "NotationUnitTest/1.0.0", - SignatureProvider: lSigner, - VerificationPlugin: "Hola Plugin", - VerificationPluginMinVersion: "1.1.1", + if certs != nil { + t.Errorf("expect nil certs but got %v", certs) } } -func getSignRequest() SignRequest { - return newSignRequest(SigningSchemeX509) -} - -func getSigningCerts() []*x509.Certificate { - return []*x509.Certificate{testhelper.GetRSALeafCertificate().Cert, testhelper.GetRSARootCertificate().Cert} -} - -func verifySignerInfo(signInfo *SignerInfo, request SignRequest, t *testing.T) { - if request.SigningAgent != signInfo.UnsignedAttributes.SigningAgent { - t.Errorf("SigningAgent: expected value %q but found %q", request.SigningAgent, signInfo.UnsignedAttributes.SigningAgent) +func TestKeySpec(t *testing.T) { + expectKeySpec := KeySpec{ + Type: KeyTypeRSA, + Size: 256, } + signer := &localSigner{keySpec: expectKeySpec} - if request.SigningTime.Format(time.RFC3339) != signInfo.SignedAttributes.SigningTime.Format(time.RFC3339) { - t.Errorf("SigningTime: expected value %q but found %q", request.SigningTime, signInfo.SignedAttributes.SigningTime) - } + keySpec, err := signer.KeySpec() - if request.Expiry.Format(time.RFC3339) != signInfo.SignedAttributes.Expiry.Format(time.RFC3339) { - t.Errorf("Expiry: expected value %q but found %q", request.SigningTime, signInfo.SignedAttributes.Expiry) - } - - if !areAttrEqual(request.ExtendedSignedAttrs, signInfo.SignedAttributes.ExtendedAttributes) { - if !(len(request.ExtendedSignedAttrs) == 0 && len(signInfo.SignedAttributes.ExtendedAttributes) == 0) { - t.Errorf("Mistmatch between expected and actual ExtendedAttributes") - } + if err != nil { + t.Errorf("expect no error but got %v", err) } - - if request.PayloadContentType != signInfo.PayloadContentType { - t.Errorf("PayloadContentType: expected value %q but found %q", request.PayloadContentType, signInfo.PayloadContentType) + if !reflect.DeepEqual(keySpec, expectKeySpec) { + t.Errorf("expect keySpec %+v, got %+v", expectKeySpec, keySpec) } +} - _, certs, err := request.SignatureProvider.Sign([]byte("")) - if err != nil || !reflect.DeepEqual(certs, signInfo.CertificateChain) { - t.Errorf("Mistmatch between expected and actual CertificateChain") +func TestCertificateChain(t *testing.T) { + expectCerts := []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, } + signer := &localSigner{certs: expectCerts} - // The input payload and the payload signed are different because the jwt library we are using converts - // payload to map and then to json but the content of payload should be same - var requestPay map[string]interface{} - if err := json.Unmarshal(request.Payload, &requestPay); err != nil { - t.Log(err) - } + certs, err := signer.CertificateChain() - var signerInfoPay map[string]interface{} - if err := json.Unmarshal(signInfo.Payload, &signerInfoPay); err != nil { - t.Log(err) + if err != nil { + t.Errorf("expect no error but got %v", err) } - - if !reflect.DeepEqual(signerInfoPay, signerInfoPay) { - t.Errorf("Payload: expected value %q but found %q", requestPay, signerInfoPay) + if !reflect.DeepEqual(certs, expectCerts) { + t.Errorf("expect certs %+v, got %+v", expectCerts, certs) } } -func verifySignWithRequest(env *SignatureEnvelope, req SignRequest, t *testing.T) { - sig, err := env.Sign(req) - if err != nil || len(sig) == 0 { - t.Fatalf("Sign() error = %v", err) - } +func TestPrivateKey(t *testing.T) { + expectKey := testhelper.GetRSALeafCertificate().PrivateKey + signer := &localSigner{key: expectKey} - info, err := env.GetSignerInfo() - if err != nil { - t.Fatalf("GetSignerInfo() error = %v", err) - } + key := signer.PrivateKey() - verifySignerInfo(info, req, t) -} - -func verifySignErrorWithRequest(env *SignatureEnvelope, req SignRequest, t *testing.T) { - _, err := env.Sign(req) - if !(err != nil && errors.As(err, new(MalformedSignRequestError))) { - t.Errorf("Expected MalformedArgumentError but but found %q", reflect.TypeOf(err)) + if !reflect.DeepEqual(key, expectKey) { + t.Errorf("expect key %+v, got %+v", expectKey, key) } } -func areAttrEqual(u []Attribute, v []Attribute) bool { - sort.Slice(u, func(p, q int) bool { - return u[p].Key < u[q].Key - }) - sort.Slice(v, func(p, q int) bool { - return v[p].Key < v[q].Key - }) - return reflect.DeepEqual(u, v) -} - -func areSignInfoEqual(u *SignerInfo, v *SignerInfo) bool { - uExtAttr := u.SignedAttributes.ExtendedAttributes - vExtAttr := v.SignedAttributes.ExtendedAttributes - u.SignedAttributes.ExtendedAttributes = nil - v.SignedAttributes.ExtendedAttributes = nil - return reflect.DeepEqual(u, v) && areAttrEqual(uExtAttr, vExtAttr) +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/signature/types.go b/signature/types.go index 19ad1a57..49cecae9 100644 --- a/signature/types.go +++ b/signature/types.go @@ -1,103 +1,149 @@ package signature -import "crypto" +import ( + "crypto/x509" + "errors" + "time" +) // SignatureMediaType list the supported media-type for signatures. type SignatureMediaType string -// SignatureAlgorithm lists supported signature algorithms. -type SignatureAlgorithm string - -// HashAlgorithm algorithm associated with the key spec. -type HashAlgorithm string +// SigningScheme formalizes the feature set (guarantees) provided by +// the signature. +// Reference: https://github.com/notaryproject/notaryproject/blob/main/signing-scheme.md +type SigningScheme string -// One of following supported specs -// https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection +// SigningSchemes supported by notation. const ( - RSASSA_PSS_SHA_256 SignatureAlgorithm = "RSASSA_PSS_SHA_256" - RSASSA_PSS_SHA_384 SignatureAlgorithm = "RSASSA_PSS_SHA_384" - RSASSA_PSS_SHA_512 SignatureAlgorithm = "RSASSA_PSS_SHA_512" - ECDSA_SHA_256 SignatureAlgorithm = "ECDSA_SHA_256" - ECDSA_SHA_384 SignatureAlgorithm = "ECDSA_SHA_384" - ECDSA_SHA_512 SignatureAlgorithm = "ECDSA_SHA_512" -) + // notary.x509 signing scheme. + SigningSchemeX509 SigningScheme = "notary.x509" -// One of following supported specs -// https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection -const ( - SHA_256 HashAlgorithm = "SHA_256" - SHA_384 HashAlgorithm = "SHA_384" - SHA_512 HashAlgorithm = "SHA_512" + // notary.x509.signingAuthority schema. + SigningSchemeX509SigningAuthority SigningScheme = "notary.x509.signingAuthority" ) -// HashFunc returns the Hash associated k. -func (h HashAlgorithm) HashFunc() crypto.Hash { - switch h { - case SHA_256: - return crypto.SHA256 - case SHA_384: - return crypto.SHA384 - case SHA_512: - return crypto.SHA512 - } - return 0 +// MediaTypePayloadV1 is the supported content type for signature's payload. +const MediaTypePayloadV1 = "application/vnd.cncf.notary.payload.v1+json" + +// SignedAttributes represents signed metadata in the signature envelope. +// Reference: https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#signed-attributes +type SignedAttributes struct { + // SigningScheme defines the Notary v2 Signing Scheme used by the signature. + SigningScheme SigningScheme + + // SigningTime indicates the time at which the signature was generated. + SigningTime time.Time + + // Expiry provides a “best by use” time for the artifact. + Expiry time.Time + + // additional signed attributes in the signature envelope. + ExtendedAttributes []Attribute } -// Hash returns the Hash associated s. -func (s SignatureAlgorithm) Hash() HashAlgorithm { - switch s { - case RSASSA_PSS_SHA_256, ECDSA_SHA_256: - return SHA_256 - case RSASSA_PSS_SHA_384, ECDSA_SHA_384: - return SHA_384 - case RSASSA_PSS_SHA_512, ECDSA_SHA_512: - return SHA_512 - } - return "" +// UnsignedAttributes represents unsigned metadata in the Signature envelope. +// Reference: https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#unsigned-attributes +type UnsignedAttributes struct { + // TimestampSignature is a counter signature providing authentic timestamp. + TimestampSignature []byte + + // SigningAgent provides the identifier of the software (e.g. Notation) that + // produces the signature on behalf of the user. + SigningAgent string } -// KeySpec defines a key type and size. -type KeySpec string +// Attribute represents metadata in the Signature envelope. +type Attribute struct { + // Key is the key name of the attribute. + Key string -const ( - RSA_2048 KeySpec = "RSA_2048" - RSA_3072 KeySpec = "RSA_3072" - RSA_4096 KeySpec = "RSA_4096" - EC_256 KeySpec = "EC_256" - EC_384 KeySpec = "EC_384" - EC_521 KeySpec = "EC_521" -) + // Critical marks the attribute that MUST be processed by a verifier. + Critical bool -// SignatureAlgorithm returns the signing algorithm associated with KeyType k. -func (k KeySpec) SignatureAlgorithm() SignatureAlgorithm { - switch k { - case RSA_2048: - return RSASSA_PSS_SHA_256 - case RSA_3072: - return RSASSA_PSS_SHA_384 - case RSA_4096: - return RSASSA_PSS_SHA_512 - case EC_256: - return ECDSA_SHA_256 - case EC_384: - return ECDSA_SHA_384 - case EC_521: - return ECDSA_SHA_512 - } - return "" + // Value is the value of the attribute. + Value interface{} } -// SigningScheme formalizes the feature set (guarantees) provided by the signature. -type SigningScheme string +// SignRequest is used to generate Signature. +type SignRequest struct { + // Payload is the payload to be signed. + Payload Payload -const ( - SigningSchemeX509 SigningScheme = "notary.x509" - SigningSchemeX509SigningAuthority SigningScheme = "notary.x509.signingAuthority" -) + // Signer is the signer used to sign the digest. + Signer Signer -// PayloadContentType list the supported content types for signature's payload . -type PayloadContentType string + // SigningTime is the time at which the signature was generated. + SigningTime time.Time -const ( - PayloadContentTypeV1 PayloadContentType = "application/vnd.cncf.notary.payload.v1+json" -) + // Expiry provides a “best by use” time for the artifact. + Expiry time.Time + + // ExtendedSignedAttributes is additional signed attributes in the + // signature envelope. + ExtendedSignedAttributes []Attribute + + // SigningAgent provides the identifier of the software (e.g. Notation) + // that produced the signature on behalf of the user. + SigningAgent string + + // SigningScheme defines the Notary v2 Signing Scheme used by the signature. + SigningScheme SigningScheme +} + +// EnvelopeContent represents a combination of payload to be signed and a parsed +// signature envelope. +type EnvelopeContent struct { + // SignerInfo is a parsed signature envelope. + SignerInfo SignerInfo + + // Payload is payload to be signed. + Payload Payload +} + +// SignerInfo represents a parsed signature envelope that is agnostic to +// signature envelope format. +type SignerInfo struct { + // SignedAttributes are additional metadata required to support the + // signature verification process. + SignedAttributes SignedAttributes + + // UnsignedAttributes are considered unsigned with respect to the signing + // key that generates the signature. + UnsignedAttributes UnsignedAttributes + + // SignatureAlgorithm defines the signature algorithm. + SignatureAlgorithm Algorithm + + // CertificateChain is an ordered list of X.509 public certificates + // associated with the signing key used to generate the signature. + // The ordered list starts with the signing certificates, any intermediate + // certificates and ends with the root certificate. + CertificateChain []*x509.Certificate + + // Signature is the bytes generated from the signature. + Signature []byte + + // Payload represents payload in bytes and its content type. + Payload Payload +} + +// Payload represents payload in bytes and its content type. +type Payload struct { + // ContentType specifies the content type of payload. + ContentType string + + // Content contains the raw bytes of the payload. + Content []byte +} + +// ExtendedAttribute fetches the specified Attribute with provided key from +// signerInfo.SignedAttributes.ExtendedAttributes. +func (signerInfo *SignerInfo) ExtendedAttribute(key string) (Attribute, error) { + for _, attr := range signerInfo.SignedAttributes.ExtendedAttributes { + if attr.Key == key { + return attr, nil + } + } + return Attribute{}, errors.New("key not in ExtendedAttributes") +} diff --git a/signature/utils.go b/signature/utils.go index d1b4fe19..705ec860 100644 --- a/signature/utils.go +++ b/signature/utils.go @@ -8,43 +8,13 @@ import ( "crypto/x509" ) -// GetKeySpec picks up a recommended signing algorithm for given certificate. -func GetKeySpec(signingCert *x509.Certificate) (KeySpec, error) { - var keyspec KeySpec - switch key := signingCert.PublicKey.(type) { - case *rsa.PublicKey: - switch key.Size() { - case 256: - keyspec = RSA_2048 - case 384: - keyspec = RSA_3072 - case 512: - keyspec = RSA_4096 - default: - return "", UnsupportedSigningKeyError{keyType: "rsa", keyLength: key.Size()} - } - case *ecdsa.PublicKey: - switch key.Curve.Params().BitSize { - case 256: - keyspec = EC_256 - case 384: - keyspec = EC_384 - case 521: - keyspec = EC_521 - default: - return "", UnsupportedSigningKeyError{keyType: "ecdsa", keyLength: key.Curve.Params().BitSize} - } - } - return keyspec, nil -} - // NewLocalSignatureProvider returns the LocalSignatureProvider created using given certificates and private key. func NewLocalSignatureProvider(certs []*x509.Certificate, pk crypto.PrivateKey) (*LocalSignatureProvider, error) { if len(certs) == 0 { - return nil, MalformedArgumentError{param: "certs"} + return nil, &MalformedArgumentError{Param: "certs"} } - ks, err := GetKeySpec(certs[0]) + ks, err := ExtractKeySpec(certs[0]) if err != nil { return nil, err } @@ -104,10 +74,10 @@ func (l *LocalSignatureProvider) KeySpec() (KeySpec, error) { } // getSignatureAlgorithm picks up a recommended signing algorithm for given certificate. -func getSignatureAlgorithm(signingCert *x509.Certificate) (SignatureAlgorithm, error) { - keySpec, err := GetKeySpec(signingCert) +func getSignatureAlgorithm(signingCert *x509.Certificate) (Algorithm, error) { + keySpec, err := ExtractKeySpec(signingCert) if err != nil { - return "", err + return 0, err } return keySpec.SignatureAlgorithm(), nil diff --git a/testhelper/certificatetest.go b/testhelper/certificatetest.go index 6b88449f..bbc6bb7d 100644 --- a/testhelper/certificatetest.go +++ b/testhelper/certificatetest.go @@ -10,15 +10,17 @@ import ( "crypto/x509" "crypto/x509/pkix" "math/big" + "strconv" "time" ) var ( - rsaRoot RSACertTuple - rsaLeaf RSACertTuple - ecdsaRoot ECCertTuple - ecdsaLeaf ECCertTuple - unsupported RSACertTuple + rsaRoot RSACertTuple + rsaLeaf RSACertTuple + ecdsaRoot ECCertTuple + ecdsaLeaf ECCertTuple + unsupportedECDSARoot ECCertTuple + unsupportedRSARoot RSACertTuple ) type RSACertTuple struct { @@ -56,10 +58,16 @@ func GetECLeafCertificate() ECCertTuple { return ecdsaLeaf } -// 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 unsupportedRSARoot +} + +// GetUnsupportedECCert returns certificate signed using EC algorithm with P-224 +// curve which is not supported by notary. +func GetUnsupportedECCert() ECCertTuple { + return unsupportedECDSARoot } func setupCertificates() { @@ -67,11 +75,12 @@ func setupCertificates() { 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()) // 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. k, _ := rsa.GenerateKey(rand.Reader, 1024) - unsupported = GetRSACertTupleWithPK(k, "Notation Unsupported Root", nil) + unsupportedRSARoot = GetRSACertTupleWithPK(k, "Notation Unsupported Root", nil) } func getCertTuple(cn string, issuer *RSACertTuple) RSACertTuple { @@ -79,6 +88,11 @@ 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) @@ -147,3 +161,28 @@ func getCertTemplate(isRoot bool, cn string) *x509.Certificate { return template } + +func GetRSACertTuple(size int) RSACertTuple { + rsaRoot := GetRSARootCertificate() + priv, _ := rsa.GenerateKey(rand.Reader, size) + + certTuple := GetRSACertTupleWithPK( + priv, + "Test RSA_"+strconv.Itoa(priv.Size()), + &rsaRoot, + ) + return certTuple +} + +func GetECCertTuple(curve elliptic.Curve) ECCertTuple { + ecdsaRoot := GetECRootCertificate() + priv, _ := ecdsa.GenerateKey(curve, rand.Reader) + bitSize := priv.Params().BitSize + + certTuple := GetECDSACertTupleWithPK( + priv, + "Test EC_"+strconv.Itoa(bitSize), + &ecdsaRoot, + ) + return certTuple +}