From 65b1b6a9d8950b4297cca25a511ff5583e4ebbc9 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Fri, 9 Sep 2022 10:59:11 +0800 Subject: [PATCH 1/3] refactor: rename signer to signature submodule (#71) Signed-off-by: Binbin Li --- {signer => signature}/errors.go | 2 +- {signer => signature}/errors_test.go | 2 +- {signer => signature}/jws.go | 2 +- {signer => signature}/jws_test.go | 2 +- {signer => signature}/jwt.go | 2 +- {signer => signature}/jwt_test.go | 2 +- {signer => signature}/signer.go | 2 +- {signer => signature}/signer_test.go | 2 +- {signer => signature}/types.go | 2 +- {signer => signature}/utils.go | 2 +- {signer => signature}/utils_test.go | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) rename {signer => signature}/errors.go (99%) rename {signer => signature}/errors_test.go (99%) rename {signer => signature}/jws.go (99%) rename {signer => signature}/jws_test.go (99%) rename {signer => signature}/jwt.go (99%) rename {signer => signature}/jwt_test.go (99%) rename {signer => signature}/signer.go (99%) rename {signer => signature}/signer_test.go (99%) rename {signer => signature}/types.go (99%) rename {signer => signature}/utils.go (99%) rename {signer => signature}/utils_test.go (99%) diff --git a/signer/errors.go b/signature/errors.go similarity index 99% rename from signer/errors.go rename to signature/errors.go index b359412b..d6ed1e8c 100644 --- a/signer/errors.go +++ b/signature/errors.go @@ -1,4 +1,4 @@ -package signer +package signature import "fmt" diff --git a/signer/errors_test.go b/signature/errors_test.go similarity index 99% rename from signer/errors_test.go rename to signature/errors_test.go index e53f7282..943835a0 100644 --- a/signer/errors_test.go +++ b/signature/errors_test.go @@ -1,4 +1,4 @@ -package signer +package signature import ( "fmt" diff --git a/signer/jws.go b/signature/jws.go similarity index 99% rename from signer/jws.go rename to signature/jws.go index c6294a37..1c4f2849 100644 --- a/signer/jws.go +++ b/signature/jws.go @@ -1,4 +1,4 @@ -package signer +package signature import ( "crypto/x509" diff --git a/signer/jws_test.go b/signature/jws_test.go similarity index 99% rename from signer/jws_test.go rename to signature/jws_test.go index 5151caa8..2369f1d0 100644 --- a/signer/jws_test.go +++ b/signature/jws_test.go @@ -1,4 +1,4 @@ -package signer +package signature import ( "crypto/rand" diff --git a/signer/jwt.go b/signature/jwt.go similarity index 99% rename from signer/jwt.go rename to signature/jwt.go index df92cc7b..15f5de73 100644 --- a/signer/jwt.go +++ b/signature/jwt.go @@ -1,4 +1,4 @@ -package signer +package signature import ( "crypto" diff --git a/signer/jwt_test.go b/signature/jwt_test.go similarity index 99% rename from signer/jwt_test.go rename to signature/jwt_test.go index 3e0f1d21..15cb0aeb 100644 --- a/signer/jwt_test.go +++ b/signature/jwt_test.go @@ -1,4 +1,4 @@ -package signer +package signature import ( "crypto/ecdsa" diff --git a/signer/signer.go b/signature/signer.go similarity index 99% rename from signer/signer.go rename to signature/signer.go index 3aa4405b..21afa805 100644 --- a/signer/signer.go +++ b/signature/signer.go @@ -1,4 +1,4 @@ -package signer +package signature import ( "crypto/x509" diff --git a/signer/signer_test.go b/signature/signer_test.go similarity index 99% rename from signer/signer_test.go rename to signature/signer_test.go index 33c8699c..760ae520 100644 --- a/signer/signer_test.go +++ b/signature/signer_test.go @@ -1,4 +1,4 @@ -package signer +package signature import ( "crypto/x509" diff --git a/signer/types.go b/signature/types.go similarity index 99% rename from signer/types.go rename to signature/types.go index 1dddfff8..19ad1a57 100644 --- a/signer/types.go +++ b/signature/types.go @@ -1,4 +1,4 @@ -package signer +package signature import "crypto" diff --git a/signer/utils.go b/signature/utils.go similarity index 99% rename from signer/utils.go rename to signature/utils.go index e165e89b..d1b4fe19 100644 --- a/signer/utils.go +++ b/signature/utils.go @@ -1,4 +1,4 @@ -package signer +package signature import ( "crypto" diff --git a/signer/utils_test.go b/signature/utils_test.go similarity index 99% rename from signer/utils_test.go rename to signature/utils_test.go index 03e7d97d..cbdd1324 100644 --- a/signer/utils_test.go +++ b/signature/utils_test.go @@ -1,4 +1,4 @@ -package signer +package signature import ( "crypto" From 55b5b3970f90b35d4a7e134259e0e73d194c6b44 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Wed, 14 Sep 2022 14:57:53 +0800 Subject: [PATCH 2/3] refactor: refactor envelope and signer to support cose (#73) Signed-off-by: Binbin Li --- signature/algorithm.go | 112 ++++ signature/algorithm_test.go | 231 ++++++++ signature/envelope.go | 92 +++ signature/envelope_test.go | 197 +++++++ signature/errors.go | 120 ++-- signature/errors_test.go | 158 ++++- signature/internal/base/envelope.go | 221 +++++++ signature/internal/base/envelope_test.go | 722 +++++++++++++++++++++++ signature/jws.go | 40 +- signature/jws_test.go | 24 +- signature/jwt.go | 14 +- signature/signer.go | 328 +++------- signature/signer_test.go | 619 ++++++------------- signature/types.go | 202 ++++--- signature/utils.go | 40 +- testhelper/certificatetest.go | 59 +- 16 files changed, 2268 insertions(+), 911 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..4f106208 --- /dev/null +++ b/signature/algorithm.go @@ -0,0 +1,112 @@ +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 { + // KeyType is the type of the key. + Type KeyType + + // KeySize is the size of the key in bits. + 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 bits 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 bits is not supported", bitSize), + } + } + } + return KeySpec{}, &UnsupportedSigningKeyError{ + Msg: "unsupported 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..d73e92f4 --- /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" + "sync" +) + +// Envelope provides 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 sync.Map // 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") + } + envelopeFuncs.Store(mediaType, envelopeFunc{ + newFunc: newFunc, + parseFunc: parseFunc, + }) + return nil +} + +// RegisteredEnvelopeTypes lists registered envelope media types. +func RegisteredEnvelopeTypes() []string { + var types []string + + envelopeFuncs.Range(func(k, v interface{}) bool { + types = append(types, k.(string)) + return true + }) + + return types +} + +// NewEnvelope generates an envelope of given media type. +func NewEnvelope(mediaType string) (Envelope, error) { + val, ok := envelopeFuncs.Load(mediaType) + if !ok { + return nil, &UnsupportedSignatureFormatError{MediaType: mediaType} + } + return val.(envelopeFunc).newFunc(), nil +} + +// ParseEnvelope generates an envelope for given envelope bytes with specified +// media type. +func ParseEnvelope(mediaType string, envelopeBytes []byte) (Envelope, error) { + val, ok := envelopeFuncs.Load(mediaType) + if !ok { + return nil, &UnsupportedSignatureFormatError{MediaType: mediaType} + } + return val.(envelopeFunc).parseFunc(envelopeBytes) +} diff --git a/signature/envelope_test.go b/signature/envelope_test.go new file mode 100644 index 00000000..56ab5135 --- /dev/null +++ b/signature/envelope_test.go @@ -0,0 +1,197 @@ +package signature + +import ( + "reflect" + "sync" + "testing" +) + +var ( + emptyFuncs sync.Map + validFuncs sync.Map +) + +func init() { + validFuncs.Store(testMediaType, envelopeFunc{ + newFunc: testNewFunc, + parseFunc: testParseFunc, + }) +} + +// 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 sync.Map + expect []string + }{ + { + name: "empty map", + envelopeFuncs: emptyFuncs, + expect: nil, + }, + { + name: "nonempty map", + envelopeFuncs: validFuncs, + 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 sync.Map + expect Envelope + expectErr bool + }{ + { + name: "unsupported media type", + mediaType: testMediaType, + envelopeFuncs: emptyFuncs, + expect: nil, + expectErr: true, + }, + { + name: "valid media type", + mediaType: testMediaType, + envelopeFuncs: validFuncs, + 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 sync.Map + expect Envelope + expectErr bool + }{ + { + name: "unsupported media type", + mediaType: testMediaType, + envelopeFuncs: emptyFuncs, + expect: nil, + expectErr: true, + }, + { + name: "valid media type", + mediaType: testMediaType, + envelopeFuncs: validFuncs, + 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..4b059033 100644 --- a/signature/errors.go +++ b/signature/errors.go @@ -2,35 +2,43 @@ 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()) } -// MalformedSignatureError is used when Signature envelope is malformed. -type MalformedSignatureError struct { - msg string +// Unwrap unwraps the internal error. +func (e *SignatureIntegrityError) Unwrap() error { + return e.Err } -func (e MalformedSignatureError) Error() string { - if e.msg != "" { - return e.msg - } - return "signature envelope format is malformed" +// InvalidSignatureError is used when Signature envelope is invalid. +type InvalidSignatureError struct { + Msg string +} +// Error returns the error message or the default message if not provided. +func (e InvalidSignatureError) Error() string { + if e.Msg != "" { + return e.Msg + } + return "signature envelope format is invalid" } // 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 +48,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 +// InvalidArgumentError is used when an argument to a function is invalid. +type InvalidArgumentError struct { + 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 *InvalidArgumentError) Error() string { + if e.Err != nil { + return fmt.Sprintf("%q param is invalid. Error: %s", e.Param, e.Err.Error()) } - return fmt.Sprintf("%q param is malformed", e.param) + return fmt.Sprintf("%q param is invalid", e.Param) } -// MalformedSignRequestError is used when SignRequest is malformed. -type MalformedSignRequestError struct { - msg string +// Unwrap returns the unwrapped error +func (e *InvalidArgumentError) Unwrap() error { + return e.Err } -func (e MalformedSignRequestError) Error() string { - if e.msg != "" { - return e.msg +// InvalidSignRequestError is used when SignRequest is invalid. +type InvalidSignRequestError struct { + Msg string +} + +// Error returns the error message or the default message if not provided. +func (e *InvalidSignRequestError) Error() string { + if e.Msg != "" { + return e.Msg } - return "SignRequest is malformed" + return "SignRequest is invalid" +} + +// UnsupportedSignatureAlgoError is used when signing algo is not supported. +type UnsupportedSignatureAlgoError struct { + Alg string +} + +// Error returns the formatted error message. +func (e *UnsupportedSignatureAlgoError) 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" } -// SignatureAlgoNotSupportedError is used when signing algo is not supported. -type SignatureAlgoNotSupportedError struct { - alg string +// DuplicateKeyError is used when repeated key name found. +type DuplicateKeyError 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 *DuplicateKeyError) Error() string { + return fmt.Sprintf("repeated key: %q exists.", e.Key) } diff --git a/signature/errors_test.go b/signature/errors_test.go index 943835a0..84cb20c1 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) +func TestInvalidSignatureError(t *testing.T) { + tests := []struct { + name string + err *InvalidSignatureError + expect string + }{ + { + name: "err msg set", + err: &InvalidSignatureError{Msg: errMsg}, + expect: errMsg, + }, + { + name: "err msg not set", + err: &InvalidSignatureError{}, + expect: "signature envelope format is invalid", + }, + } - 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) +func TestInvalidArgumentError(t *testing.T) { + expectedMsg := "\"hola\" param is invalid" + validateErrorMsg(&InvalidArgumentError{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) + expectedMsg = "\"hola\" param is invalid. Error: se produjo un error" + validateErrorMsg(&InvalidArgumentError{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) +func TestUnsupportedSignatureAlgoError(t *testing.T) { + err := &UnsupportedSignatureAlgoError{ + 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) +func TestInvalidSignRequestError(t *testing.T) { + expectedMsg := "SignRequest is invalid" + validateErrorMsg(&InvalidSignRequestError{}, expectedMsg, t) expectedMsg = "Se produjo un error" - validateErrorMsg(MalformedSignRequestError{msg: expectedMsg}, expectedMsg, t) + validateErrorMsg(&InvalidSignRequestError{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 TestInvalidArgumentError_Unwrap(t *testing.T) { + err := &InvalidArgumentError{ + 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 := &DuplicateKeyError{Key: errMsg} + expectMsg := fmt.Sprintf("repeated key: %q exists.", 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..3379d3a1 --- /dev/null +++ b/signature/internal/base/envelope.go @@ -0,0 +1,221 @@ +package base + +import ( + "crypto/x509" + "errors" + "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 + } + + if err := validateCertificateChain( + content.SignerInfo.CertificateChain, + content.SignerInfo.SignedAttributes.SigningTime, + content.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.SignatureNotFoundError{} + } + + // 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.SignatureNotFoundError{} + } + + 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 &signature.InvalidSignRequestError{Msg: err.Error()} + } + + if err := validateSigningAndExpiryTime(req.SigningTime, req.Expiry); err != nil { + return err + } + + if req.Signer == nil { + return &signature.InvalidSignRequestError{Msg: "signer is nil"} + } + + if _, err := req.Signer.KeySpec(); err != nil { + return err + } + + return validateSigningSchema(req.SigningScheme) +} + +// validateSigningSchema validates the schema. +func validateSigningSchema(schema signature.SigningScheme) error { + if schema == "" { + return &signature.InvalidSignRequestError{Msg: "SigningScheme not present"} + } + return nil +} + +// validateEnvelopeContent validates the content which includes signerInfo and +// payload. +func validateEnvelopeContent(content *signature.EnvelopeContent) error { + if err := validatePayload(&content.Payload); err != nil { + return &signature.InvalidSignatureError{Msg: err.Error()} + } + 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.InvalidSignatureError{Msg: "signature not present or is empty"} + } + + if info.SignatureAlgorithm == 0 { + return &signature.InvalidSignatureError{Msg: "SignatureAlgorithm is not present"} + } + + signingTime := info.SignedAttributes.SigningTime + if err := validateSigningAndExpiryTime(signingTime, info.SignedAttributes.Expiry); err != nil { + return err + } + + if err := validateSigningSchema(info.SignedAttributes.SigningScheme); err != nil { + return err + } + + return validateCertificateChain( + info.CertificateChain, + signingTime, + info.SignatureAlgorithm, + ) +} + +// validateSigningAndExpiryTime checks that signing time is within the valid +// range of time duration and expire time is valid. +func validateSigningAndExpiryTime(signingTime, expireTime time.Time) error { + if signingTime.IsZero() { + return &signature.InvalidSignatureError{Msg: "signing-time not present"} + } + + if !expireTime.IsZero() && (expireTime.Before(signingTime) || expireTime.Equal(signingTime)) { + return &signature.InvalidSignatureError{Msg: "expiry cannot be equal or before the signing time"} + } + return nil +} + +// validatePayload performs validation of the payload. +func validatePayload(payload *signature.Payload) error { + if len(payload.Content) == 0 { + return errors.New("content not present") + } + + 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.InvalidSignatureError{Msg: "certificate-chain not present or is empty"} + } + + err := nx509.ValidateCodeSigningCertChain(certChain, signTime) + if err != nil { + return &signature.InvalidSignatureError{ + Msg: fmt.Sprintf("certificate-chain is invalid, %s", err), + } + } + + signingAlg, err := getSignatureAlgorithm(certChain[0]) + if err != nil { + return &signature.InvalidSignatureError{Msg: err.Error()} + } + if signingAlg != expectedAlg { + return &signature.InvalidSignatureError{ + 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..20aa887c --- /dev/null +++ b/signature/internal/base/envelope_test.go @@ -0,0 +1,722 @@ +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" + signiningSchema = signature.SigningScheme("notary.x509") + validSignerInfo = &signature.SignerInfo{ + Signature: validBytes, + SignatureAlgorithm: signature.AlgorithmPS384, + SignedAttributes: signature.SignedAttributes{ + SigningTime: testhelper.GetRSALeafCertificate().Cert.NotBefore, + Expiry: testhelper.GetECLeafCertificate().Cert.NotAfter, + SigningScheme: signiningSchema, + }, + 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, + SigningScheme: signiningSchema, + 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, + SigningScheme: signiningSchema, + 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{}, + }, + }, + }, + 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: "keySpec is empty", + req: &signature.SignRequest{ + Payload: signature.Payload{ + ContentType: validContentType, + Content: validBytes, + }, + SigningTime: time08_02, + Expiry: time08_03, + SigningScheme: signiningSchema, + Signer: &mockSigner{ + certs: []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, + testhelper.GetRSARootCertificate().Cert, + }, + keySpec: signature.KeySpec{}, + }, + }, + expectErr: true, + }, + { + name: "invalid signing schema", + req: &signature.SignRequest{ + Payload: signature.Payload{ + ContentType: validContentType, + Content: validBytes, + }, + SigningTime: time08_02, + Expiry: time08_03, + Signer: &mockSigner{ + keySpec: signature.KeySpec{ + Type: signature.KeyTypeRSA, + Size: 3072, + }, + certs: []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, + testhelper.GetRSARootCertificate().Cert, + }, + }, + }, + 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: "invalid signing schema", + info: &signature.SignerInfo{ + Signature: validBytes, + SignatureAlgorithm: signature.AlgorithmPS384, + SignedAttributes: signature.SignedAttributes{ + SigningTime: testhelper.GetRSALeafCertificate().Cert.NotBefore, + Expiry: testhelper.GetECLeafCertificate().Cert.NotAfter, + }, + }, + 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 := validateSigningAndExpiryTime(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..86763867 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 InvalidSignatureError{Msg: "malformed leaf certificate"} } cert, err := x509.ParseCertificate(jws.internalEnv.Header.CertChain[0]) if err != nil { - return MalformedSignatureError{msg: "malformed leaf certificate"} + return InvalidSignatureError{Msg: "malformed leaf certificate"} } // verify JWT @@ -72,7 +72,7 @@ func (jws *jwsEnvelope) validateIntegrity() error { func (jws *jwsEnvelope) signPayload(req SignRequest) ([]byte, error) { errorFunc := func(s string) error { - return MalformedSignRequestError{msg: s} + return InvalidSignRequestError{msg: s} } ks, err := req.SignatureProvider.KeySpec() @@ -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, InvalidSignatureError{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, InvalidSignatureError{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, InvalidSignatureError{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 InvalidSignatureError{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 InvalidSignatureError{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 InvalidSignatureError{Msg: fmt.Sprintf("%q header must be present for %s signing scheme", headerKeyAuthenticSigningTime, SigningSchemeX509)} } } @@ -250,7 +250,7 @@ func validateProtectedHeaders(protectedHdr *jwsProtectedHeader) error { // validateCriticalHeaders validates headers that should be present or marked critical as per singing scheme func validateCriticalHeaders(protectedHdr *jwsProtectedHeader) error { if len(protectedHdr.Critical) == 0 { - return MalformedSignatureError{"missing `crit` header"} + return InvalidSignatureError{"missing `crit` header"} } mustMarkedCrit := map[string]bool{headerKeySigningScheme: true} @@ -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 InvalidSignatureError{Msg: fmt.Sprintf("%q header is marked critical but not present", val)} } } } @@ -287,7 +287,7 @@ func validateCriticalHeaders(protectedHdr *jwsProtectedHeader) error { for k := range mustMarkedCrit { keys = append(keys, k) } - return MalformedSignatureError{fmt.Sprintf("these required headers are not marked as critical: %v", keys)} + return InvalidSignatureError{fmt.Sprintf("these required headers are not marked as critical: %v", keys)} } return nil @@ -339,7 +339,7 @@ func getSignedAttrs(req SignRequest, sigAlg SignatureAlgorithm) (map[string]inte jwsProtectedHdr.Critical = crit m, err := convertToMap(jwsProtectedHdr) if err != nil { - return nil, MalformedSignRequestError{msg: fmt.Sprintf("unexpected error occured while creating protected headers, Error: %s", err.Error())} + return nil, InvalidSignRequestError{msg: fmt.Sprintf("unexpected error occured while creating protected headers, Error: %s", err.Error())} } return mergeMaps(m, extAttrs), nil @@ -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) @@ -462,7 +462,7 @@ func sign(payload []byte, headers map[string]interface{}, sigPro SignatureProvid func getSignatureAlgo(alg string) (SignatureAlgorithm, error) { signatureAlg, ok := jwsAlgSignatureAlgMap[alg] if !ok { - return "", SignatureAlgoNotSupportedError{alg: alg} + return "", UnsupportedSignatureAlgoError{alg: alg} } return signatureAlg, nil @@ -471,7 +471,7 @@ func getSignatureAlgo(alg string) (SignatureAlgorithm, error) { func getJWSAlgo(alg SignatureAlgorithm) (string, error) { jwsAlg, ok := signatureAlgJWSAlgMap[alg] if !ok { - return "", SignatureAlgoNotSupportedError{alg: string(alg)} + return "", UnsupportedSignatureAlgoError{alg: string(alg)} } return jwsAlg, nil diff --git a/signature/jws_test.go b/signature/jws_test.go index 2369f1d0..f6c554a0 100644 --- a/signature/jws_test.go +++ b/signature/jws_test.go @@ -50,14 +50,14 @@ func TestValidateIntegrity(t *testing.T) { t.Run("with invalid base64 bytes sig envelope returns error", func(t *testing.T) { env, _ := newJWSEnvelopeFromBytes([]byte("{\"Payload\":\"Hi!\",\"Protected\":\"Hi\",\"Header\":{},\"Signature\":\"Hi!\"}")) err := env.validateIntegrity() - if !(err != nil && errors.As(err, new(MalformedSignatureError))) { + if !(err != nil && errors.As(err, new(InvalidSignatureError))) { t.Errorf("Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) } }) t.Run("with incomplete sig envelope returns error", func(t *testing.T) { env, _ := newJWSEnvelopeFromBytes([]byte("{\"Payload\":\"eyJhbGciOiJIUzI1NiJ9\",\"Protected\":\"eyJhbGciOiJQUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiXSwiaW8uY25jZi5ub3Rhcnkuc2luaW5nVGltZSI6IjIwMDYtMDEtMDJUMTU6MDQ6MDVaIn0\",\"Header\":{},\"Signature\":\"YjGj\"}")) - if err := env.validateIntegrity(); !(err != nil && errors.As(err, new(MalformedSignatureError))) { + if err := env.validateIntegrity(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { t.Errorf("Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) } }) @@ -84,7 +84,7 @@ func TestValidateIntegrity(t *testing.T) { malformedSig := "{\"payload\":\"eyJ0YXJnZXRBcnRpZmFjdCI6eyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLm1hbmlmZXN0LnYxK2pzb24iLCJkaWdlc3QiOiJzaGEyNTY6NzNjODAzOTMwZWEzYmExZTU0YmMyNWMyYmRjNTNlZGQwMjg0YzYyZWQ2NTFmZTdiMDAzNjlkYTUxOWEzYzMzMyIsInNpemUiOjE2NzI0LCJhbm5vdGF0aW9ucyI6eyJpby53YWJiaXQtbmV0d29ya3MuYnVpbGRJZCI6IjEyMyJ9fX0\",\"protected\":\"eyJhbGciOiJQUzM4NCIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSIsInNpZ25lZENyaXRLZXkxIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luIiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luTWluVmVyc2lvbiJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IjoiMjAyMi0wOC0wNlQxMDowNTowNy0wNzowMCIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdTY2hlbWUiOiJub3RhcnkueDUwOS5zaWduaW5nQXV0aG9yaXR5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDIyLTA4LTA1VDEwOjA1OjA3LTA3OjAwIiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luIjoiSG9sYSBQbHVnaW4iLCJpby5jbmNmLm5vdGFyeS52ZXJpZmljYXRpb25QbHVnaW5NaW5WZXJzaW9uIjoiMS4xLjEiLCJzaWduZWRDcml0S2V5MSI6InNpZ25lZFZhbHVlMSIsInNpZ25lZEtleTEiOiJzaWduZWRLZXkyIn0\",\"header\":{\"x5c\":[\"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\"],\"io.cncf.notary.SigningAgent\":\"NotationUnitTest/1.0.0\"},\"signature\":\"K5r5b2bJF15kV2Qe5NXf42SCI5_V9K0sCuHSd1bg2OFIOp3FcupjYT4yb26jsV2aE9lrsn8FNxoP-PqkV385klZ_xnTzhRO0T3S7bCL_wu2ZtzuRKp43yOjPc7TPdbd2Q1BKd5rIS05RtxfZTYF1gGIWyRMMc8pos-EgBGhlEXNK78IsH7Eh__bk6pFlY0y5TsKDx8-9h85OKL910CKtCyjP3JgLmB_STxc6iz7iSC8lBmiq_fra3lhfwgDTwTWL2I82-SNFGf3baANppjLP-W1f6ckV9PaFmbPz8hMZ_kYXMRk100IkeSz5inK8rfbCFPHeA6evjydPNO35noIY1ETy7AppB8HlctY903u_iRGh4ur4mKf4snduQbpDr9EARG0c_6styaiwhxkshkrHLKov0C_ZZPNqAZ5ItN2QuBShyNtaKzWPCPjF4EPANVnFjdEH8Up4WpShMX3-N1wQb3IQmNf9kU04YFwkTJn8HECFseGRmZAvG8x0W5PcQik5\"}" env, _ := newJWSEnvelopeFromBytes([]byte(malformedSig)) - if err := env.validateIntegrity(); !(err != nil && errors.As(err, new(MalformedSignatureError))) { + if err := env.validateIntegrity(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { t.Errorf("validateIntegrity(). Expected SignatureIntegrityError but found %q", reflect.TypeOf(err)) } }) @@ -99,7 +99,7 @@ func TestValidateIntegrity(t *testing.T) { t.Errorf("validateIntegrity(). Error = %s", err) } - if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(MalformedSignatureError))) { + if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { t.Errorf("getSignerInfo. Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) } } @@ -143,7 +143,7 @@ func TestGetSignerInfo(t *testing.T) { t.Run("with invalid singing time returns error", func(t *testing.T) { env, _ := newJWSEnvelopeFromBytes([]byte("{\"Payload\":\"eyJhbGciOiJIUzI1NiJ9\",\"Protected\":\"eyJhbGciOiJQUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiXSwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDA2LS0wMlQxNTowNDowNVoifQ\"" + ",\"Header\":{},\"Signature\":\"YjGj\"}")) - if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(MalformedSignatureError))) { + if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { t.Errorf("Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) } }) @@ -161,7 +161,7 @@ func TestGetSignerInfo(t *testing.T) { t.Errorf("validateIntegrity(). Error: %s", err.Error()) } - if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(MalformedSignatureError))) { + if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { t.Errorf("Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) } }) @@ -169,7 +169,7 @@ func TestGetSignerInfo(t *testing.T) { t.Run("with malformed alg header returns error", func(t *testing.T) { env, _ := newJWSEnvelopeFromBytes([]byte("{\"Payload\":\"eyJhbGciOiJIUzI1NiJ9\",\"Protected\":\"eyJhbGciOjEzLCJjcml0IjpbImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdUaW1lIl0sImlvLmNuY2Yubm90YXJ5LnNpbmluZ1RpbWUiOiIyMDA2LTAxLTAyVDE1OjA0OjA1WiJ9\"" + ",\"Header\":{},\"Signature\":\"YjGj\"}")) - if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(MalformedSignatureError))) { + if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { t.Errorf("Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) } }) @@ -177,7 +177,7 @@ func TestGetSignerInfo(t *testing.T) { t.Run("with malformed cty header returns error", func(t *testing.T) { env, _ := newJWSEnvelopeFromBytes([]byte("{\"Payload\":\"eyJhbGciOiJIUzI1NiJ9\",\"Protected\":\"eyJhbGciOiJQUzUxMiIsImN0eSI6MTIzLCJjcml0IjpbImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdUaW1lIl0sImlvLmNuY2Yubm90YXJ5LnNpbmluZ1RpbWUiOiIyMDA2LTAxLTAyVDE1OjA0OjA1WiJ9\"" + ",\"Header\":{},\"Signature\":\"YjGj\"}")) - if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(MalformedSignatureError))) { + if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { t.Errorf("Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) } }) @@ -210,28 +210,28 @@ func TestSignPayloadError(t *testing.T) { req := getSignRequest() t.Run("when SignatureProvider'KeySpec returns error", func(t *testing.T) { req.SignatureProvider = ErrorSignatureProvider{KeySpecError: true} - if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(MalformedSignRequestError))) { + if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(InvalidSignRequestError))) { t.Errorf("signPayload(). Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) } }) t.Run("when SignatureProvider'SignError returns error", func(t *testing.T) { req.SignatureProvider = ErrorSignatureProvider{SignError: true} - if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(MalformedSignRequestError))) { + if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(InvalidSignRequestError))) { t.Errorf("signPayload(). Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) } }) t.Run("when SignatureProvider'Sign returns invalid certificate chain", func(t *testing.T) { req.SignatureProvider = ErrorSignatureProvider{InvalidCertChain: true} - if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(MalformedSignRequestError))) { + if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(InvalidSignRequestError))) { t.Errorf("signPayload(). Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) } }) t.Run("when SignatureProvider'KeySpec returns invalid value", func(t *testing.T) { req.SignatureProvider = ErrorSignatureProvider{InvalidKeySpec: true} - if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(SignatureAlgoNotSupportedError))) { + if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(UnsupportedSignatureAlgoError))) { t.Errorf("signPayload(). Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) } }) diff --git a/signature/jwt.go b/signature/jwt.go index 15f5de73..394c1dec 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, InvalidSignatureError{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..6364de6c 100644 --- a/signature/signer.go +++ b/signature/signer.go @@ -1,296 +1,134 @@ 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 - - // 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 -} +// 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) -// UnsignedAttributes represents unsigned metadata in the Signature envelope -type UnsignedAttributes struct { - SigningAgent string -} - -// 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 -} - -// Attribute represents metadata in the Signature envelope -type Attribute struct { - Key string - Critical bool - Value interface{} -} - -// SignatureProvider is used to sign bytes generated after creating Signature envelope. -type SignatureProvider interface { - Sign([]byte) ([]byte, []*x509.Certificate, error) + // KeySpec returns the key specification. KeySpec() (KeySpec, error) } -// SignatureEnvelope provides functions to generate signature and verify signature. -type SignatureEnvelope struct { - rawSignatureEnvelope []byte - internalEnvelope internalSignatureEnvelope -} - -// Contains a set of common methods that every Signature envelope format must implement. -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) - // 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{} - } +// LocalSigner is only used by built-in signers to sign. +type LocalSigner interface { + Signer - integrityError := s.internalEnvelope.validateIntegrity() - if integrityError != nil { - return nil, integrityError - } + // CertificateChain returns the certificate chain. + CertificateChain() ([]*x509.Certificate, error) - singerInfo, singerInfoErr := s.GetSignerInfo() - if singerInfoErr != nil { - return nil, singerInfoErr - } - - return singerInfo, nil + // PrivateKey returns the private key. + PrivateKey() crypto.PrivateKey } -// 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 +// 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 } -// 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, &InvalidArgumentError{ + 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, &InvalidArgumentError{ + 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, &InvalidArgumentError{Param: "trustedCerts"} } if signerInfo == nil { - return nil, MalformedArgumentError{param: "signerInfo"} + return nil, &InvalidArgumentError{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..30a0d02d 100644 --- a/signature/types.go +++ b/signature/types.go @@ -1,103 +1,143 @@ 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 +// 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. +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..c07847f3 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, &InvalidArgumentError{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 +} From 9c1169a60d7d42710a18f7937a98c0a1d4bc1701 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Wed, 21 Sep 2022 11:06:44 +0800 Subject: [PATCH 3/3] refactor: JWS for signature package (#76) Signed-off-by: Junjie Gao --- signature/jws.go | 519 -------------------- signature/jws/conformance_test.go | 183 +++++++ signature/jws/envelope.go | 209 ++++++++ signature/jws/envelope_test.go | 603 ++++++++++++++++++++++++ signature/jws/jws.go | 261 ++++++++++ signature/jws/jws_test.go | 55 +++ signature/jws/jwt.go | 152 ++++++ signature/jws/jwt_test.go | 124 +++++ signature/jws/testdata/conformance.json | 8 + signature/jws/types.go | 115 +++++ signature/jws_test.go | 273 ----------- signature/jwt.go | 71 --- signature/jwt_test.go | 83 ---- signature/signaturetest/algorithm.go | 18 + signature/signaturetest/signer.go | 52 ++ signature/types.go | 4 + signature/utils.go | 84 ---- signature/utils_test.go | 159 ------- 18 files changed, 1784 insertions(+), 1189 deletions(-) delete mode 100644 signature/jws.go create mode 100644 signature/jws/conformance_test.go create mode 100644 signature/jws/envelope.go create mode 100644 signature/jws/envelope_test.go create mode 100644 signature/jws/jws.go create mode 100644 signature/jws/jws_test.go create mode 100644 signature/jws/jwt.go create mode 100644 signature/jws/jwt_test.go create mode 100644 signature/jws/testdata/conformance.json create mode 100644 signature/jws/types.go delete mode 100644 signature/jws_test.go delete mode 100644 signature/jwt.go delete mode 100644 signature/jwt_test.go create mode 100644 signature/signaturetest/algorithm.go create mode 100644 signature/signaturetest/signer.go delete mode 100644 signature/utils.go delete mode 100644 signature/utils_test.go diff --git a/signature/jws.go b/signature/jws.go deleted file mode 100644 index 86763867..00000000 --- a/signature/jws.go +++ /dev/null @@ -1,519 +0,0 @@ -package signature - -import ( - "crypto/x509" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "strings" - "time" -) - -const ( - MediaTypeJWSJson SignatureMediaType = "application/jose+json" -) - -const ( - headerKeyExpiry = "io.cncf.notary.expiry" - headerKeySigningTime = "io.cncf.notary.signingTime" - headerKeyAuthenticSigningTime = "io.cncf.notary.authenticSigningTime" - headerKeySigningScheme = "io.cncf.notary.signingScheme" - headerKeyVerificationPlugin = "io.cncf.notary.verificationPlugin" - headerKeyVerificationPluginMinVersion = "io.cncf.notary.verificationPluginMinVersion" - headerKeyCrit = "crit" - headerKeyAlg = "alg" - headerKeyCty = "cty" -) - -var signatureAlgJWSAlgMap = map[SignatureAlgorithm]string{ - RSASSA_PSS_SHA_256: "PS256", - RSASSA_PSS_SHA_384: "PS384", - RSASSA_PSS_SHA_512: "PS512", - ECDSA_SHA_256: "ES256", - ECDSA_SHA_384: "ES384", - ECDSA_SHA_512: "ES512", -} - -var jwsAlgSignatureAlgMap = reverseMap(signatureAlgJWSAlgMap) - -// jwsEnvelope represents implements internalSignatureEnvelope interface. -type jwsEnvelope struct { - internalEnv *jwsInternalEnvelope -} - -func newJWSEnvelopeFromBytes(envelopeBytes []byte) (*jwsEnvelope, error) { - jwsInternal, err := newJwsInternalEnvelopeFromBytes(envelopeBytes) - if err != nil { - return nil, err - } - - return &jwsEnvelope{internalEnv: jwsInternal}, nil -} - -func (jws *jwsEnvelope) validateIntegrity() error { - if jws.internalEnv == nil { - return SignatureNotFoundError{} - } - - if len(jws.internalEnv.Header.CertChain) == 0 { - return InvalidSignatureError{Msg: "malformed leaf certificate"} - } - - cert, err := x509.ParseCertificate(jws.internalEnv.Header.CertChain[0]) - if err != nil { - return InvalidSignatureError{Msg: "malformed leaf certificate"} - } - - // verify JWT - compact := strings.Join([]string{jws.internalEnv.Protected, jws.internalEnv.Payload, jws.internalEnv.Signature}, ".") - return verifyJWT(compact, cert.PublicKey) -} - -func (jws *jwsEnvelope) signPayload(req SignRequest) ([]byte, error) { - errorFunc := func(s string) error { - return InvalidSignRequestError{msg: s} - } - - ks, err := req.SignatureProvider.KeySpec() - if err != nil { - return nil, errorFunc(err.Error()) - } - alg := ks.SignatureAlgorithm() - - signedAttrs, err := getSignedAttrs(req, alg) - if err != nil { - return nil, err - } - - compact, certs, err := sign(req.Payload, signedAttrs, req.SignatureProvider) - if err != nil { - return nil, errorFunc(err.Error()) - } - - // not performed by SignatureEnvelope's Sign function as we don't have access to certificates. - if err := validateCertificateChain(certs, req.SigningTime, alg, errorFunc); err != nil { - return nil, err - } - - j, err := generateJws(compact, req, certs) - if err != nil { - return nil, err - } - - b, err := json.Marshal(j) - if err != nil { - return nil, err - } - jws.internalEnv = j - return b, nil -} - -func (jws *jwsEnvelope) getSignerInfo() (*EnvelopeContent, error) { - signInfo := EnvelopeContent{} - if jws.internalEnv == nil { - return nil, SignatureNotFoundError{} - } - - // parse payload - payload, err := base64.RawURLEncoding.DecodeString(jws.internalEnv.Payload) - if err != nil { - return nil, err - } - signInfo.Payload = payload - - // parse protected headers - protected, err := parseProtectedHeaders(jws.internalEnv.Protected) - if err != nil { - return nil, err - } - if err := populateProtectedHeaders(protected, &signInfo); err != nil { - return nil, err - } - - // parse signature - sig, err := base64.RawURLEncoding.DecodeString(jws.internalEnv.Signature) - if err != nil { - return nil, err - } - signInfo.Signature = sig - - // parse headers - var certs []*x509.Certificate - for _, certBytes := range jws.internalEnv.Header.CertChain { - cert, err := x509.ParseCertificate(certBytes) - if err != nil { - return nil, err - } - certs = append(certs, cert) - } - signInfo.CertificateChain = certs - signInfo.UnsignedAttributes.SigningAgent = jws.internalEnv.Header.SigningAgent - signInfo.TimestampSignature = jws.internalEnv.Header.TimestampSignature - - return &signInfo, nil -} - -func parseProtectedHeaders(encoded string) (*jwsProtectedHeader, error) { - rawProtected, err := base64.RawURLEncoding.DecodeString(encoded) - if err != nil { - return nil, InvalidSignatureError{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. - // We unmarshal twice: once into a value of type jwsProtectedHeader and once into a value of type jwsProtectedHeader.ExtendedAttributes(map[string]interface{}) - // and removing the keys are already been defined in jwsProtectedHeader. - var protected jwsProtectedHeader - if err = json.Unmarshal(rawProtected, &protected); err != nil { - return nil, InvalidSignatureError{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, InvalidSignatureError{Msg: fmt.Sprintf("jws envelope protected header can't be decoded: %s", err.Error())} - } - - // delete attributes that are already defined in jwsProtectedHeader. - delete(protected.ExtendedAttributes, headerKeyAlg) - delete(protected.ExtendedAttributes, headerKeyCty) - delete(protected.ExtendedAttributes, headerKeyCrit) - delete(protected.ExtendedAttributes, headerKeySigningTime) - delete(protected.ExtendedAttributes, headerKeyExpiry) - delete(protected.ExtendedAttributes, headerKeyAuthenticSigningTime) - delete(protected.ExtendedAttributes, headerKeySigningScheme) - delete(protected.ExtendedAttributes, headerKeyVerificationPlugin) - delete(protected.ExtendedAttributes, headerKeyVerificationPluginMinVersion) - - return &protected, nil -} - -func populateProtectedHeaders(protectedHdr *jwsProtectedHeader, signInfo *EnvelopeContent) error { - err := validateProtectedHeaders(protectedHdr) - if err != nil { - return err - } - - if signInfo.SignatureAlgorithm, err = getSignatureAlgo(protectedHdr.Algorithm); err != nil { - return err - } - - signInfo.PayloadContentType = protectedHdr.ContentType - signInfo.SignedAttributes.ExtendedAttributes = getExtendedAttributes(protectedHdr.ExtendedAttributes, protectedHdr.Critical) - signInfo.SigningScheme = protectedHdr.SigningScheme - signInfo.SignedAttributes.VerificationPlugin = protectedHdr.VerificationPlugin - signInfo.SignedAttributes.VerificationPluginMinVersion = protectedHdr.VerificationPluginMinVersion - if protectedHdr.Expiry != nil { - signInfo.SignedAttributes.Expiry = *protectedHdr.Expiry - } - switch protectedHdr.SigningScheme { - case SigningSchemeX509: - if protectedHdr.SigningTime != nil { - signInfo.SignedAttributes.SigningTime = *protectedHdr.SigningTime - } - case SigningSchemeX509SigningAuthority: - if protectedHdr.AuthenticSigningTime != nil { - signInfo.SignedAttributes.SigningTime = *protectedHdr.AuthenticSigningTime - } - } - return nil -} - -func getExtendedAttributes(attrs map[string]interface{}, critical []string) []Attribute { - extendedAttr := make([]Attribute, 0, len(attrs)) - for key, value := range attrs { - extendedAttr = append(extendedAttr, Attribute{ - Key: key, - Critical: contains(critical, key), - Value: value, - }) - } - return extendedAttr -} - -func validateProtectedHeaders(protectedHdr *jwsProtectedHeader) error { - // validate headers that should not be present as per signing schemes - switch protectedHdr.SigningScheme { - case SigningSchemeX509: - if protectedHdr.AuthenticSigningTime != nil { - return InvalidSignatureError{Msg: fmt.Sprintf("%q header must not be present for %s signing scheme", headerKeyAuthenticSigningTime, SigningSchemeX509)} - } - case SigningSchemeX509SigningAuthority: - if protectedHdr.SigningTime != nil { - return InvalidSignatureError{Msg: fmt.Sprintf("%q header must not be present for %s signing scheme", headerKeySigningTime, SigningSchemeX509SigningAuthority)} - } - if protectedHdr.AuthenticSigningTime == nil { - return InvalidSignatureError{Msg: fmt.Sprintf("%q header must be present for %s signing scheme", headerKeyAuthenticSigningTime, SigningSchemeX509)} - } - } - - return validateCriticalHeaders(protectedHdr) -} - -// validateCriticalHeaders validates headers that should be present or marked critical as per singing scheme -func validateCriticalHeaders(protectedHdr *jwsProtectedHeader) error { - if len(protectedHdr.Critical) == 0 { - return InvalidSignatureError{"missing `crit` header"} - } - - mustMarkedCrit := map[string]bool{headerKeySigningScheme: true} - if protectedHdr.Expiry != nil && !protectedHdr.Expiry.IsZero() { - mustMarkedCrit[headerKeyExpiry] = true - } - - if protectedHdr.SigningScheme == SigningSchemeX509SigningAuthority { - mustMarkedCrit[headerKeyAuthenticSigningTime] = true - } - - if protectedHdr.VerificationPlugin != "" { - mustMarkedCrit[headerKeyVerificationPlugin] = true - } - - if protectedHdr.VerificationPluginMinVersion != "" { - mustMarkedCrit[headerKeyVerificationPluginMinVersion] = true - } - - for _, val := range protectedHdr.Critical { - if _, ok := mustMarkedCrit[val]; ok { - delete(mustMarkedCrit, val) - } else { - if _, ok := protectedHdr.ExtendedAttributes[val]; !ok { - return InvalidSignatureError{Msg: fmt.Sprintf("%q header is marked critical but not present", val)} - } - } - } - - // validate all required critical headers are present. - if len(mustMarkedCrit) != 0 { - // This is not taken care by VerifySignerInfo method - keys := make([]string, 0, len(mustMarkedCrit)) - for k := range mustMarkedCrit { - keys = append(keys, k) - } - return InvalidSignatureError{fmt.Sprintf("these required headers are not marked as critical: %v", keys)} - } - - return nil -} - -func getSignedAttrs(req SignRequest, sigAlg SignatureAlgorithm) (map[string]interface{}, error) { - extAttrs := make(map[string]interface{}) - crit := []string{headerKeySigningScheme} - - for _, elm := range req.ExtendedSignedAttrs { - extAttrs[elm.Key] = elm.Value - if elm.Critical { - crit = append(crit, elm.Key) - } - } - - alg, err := getJWSAlgo(sigAlg) - if err != nil { - return nil, err - } - - jwsProtectedHdr := jwsProtectedHeader{ - Algorithm: alg, - ContentType: req.PayloadContentType, - SigningScheme: req.SigningScheme, - } - - switch req.SigningScheme { - case SigningSchemeX509: - jwsProtectedHdr.SigningTime = &req.SigningTime - case SigningSchemeX509SigningAuthority: - crit = append(crit, headerKeyAuthenticSigningTime) - jwsProtectedHdr.AuthenticSigningTime = &req.SigningTime - } - - if !req.Expiry.IsZero() { - crit = append(crit, headerKeyExpiry) - jwsProtectedHdr.Expiry = &req.Expiry - } - if strings.TrimSpace(req.VerificationPlugin) != "" { - crit = append(crit, headerKeyVerificationPlugin) - jwsProtectedHdr.VerificationPlugin = req.VerificationPlugin - } - if strings.TrimSpace(req.VerificationPluginMinVersion) != "" { - crit = append(crit, headerKeyVerificationPluginMinVersion) - jwsProtectedHdr.VerificationPluginMinVersion = req.VerificationPluginMinVersion - } - - jwsProtectedHdr.Critical = crit - m, err := convertToMap(jwsProtectedHdr) - if err != nil { - return nil, InvalidSignRequestError{msg: fmt.Sprintf("unexpected error occured while creating protected headers, Error: %s", err.Error())} - } - - return mergeMaps(m, extAttrs), nil -} - -// *********************************************************************** -// jwsEnvelope-JSON specific code -// *********************************************************************** - -// jwsInternalEnvelope is the final Signature envelope. -type jwsInternalEnvelope struct { - // JWSPayload Base64URL-encoded. - Payload string `json:"payload"` - - // jwsProtectedHeader Base64URL-encoded. - Protected string `json:"protected"` - - // Signature metadata that is not integrity Protected - Header jwsUnprotectedHeader `json:"header"` - - // Base64URL-encoded Signature. - Signature string `json:"signature"` -} - -// jwsProtectedHeader contains the set of protected headers. -type jwsProtectedHeader struct { - // Defines which algorithm was used to generate the signature. - Algorithm string `json:"alg"` - - // Media type of the secured content (the payload). - ContentType MediaTypePayloadV1 `json:"cty"` - - // Lists the headers that implementation MUST understand and process. - Critical []string `json:"crit,omitempty"` - - // The "best by use" time for the artifact, as defined by the signer. - Expiry *time.Time `json:"io.cncf.notary.expiry,omitempty"` - - // Specifies the Notary v2 Signing Scheme used by the signature. - SigningScheme SigningScheme `json:"io.cncf.notary.signingScheme"` - - // The time at which the signature was generated. only valid when signing scheme is `notary.x509` - SigningTime *time.Time `json:"io.cncf.notary.signingTime,omitempty"` - - // The time at which the signature was generated. only valid when signing scheme is `notary.x509.signingAuthority` - AuthenticSigningTime *time.Time `json:"io.cncf.notary.authenticSigningTime,omitempty"` - - // VerificationPlugin specifies the name of the verification plugin that should be used to verify the signature. - VerificationPlugin string `json:"io.cncf.notary.verificationPlugin,omitempty"` - - // VerificationPluginMinVersion specifies the minimum version of the verification plugin that should be used to verify the signature. - VerificationPluginMinVersion string `json:"io.cncf.notary.verificationPluginMinVersion,omitempty"` - - // The user defined attributes. - ExtendedAttributes map[string]interface{} `json:"-"` -} - -// jwsUnprotectedHeader contains the set of unprotected headers. -type jwsUnprotectedHeader struct { - // RFC3161 time stamp token Base64-encoded. - TimestampSignature []byte `json:"io.cncf.notary.TimestampSignature,omitempty"` - - // List of X.509 Base64-DER-encoded certificates - // as defined at https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6. - CertChain [][]byte `json:"x5c"` - - // SigningAgent used for signing - SigningAgent string `json:"io.cncf.notary.SigningAgent,omitempty"` -} - -func newJwsInternalEnvelopeFromBytes(b []byte) (*jwsInternalEnvelope, error) { - var jws jwsInternalEnvelope - if err := json.Unmarshal(b, &jws); err != nil { - return nil, err - } - return &jws, nil -} - -func generateJws(compact string, req SignRequest, certs []*x509.Certificate) (*jwsInternalEnvelope, error) { - parts := strings.Split(compact, ".") - if len(parts) != 3 { - // this should never happen - return nil, errors.New("unexpected error occurred while generating a JWS-JSON serialization from compact serialization") - } - - rawCerts := make([][]byte, len(certs)) - for i, cert := range certs { - rawCerts[i] = cert.Raw - } - - return &jwsInternalEnvelope{ - Protected: parts[0], - Payload: parts[1], - Signature: parts[2], - Header: jwsUnprotectedHeader{ - CertChain: rawCerts, - SigningAgent: req.SigningAgent, - }, - }, nil -} - -// sign the given payload and headers using the given signing method and signature provider -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) - } - protectedRaw := base64.RawURLEncoding.EncodeToString(jsonPHeaders) - payloadRaw := base64.RawURLEncoding.EncodeToString(payload) - signingString := protectedRaw + "." + payloadRaw - - sigB, certs, err := sigPro.Sign([]byte(signingString)) - if err != nil { - return "", nil, fmt.Errorf("failed to sign digest. error : %v", err) - } - finalSig := signingString + "." + base64.RawURLEncoding.EncodeToString(sigB) - return finalSig, certs, err -} - -func getSignatureAlgo(alg string) (SignatureAlgorithm, error) { - signatureAlg, ok := jwsAlgSignatureAlgMap[alg] - if !ok { - return "", UnsupportedSignatureAlgoError{alg: alg} - } - - return signatureAlg, nil -} - -func getJWSAlgo(alg SignatureAlgorithm) (string, error) { - jwsAlg, ok := signatureAlgJWSAlgMap[alg] - if !ok { - return "", UnsupportedSignatureAlgoError{alg: string(alg)} - } - - return jwsAlg, nil -} - -func reverseMap(m map[SignatureAlgorithm]string) map[string]SignatureAlgorithm { - n := make(map[string]SignatureAlgorithm, len(m)) - for k, v := range m { - n[v] = k - } - return n -} - -func convertToMap(i interface{}) (map[string]interface{}, error) { - s, err := json.Marshal(i) - if err != nil { - return nil, err - } - - var m map[string]interface{} - if err := json.Unmarshal(s, &m); err != nil { - return nil, err - } - - return m, nil -} - -func mergeMaps(maps ...map[string]interface{}) map[string]interface{} { - result := make(map[string]interface{}) - for _, m := range maps { - for k, v := range m { - result[k] = v - } - } - return result -} - -func contains(s []string, e string) bool { - for _, a := range s { - if a == e { - return true - } - } - return false -} diff --git a/signature/jws/conformance_test.go b/signature/jws/conformance_test.go new file mode 100644 index 00000000..92e40f2a --- /dev/null +++ b/signature/jws/conformance_test.go @@ -0,0 +1,183 @@ +package jws + +import ( + "crypto/elliptic" + "crypto/x509" + "encoding/json" + "os" + "reflect" + "sort" + "strings" + "testing" + "time" + + "github.com/notaryproject/notation-core-go/signature" + "github.com/notaryproject/notation-core-go/testhelper" +) + +var ( + // prepare signing time + signingTime, _ = time.Parse("2006-01-02 15:04:05", "2022-08-29 13:50:00") + expiry, _ = time.Parse("2006-01-02 15:04:05", "2099-08-29 13:50:00") + // signedAttributes for signing request + signedAttributes = signature.SignedAttributes{ + SigningScheme: "notary.x509", + SigningTime: signingTime, + Expiry: expiry.Add(time.Hour * 24), + ExtendedAttributes: sortAttributes([]signature.Attribute{ + {Key: "signedCritKey1", Value: "signedCritValue1", Critical: true}, + {Key: "signedKey1", Value: "signedValue1", Critical: false}, + {Key: "signedKey2", Value: "signedValue1", Critical: false}, + {Key: "signedKey3", Value: "signedValue1", Critical: false}, + {Key: "signedKey4", Value: "signedValue1", Critical: false}, + }), + } + // unsignedAttributes for signing request + unsignedAttributes = signature.UnsignedAttributes{ + SigningAgent: "NotationConformanceTest/1.0.0", + } + // payload to be signed + payload = signature.Payload{ + ContentType: "application/vnd.cncf.notary.payload.v1+json", + Content: []byte(`{"key":"hello JWS"}`), + } + // certificate chain for signer + leafCertTuple = testhelper.GetECCertTuple(elliptic.P256()) + certs = []*x509.Certificate{leafCertTuple.Cert, testhelper.GetECRootCertificate().Cert} +) + +func conformanceTestSignReq() *signature.SignRequest { + signer, err := signature.NewLocalSigner(certs, leafCertTuple.PrivateKey) + if err != nil { + panic(err) + } + + return &signature.SignRequest{ + Payload: payload, + Signer: signer, + SigningTime: signedAttributes.SigningTime, + Expiry: signedAttributes.Expiry, + ExtendedSignedAttributes: signedAttributes.ExtendedAttributes, + SigningAgent: unsignedAttributes.SigningAgent, + SigningScheme: signedAttributes.SigningScheme, + } +} + +// TestSignedMessageConformance check the conformance between the encoded message +// and the valid encoded message in conformance.json +// +// check payload, protected and signingAgent section +func TestSignedMessageConformance(t *testing.T) { + // get encoded message + env := envelope{} + signReq := conformanceTestSignReq() + encoded, err := env.Sign(signReq) + checkNoError(t, err) + + // parse encoded message to be a map + envMap, err := unmarshalEncodedMessage(encoded) + checkNoError(t, err) + // load validation encoded message + validEnvMap, err := getValidEnvelopeMap() + checkNoError(t, err) + + // check payload section conformance + if !reflect.DeepEqual(envMap["payload"], validEnvMap["payload"]) { + t.Fatal("signed message payload test failed.") + } + + // check protected section conformance + if !reflect.DeepEqual(envMap["protected"], validEnvMap["protected"]) { + t.Fatal("signed message protected test failed.") + } + + // prepare header + header, ok := envMap["header"].(map[string]interface{}) + if !ok { + t.Fatal("signed message header format error.") + } + validHeader, ok := validEnvMap["header"].(map[string]interface{}) + if !ok { + t.Fatal("conformance.json header format error.") + } + // check io.cncf.notary.signingAgent conformance + if !reflect.DeepEqual(header["io.cncf.notary.signingAgent"], validHeader["io.cncf.notary.signingAgent"]) { + t.Fatal("signed message signingAgent test failed.") + } +} + +func getValidEnvelopeMap() (map[string]interface{}, error) { + encoded, err := os.ReadFile("./testdata/conformance.json") + if err != nil { + return nil, err + } + return unmarshalEncodedMessage(encoded) +} + +func unmarshalEncodedMessage(encoded []byte) (envelopeMap map[string]interface{}, err error) { + err = json.Unmarshal(encoded, &envelopeMap) + return +} + +// TestVerifyConformance generates JWS encoded message, parses the encoded message and +// verify the payload, signed/unsigned attributes conformance. +func TestVerifyConformance(t *testing.T) { + env := envelope{} + signReq := conformanceTestSignReq() + encoded, err := env.Sign(signReq) + checkNoError(t, err) + + // parse envelope + var e jwsEnvelope + err = json.Unmarshal(encoded, &e) + checkNoError(t, err) + newEnv := envelope{base: &e} + + // verify validity + content, err := newEnv.Verify() + checkNoError(t, err) + + // check payload conformance + verifyPayload(t, &content.Payload) + + // check signed/unsigned attributes conformance + verifyAttributes(t, &content.SignerInfo) +} + +func verifyPayload(t *testing.T, gotPayload *signature.Payload) { + if !reflect.DeepEqual(&payload, gotPayload) { + t.Fatalf("verify payload failed. want: %+v got: %+v\n", &payload, gotPayload) + } +} + +func verifyAttributes(t *testing.T, signerInfo *signature.SignerInfo) { + // check unsigned attributes + if !reflect.DeepEqual(&unsignedAttributes, &signerInfo.UnsignedAttributes) { + t.Fatalf("verify UnsignedAttributes failed. want: %+v got: %+v\n", &unsignedAttributes, &signerInfo.UnsignedAttributes) + } + + // check signed attributes + sortAttributes(signerInfo.SignedAttributes.ExtendedAttributes) + if !reflect.DeepEqual(&signedAttributes, &signerInfo.SignedAttributes) { + t.Fatalf("verify SignedAttributes failed. want: %+v got: %+v\n", &signedAttributes, &signerInfo.SignedAttributes) + } + + // check signature algorithm + keySpec, err := signature.ExtractKeySpec(certs[0]) + checkNoError(t, err) + if keySpec.SignatureAlgorithm() != signerInfo.SignatureAlgorithm { + t.Fatalf("verify signature algorithm failed. want: %d got: %d\n", keySpec.SignatureAlgorithm(), signerInfo.SignatureAlgorithm) + } + + // check certificate chain + if !reflect.DeepEqual(signerInfo.CertificateChain, certs) { + t.Fatalf("verify certificate chain failed. want: %+v got: %+v\n", &signerInfo.CertificateChain, certs) + } +} + +func sortAttributes(attributes []signature.Attribute) []signature.Attribute { + sort.Slice(attributes, func(i, j int) bool { + return strings.Compare(attributes[i].Key, attributes[j].Key) < 0 + }) + return attributes +} diff --git a/signature/jws/envelope.go b/signature/jws/envelope.go new file mode 100644 index 00000000..87c7544f --- /dev/null +++ b/signature/jws/envelope.go @@ -0,0 +1,209 @@ +package jws + +import ( + "crypto/x509" + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/golang-jwt/jwt/v4" + "github.com/notaryproject/notation-core-go/signature" + "github.com/notaryproject/notation-core-go/signature/internal/base" +) + +// MediaTypeEnvelope defines the media type name of JWS envelope. +const MediaTypeEnvelope = "application/jose+json" + +func init() { + if err := signature.RegisterEnvelopeType(MediaTypeEnvelope, NewEnvelope, ParseEnvelope); err != nil { + panic(err) + } +} + +type envelope struct { + base *jwsEnvelope +} + +// NewEnvelope generates an JWS envelope. +func NewEnvelope() signature.Envelope { + return &base.Envelope{ + Envelope: &envelope{}, + } +} + +// ParseEnvelope parses the envelope bytes and return a JWS envelope. +func ParseEnvelope(envelopeBytes []byte) (signature.Envelope, error) { + var e jwsEnvelope + err := json.Unmarshal(envelopeBytes, &e) + if err != nil { + return nil, &signature.InvalidSignatureError{Msg: err.Error()} + } + return &base.Envelope{ + Envelope: &envelope{base: &e}, + Raw: envelopeBytes, + }, nil +} + +// Sign generates and sign the envelope according to the sign request. +func (e *envelope) Sign(req *signature.SignRequest) ([]byte, error) { + // get signingMethod for JWT package + method, err := getSigningMethod(req.Signer) + if err != nil { + return nil, &signature.InvalidSignRequestError{Msg: err.Error()} + } + + // get all attributes ready to be signed + signedAttrs, err := getSignedAttributes(req, method.Alg()) + if err != nil { + return nil, &signature.InvalidSignRequestError{Msg: err.Error()} + } + + // parse payload as jwt.MapClaims + // [jwt-go]: https://pkg.go.dev/github.com/dgrijalva/jwt-go#MapClaims + var payload jwt.MapClaims + if err = json.Unmarshal(req.Payload.Content, &payload); err != nil { + return nil, &signature.InvalidSignRequestError{ + Msg: fmt.Sprintf("payload format error: %v", err.Error())} + } + + // JWT sign and get certificate chain + compact, certs, err := sign(payload, signedAttrs, method) + if err != nil { + return nil, &signature.InvalidSignRequestError{Msg: err.Error()} + } + + // generate envelope + env, err := generateJWS(compact, req, certs) + if err != nil { + return nil, &signature.InvalidSignatureError{Msg: err.Error()} + } + + encoded, err := json.Marshal(env) + if err != nil { + return nil, &signature.InvalidSignatureError{Msg: err.Error()} + } + e.base = env + return encoded, nil +} + +// Verify verifies the envelope and returns its enclosed payload and signer info. +func (e *envelope) Verify() (*signature.EnvelopeContent, error) { + if e.base == nil { + return nil, &signature.SignatureEnvelopeNotFoundError{} + } + + if len(e.base.Header.CertChain) == 0 { + return nil, &signature.InvalidSignatureError{Msg: "certificate chain is not present"} + } + + cert, err := x509.ParseCertificate(e.base.Header.CertChain[0]) + if err != nil { + return nil, &signature.InvalidSignatureError{Msg: "malformed leaf certificate"} + } + + // verify JWT + compact := compactJWS(e.base) + if err = verifyJWT(compact, cert.PublicKey); err != nil { + return nil, err + } + + return e.Content() +} + +// Content returns the payload and signer information of the envelope. +// Content is trusted only after the successful call to `Verify()`. +func (e *envelope) Content() (*signature.EnvelopeContent, error) { + if e.base == nil { + return nil, &signature.SignatureEnvelopeNotFoundError{} + } + + // parse protected headers + protected, err := parseProtectedHeaders(e.base.Protected) + if err != nil { + return nil, err + } + + // extract payload + payload, err := e.payload(protected) + if err != nil { + return nil, err + } + + // extract signer info + signerInfo, err := e.signerInfo(protected) + if err != nil { + return nil, err + } + return &signature.EnvelopeContent{ + SignerInfo: *signerInfo, + Payload: *payload, + }, nil +} + +// payload returns the payload of JWS envelope. +func (e *envelope) payload(protected *jwsProtectedHeader) (*signature.Payload, error) { + payload, err := base64.RawURLEncoding.DecodeString(e.base.Payload) + if err != nil { + return nil, &signature.InvalidSignatureError{ + Msg: fmt.Sprintf("payload error: %v", err)} + } + + return &signature.Payload{ + Content: payload, + ContentType: protected.ContentType, + }, nil +} + +// signerInfo returns the SignerInfo of JWS envelope. +func (e *envelope) signerInfo(protected *jwsProtectedHeader) (*signature.SignerInfo, error) { + var signerInfo signature.SignerInfo + + // populate protected header to signerInfo + if err := populateProtectedHeaders(protected, &signerInfo); err != nil { + return nil, err + } + + // parse signature + sig, err := base64.RawURLEncoding.DecodeString(e.base.Signature) + if err != nil { + return nil, &signature.InvalidSignatureError{Msg: err.Error()} + } + if len(sig) == 0 { + return nil, &signature.InvalidSignatureError{Msg: "signature missing in jws-json envelope"} + } + signerInfo.Signature = sig + + // parse headers + var certs []*x509.Certificate + for _, certBytes := range e.base.Header.CertChain { + cert, err := x509.ParseCertificate(certBytes) + if err != nil { + return nil, &signature.InvalidSignatureError{Msg: err.Error()} + } + certs = append(certs, cert) + } + signerInfo.CertificateChain = certs + signerInfo.UnsignedAttributes.SigningAgent = e.base.Header.SigningAgent + signerInfo.UnsignedAttributes.TimestampSignature = e.base.Header.TimestampSignature + return &signerInfo, nil +} + +// sign the given payload and headers using the given signature provider. +func sign(payload jwt.MapClaims, headers map[string]interface{}, method signingMethod) (string, []*x509.Certificate, error) { + // generate token + token := jwt.NewWithClaims(method, payload) + token.Header = headers + + // sign and return compact JWS + compact, err := token.SignedString(method.PrivateKey()) + if err != nil { + return "", nil, err + } + + // access certificate chain after sign + certs, err := method.CertificateChain() + if err != nil { + return "", nil, err + } + return compact, certs, nil +} diff --git a/signature/jws/envelope_test.go b/signature/jws/envelope_test.go new file mode 100644 index 00000000..bcd49130 --- /dev/null +++ b/signature/jws/envelope_test.go @@ -0,0 +1,603 @@ +package jws + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "math" + "reflect" + "strings" + "testing" + "time" + + "github.com/notaryproject/notation-core-go/signature" + "github.com/notaryproject/notation-core-go/signature/signaturetest" + "github.com/notaryproject/notation-core-go/testhelper" +) + +// remoteMockSigner is used to mock remote signer +type remoteMockSigner struct { + privateKey crypto.PrivateKey + certs []*x509.Certificate +} + +// Sign signs the digest and returns the raw signature +func (signer *remoteMockSigner) Sign(payload []byte) ([]byte, []*x509.Certificate, error) { + // calculate hash + keySpec, err := signer.KeySpec() + if err != nil { + return nil, nil, err + } + + // calculate hash + hasher := keySpec.SignatureAlgorithm().Hash().HashFunc() + h := hasher.New() + h.Write(payload) + hash := h.Sum(nil) + + // sign + switch key := signer.privateKey.(type) { + case *rsa.PrivateKey: + sig, err := rsa.SignPSS(rand.Reader, key, hasher.HashFunc(), hash, &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}) + if err != nil { + return nil, nil, err + } + return sig, signer.certs, nil + case *ecdsa.PrivateKey: + r, s, err := ecdsa.Sign(rand.Reader, key, hash) + if err != nil { + return nil, nil, err + } + + curveBits := key.Curve.Params().BitSize + keyBytes := curveBits / 8 + if curveBits%8 > 0 { + keyBytes += 1 + } + + out := make([]byte, 2*keyBytes) + r.FillBytes(out[0:keyBytes]) // r is assigned to the first half of output. + s.FillBytes(out[keyBytes:]) // s is assigned to the second half of output. + return out, signer.certs, nil + } + + return nil, nil, &signature.UnsupportedSigningKeyError{} +} + +// KeySpec returns the key specification +func (signer *remoteMockSigner) KeySpec() (signature.KeySpec, error) { + return signature.ExtractKeySpec(signer.certs[0]) +} + +func checkNoError(t *testing.T, err error) { + if err != nil { + t.Fatal(err) + } +} + +func checkErrorEqual(t *testing.T, want, got string) { + if !strings.Contains(got, want) { + t.Fatalf("want: %v got: %v\n", want, got) + } +} + +var ( + extSignedAttr = []signature.Attribute{ + { + Key: "testKey", + Critical: true, + Value: "testValue", + }, + { + Key: "testKey2", + Critical: false, + Value: "testValue2", + }, + } + extSignedAttrRepeated = []signature.Attribute{ + { + Key: "cty", + Critical: false, + Value: "testValue2", + }, + } + extSignedAttrErrorValue = []signature.Attribute{ + { + Key: "add", + Critical: false, + Value: math.Inf(1), + }, + } +) + +func getSigningCerts() []*x509.Certificate { + return []*x509.Certificate{testhelper.GetRSALeafCertificate().Cert, testhelper.GetRSARootCertificate().Cert} +} + +func getSignReq(signingScheme signature.SigningScheme, signer signature.Signer, extendedSignedAttribute []signature.Attribute) (*signature.SignRequest, error) { + payloadBytes := []byte(`{ + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", + "size": 16724, + "annotations": { + "io.wabbit-networks.buildId": "123" + } + } +}`) + return &signature.SignRequest{ + Payload: signature.Payload{ + ContentType: "application/vnd.cncf.notary.payload.v1+json", + Content: payloadBytes, + }, + Signer: signer, + SigningTime: time.Now(), + Expiry: time.Now().Add(time.Hour), + ExtendedSignedAttributes: extendedSignedAttribute, + SigningAgent: "Notation/1.0.0", + SigningScheme: signingScheme, + }, nil +} + +func getSigner(isLocal bool, certs []*x509.Certificate, privateKey *rsa.PrivateKey) (signature.Signer, error) { + if certs == nil { + certs = getSigningCerts() + } + if privateKey == nil { + privateKey = testhelper.GetRSALeafCertificate().PrivateKey + } + if isLocal { + return signature.NewLocalSigner(certs, privateKey) + } + + return &remoteMockSigner{ + certs: certs, + privateKey: privateKey, + }, nil +} + +func getEnvelope(signingScheme signature.SigningScheme, isLocal bool, extendedSignedAttribute []signature.Attribute) (*jwsEnvelope, error) { + encoded, err := getEncodedMessage(signingScheme, isLocal, extendedSignedAttribute) + if err != nil { + return nil, err + } + var sigEnv jwsEnvelope + err = json.Unmarshal(encoded, &sigEnv) + if err != nil { + return nil, err + } + return &sigEnv, nil +} + +func getEncodedMessage(signingScheme signature.SigningScheme, isLocal bool, extendedSignedAttribute []signature.Attribute) ([]byte, error) { + signer, err := getSigner(isLocal, nil, nil) + if err != nil { + return nil, err + } + + signReq, err := getSignReq(signingScheme, signer, extendedSignedAttribute) + if err != nil { + return nil, err + } + e := envelope{} + return e.Sign(signReq) +} + +func getSignedEnvelope(signingScheme signature.SigningScheme, isLocal bool, extendedSignedAttribute []signature.Attribute) (*jwsEnvelope, error) { + encoded, err := getEncodedMessage(signingScheme, isLocal, extendedSignedAttribute) + if err != nil { + return nil, err + } + var env jwsEnvelope + err = json.Unmarshal(encoded, &env) + if err != nil { + return nil, err + } + return &env, nil +} + +func verifyEnvelope(env *jwsEnvelope) error { + newEncoded, err := json.Marshal(env) + if err != nil { + return err + } + _, err = verifyCore(newEncoded) + return err +} + +func verifyCore(encoded []byte) (*signature.EnvelopeContent, error) { + env, err := ParseEnvelope(encoded) + if err != nil { + return nil, err + } + return env.Verify() +} + +func TestNewEnvelope(t *testing.T) { + env := NewEnvelope() + if env == nil { + t.Fatal("should get an JWS envelope") + } +} + +// Test the same key exists both in extended signed attributes and protected header +func TestSignFailed(t *testing.T) { + t.Run("extended attribute conflict with protected header keys", func(t *testing.T) { + _, err := getEncodedMessage(signature.SigningSchemeX509, true, extSignedAttrRepeated) + checkErrorEqual(t, "attribute key:cty repeated", err.Error()) + }) + + t.Run("extended attribute error value", func(t *testing.T) { + _, err := getEncodedMessage(signature.SigningSchemeX509, true, extSignedAttrErrorValue) + checkErrorEqual(t, "json: unsupported value: +Inf", err.Error()) + }) + + t.Run("unsupported sign algorithm", func(t *testing.T) { + signer := errorLocalSigner{ + algType: signature.KeyTypeRSA, + size: 222, + } + _, err := getEncodedMessage(signature.SigningSchemeX509, true, nil) + checkNoError(t, err) + + signReq, err := getSignReq(signature.SigningSchemeX509, &signer, nil) + checkNoError(t, err) + + e := envelope{} + _, err = e.Sign(signReq) + checkErrorEqual(t, `signature algorithm "#0" is not supported`, err.Error()) + }) +} + +func TestSigningScheme(t *testing.T) { + var signParams = []struct { + isLocal bool + signingScheme signature.SigningScheme + }{ + {true, signature.SigningSchemeX509}, + {true, signature.SigningSchemeX509SigningAuthority}, + {false, signature.SigningSchemeX509}, + {false, signature.SigningSchemeX509SigningAuthority}, + } + + for _, tt := range signParams { + t.Run(fmt.Sprintf("verify_isLocal=%v_signingScheme=%v", tt.isLocal, tt.signingScheme), func(t *testing.T) { + encoded, err := getEncodedMessage(tt.signingScheme, tt.isLocal, extSignedAttr) + checkNoError(t, err) + + _, err = verifyCore(encoded) + checkNoError(t, err) + }) + } +} + +func TestSignVerify(t *testing.T) { + for _, keyType := range signaturetest.KeyTypes { + keyName := map[signature.KeyType]string{ + signature.KeyTypeEC: "ECDSA", + signature.KeyTypeRSA: "RSA", + }[keyType] + for _, size := range signaturetest.GetKeySizes(keyType) { + t.Run(fmt.Sprintf("%s %d", keyName, size), func(t *testing.T) { + signer, err := signaturetest.GetTestLocalSigner(keyType, size) + checkNoError(t, err) + + signReq, err := getSignReq(signature.SigningSchemeX509, signer, nil) + checkNoError(t, err) + + e := envelope{} + encoded, err := e.Sign(signReq) + checkNoError(t, err) + + _, err = verifyCore(encoded) + checkNoError(t, err) + }) + } + } +} + +func TestVerify(t *testing.T) { + t.Run("break json format", func(t *testing.T) { + encoded, err := getEncodedMessage(signature.SigningSchemeX509, true, extSignedAttr) + checkNoError(t, err) + + encoded[0] = '}' + + _, err = verifyCore(encoded) + checkErrorEqual(t, "invalid character '}' looking for beginning of value", err.Error()) + }) + + t.Run("tamper signature", func(t *testing.T) { + // get envelope + env, err := getSignedEnvelope(signature.SigningSchemeX509, true, extSignedAttr) + checkNoError(t, err) + + // temper envelope + env.Signature = "" + + err = verifyEnvelope(env) + checkErrorEqual(t, "signature is invalid. Error: crypto/rsa: verification error", err.Error()) + }) + + t.Run("empty certificate", func(t *testing.T) { + // get envelope + env, err := getSignedEnvelope(signature.SigningSchemeX509, true, extSignedAttr) + checkNoError(t, err) + + // temper envelope + env.Header.CertChain = [][]byte{} + + err = verifyEnvelope(env) + checkErrorEqual(t, "certificate chain is not present", err.Error()) + }) + + t.Run("tamper certificate", func(t *testing.T) { + // get envelope + env, err := getSignedEnvelope(signature.SigningSchemeX509, true, extSignedAttr) + checkNoError(t, err) + + // temper envelope + env.Header.CertChain[0][0] = 'C' + + err = verifyEnvelope(env) + checkErrorEqual(t, "malformed leaf certificate", err.Error()) + }) + + t.Run("malformed protected header base64 encoded", func(t *testing.T) { + // get envelope + env, err := getSignedEnvelope(signature.SigningSchemeX509, true, extSignedAttr) + checkNoError(t, err) + + // temper envelope + env.Protected = "$" + env.Protected + + err = verifyEnvelope(env) + checkErrorEqual(t, "signature is invalid. Error: illegal base64 data at input byte 0", err.Error()) + }) + t.Run("malformed protected header raw", func(t *testing.T) { + // get envelope + env, err := getSignedEnvelope(signature.SigningSchemeX509, true, extSignedAttr) + checkNoError(t, err) + + // temper envelope + rawProtected, err := base64.RawURLEncoding.DecodeString(env.Protected) + checkNoError(t, err) + + rawProtected[0] = '}' + env.Protected = base64.RawURLEncoding.EncodeToString(rawProtected) + + err = verifyEnvelope(env) + checkErrorEqual(t, "signature is invalid. Error: invalid character '}' looking for beginning of value", err.Error()) + }) +} + +func TestSignerInfo(t *testing.T) { + getEnvelopeAndHeader := func(signingScheme signature.SigningScheme) (*jwsEnvelope, *jwsProtectedHeader) { + // get envelope + env, err := getSignedEnvelope(signingScheme, true, extSignedAttr) + checkNoError(t, err) + + // get protected header + header, err := parseProtectedHeaders(env.Protected) + checkNoError(t, err) + return env, header + } + updateProtectedHeader := func(env *jwsEnvelope, protected *jwsProtectedHeader) { + // generate protected header + headerMap := make(map[string]interface{}) + valueOf := reflect.ValueOf(*protected) + for i := 0; i < valueOf.NumField(); i++ { + var key string + tags := strings.Split(valueOf.Type().Field(i).Tag.Get("json"), ",") + if len(tags) > 0 { + key = tags[0] + } + if key == "-" { + continue + } + headerMap[key] = valueOf.Field(i).Interface() + } + // extract extended attribute + for key, value := range protected.ExtendedAttributes { + headerMap[key] = value + } + + // marshal and write back to envelope + rawProtected, err := json.Marshal(headerMap) + checkNoError(t, err) + env.Protected = base64.RawURLEncoding.EncodeToString(rawProtected) + } + getSignerInfo := func(env *jwsEnvelope, protected *jwsProtectedHeader) (*signature.SignerInfo, error) { + updateProtectedHeader(env, protected) + // marshal tampered envelope + newEncoded, err := json.Marshal(env) + checkNoError(t, err) + + // parse tampered envelope + newEnv, err := ParseEnvelope(newEncoded) + checkNoError(t, err) + + content, err := newEnv.Content() + if err != nil { + return nil, err + } + return &content.SignerInfo, nil + } + + t.Run("tamper protected header signing scheme X509", func(t *testing.T) { + env, header := getEnvelopeAndHeader(signature.SigningSchemeX509) + + // temper protected header + signingTime := time.Now() + header.AuthenticSigningTime = &signingTime + + _, err := getSignerInfo(env, header) + checkErrorEqual(t, `"io.cncf.notary.authenticSigningTime" header must not be present for notary.x509 signing scheme`, err.Error()) + }) + + t.Run("tamper protected header signing scheme X509 Signing Authority", func(t *testing.T) { + env, header := getEnvelopeAndHeader(signature.SigningSchemeX509SigningAuthority) + + // temper protected header + signingTime := time.Now() + header.SigningTime = &signingTime + + _, err := getSignerInfo(env, header) + checkErrorEqual(t, `"io.cncf.notary.signingTime" header must not be present for notary.x509.signingAuthority signing scheme`, err.Error()) + }) + + t.Run("tamper protected header signing scheme X509 Signing Authority 2", func(t *testing.T) { + env, header := getEnvelopeAndHeader(signature.SigningSchemeX509SigningAuthority) + + // temper protected header + header.AuthenticSigningTime = nil + + _, err := getSignerInfo(env, header) + checkErrorEqual(t, `"io.cncf.notary.authenticSigningTime" header must be present for notary.x509 signing scheme`, err.Error()) + }) + + t.Run("tamper protected header extended attributes", func(t *testing.T) { + env, header := getEnvelopeAndHeader(signature.SigningSchemeX509) + + // temper protected header + header.ExtendedAttributes = make(map[string]interface{}) + + _, err := getSignerInfo(env, header) + checkErrorEqual(t, `"testKey" header is marked critical but not present`, err.Error()) + }) + + t.Run("add protected header critical key", func(t *testing.T) { + env, header := getEnvelopeAndHeader(signature.SigningSchemeX509) + + // temper protected header + header.Critical = header.Critical[:len(header.Critical)-2] + + _, err := getSignerInfo(env, header) + checkErrorEqual(t, `these required headers are not marked as critical: [io.cncf.notary.expiry]`, err.Error()) + }) + + t.Run("empty critical section", func(t *testing.T) { + env, header := getEnvelopeAndHeader(signature.SigningSchemeX509) + + // temper protected header + header.Critical = []string{} + + _, err := getSignerInfo(env, header) + checkErrorEqual(t, `missing "crit" header`, err.Error()) + }) + + t.Run("unsupported algorithm", func(t *testing.T) { + env, header := getEnvelopeAndHeader(signature.SigningSchemeX509) + + // temper protected header + header.Algorithm = "ES222" + + _, err := getSignerInfo(env, header) + checkErrorEqual(t, `signature algorithm "ES222" is not supported`, err.Error()) + }) + + t.Run("tamper raw protected header json format", func(t *testing.T) { + // get envelope + env, err := getSignedEnvelope(signature.SigningSchemeX509, true, extSignedAttr) + checkNoError(t, err) + + rawProtected, err := base64.RawURLEncoding.DecodeString(env.Protected) + checkNoError(t, err) + + // temper envelope + rawProtected[0] = '}' + env.Protected = base64.RawURLEncoding.EncodeToString(rawProtected) + + newEncoded, err := json.Marshal(env) + checkNoError(t, err) + + // parse tampered envelope + newEnv, err := ParseEnvelope(newEncoded) + checkNoError(t, err) + + _, err = newEnv.Content() + checkErrorEqual(t, "jws envelope protected header can't be decoded: invalid character '}' looking for beginning of value", err.Error()) + }) + t.Run("tamper signature base64 encoding", func(t *testing.T) { + env, header := getEnvelopeAndHeader(signature.SigningSchemeX509) + + // temper protected header + env.Signature = "{" + env.Signature + + _, err := getSignerInfo(env, header) + checkErrorEqual(t, `illegal base64 data at input byte 0`, err.Error()) + }) + t.Run("tamper empty signature", func(t *testing.T) { + env, header := getEnvelopeAndHeader(signature.SigningSchemeX509) + + // temper protected header + env.Signature = "" + + _, err := getSignerInfo(env, header) + checkErrorEqual(t, `signature missing in jws-json envelope`, err.Error()) + }) + t.Run("tamper cert chain", func(t *testing.T) { + env, header := getEnvelopeAndHeader(signature.SigningSchemeX509) + + // temper protected header + env.Header.CertChain[0] = append(env.Header.CertChain[0], 'v') + + _, err := getSignerInfo(env, header) + checkErrorEqual(t, `x509: trailing data`, err.Error()) + }) +} + +func TestPayload(t *testing.T) { + t.Run("tamper envelope cause JWT parse failed", func(t *testing.T) { + // get envelope + env, err := getSignedEnvelope(signature.SigningSchemeX509, true, extSignedAttr) + checkNoError(t, err) + + // tamper payload + env.Payload = env.Payload[1:] + + // marshal tampered envelope + newEncoded, err := json.Marshal(env) + checkNoError(t, err) + + // parse tampered envelope + newEnv, err := ParseEnvelope(newEncoded) + checkNoError(t, err) + + _, err = newEnv.Content() + checkErrorEqual(t, "payload error: illegal base64 data at input byte", err.Error()) + + }) +} + +func TestEmptyEnvelope(t *testing.T) { + wantErr := &signature.SignatureEnvelopeNotFoundError{} + env := envelope{} + + t.Run("Verify()_with_empty_envelope", func(t *testing.T) { + _, err := env.Verify() + if !errors.Is(err, wantErr) { + t.Fatalf("want: %v, got: %v", wantErr, err) + } + }) + + t.Run("Payload()_with_empty_envelope", func(t *testing.T) { + _, err := env.Content() + if !errors.Is(err, wantErr) { + t.Fatalf("want: %v, got: %v", wantErr, err) + } + }) + + t.Run("SignerInfo()_with_empty_envelope", func(t *testing.T) { + _, err := env.Content() + if !errors.Is(err, wantErr) { + t.Fatalf("want: %v, got: %v", wantErr, err) + } + }) +} diff --git a/signature/jws/jws.go b/signature/jws/jws.go new file mode 100644 index 00000000..e8088de9 --- /dev/null +++ b/signature/jws/jws.go @@ -0,0 +1,261 @@ +package jws + +import ( + "crypto/x509" + "encoding/base64" + "encoding/json" + "fmt" + "strings" + + "github.com/notaryproject/notation-core-go/signature" +) + +func parseProtectedHeaders(encoded string) (*jwsProtectedHeader, error) { + rawProtected, err := base64.RawURLEncoding.DecodeString(encoded) + if err != nil { + return nil, &signature.InvalidSignatureError{ + 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. + // We unmarshal twice: once into a value of type jwsProtectedHeader and once into a value of type jwsProtectedHeader.ExtendedAttributes(map[string]interface{}) + // and removing the keys are already been defined in jwsProtectedHeader. + var protected jwsProtectedHeader + if err = json.Unmarshal(rawProtected, &protected); err != nil { + return nil, &signature.InvalidSignatureError{ + 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, &signature.InvalidSignatureError{ + Msg: fmt.Sprintf("jws envelope protected header can't be decoded: %s", err.Error())} + } + + // delete attributes that are already defined in jwsProtectedHeader. + for _, headerKey := range headerKeys { + delete(protected.ExtendedAttributes, headerKey) + } + return &protected, nil +} + +func populateProtectedHeaders(protectedHeader *jwsProtectedHeader, signerInfo *signature.SignerInfo) error { + err := validateProtectedHeaders(protectedHeader) + if err != nil { + return err + } + + if signerInfo.SignatureAlgorithm, err = getSignatureAlgorithm(protectedHeader.Algorithm); err != nil { + return err + } + + signerInfo.SignedAttributes.ExtendedAttributes = getExtendedAttributes(protectedHeader.ExtendedAttributes, protectedHeader.Critical) + signerInfo.SignedAttributes.SigningScheme = protectedHeader.SigningScheme + if protectedHeader.Expiry != nil { + signerInfo.SignedAttributes.Expiry = *protectedHeader.Expiry + } + switch protectedHeader.SigningScheme { + case signature.SigningSchemeX509: + if protectedHeader.SigningTime != nil { + signerInfo.SignedAttributes.SigningTime = *protectedHeader.SigningTime + } + case signature.SigningSchemeX509SigningAuthority: + if protectedHeader.AuthenticSigningTime != nil { + signerInfo.SignedAttributes.SigningTime = *protectedHeader.AuthenticSigningTime + } + default: + return &signature.InvalidSignatureError{ + Msg: fmt.Sprintf("unsupported SigningScheme: `%v`", protectedHeader.SigningScheme), + } + } + return nil +} + +func validateProtectedHeaders(protectedHeader *jwsProtectedHeader) error { + // validate headers that should not be present as per signing schemes + switch protectedHeader.SigningScheme { + case signature.SigningSchemeX509: + if protectedHeader.AuthenticSigningTime != nil { + return &signature.InvalidSignatureError{Msg: fmt.Sprintf("%q header must not be present for %s signing scheme", headerKeyAuthenticSigningTime, signature.SigningSchemeX509)} + } + case signature.SigningSchemeX509SigningAuthority: + if protectedHeader.SigningTime != nil { + return &signature.InvalidSignatureError{Msg: fmt.Sprintf("%q header must not be present for %s signing scheme", headerKeySigningTime, signature.SigningSchemeX509SigningAuthority)} + } + if protectedHeader.AuthenticSigningTime == nil { + return &signature.InvalidSignatureError{Msg: fmt.Sprintf("%q header must be present for %s signing scheme", headerKeyAuthenticSigningTime, signature.SigningSchemeX509)} + } + default: + return &signature.InvalidSignatureError{Msg: fmt.Sprintf("unsupported SigningScheme: `%v`", protectedHeader.SigningScheme)} + } + + return validateCriticalHeaders(protectedHeader) +} + +func validateCriticalHeaders(protectedHeader *jwsProtectedHeader) error { + if len(protectedHeader.Critical) == 0 { + return &signature.InvalidSignatureError{Msg: `missing "crit" header`} + } + + mustMarkedCrit := map[string]bool{headerKeySigningScheme: true} + if protectedHeader.Expiry != nil && !protectedHeader.Expiry.IsZero() { + mustMarkedCrit[headerKeyExpiry] = true + } + + if protectedHeader.SigningScheme == signature.SigningSchemeX509SigningAuthority { + mustMarkedCrit[headerKeyAuthenticSigningTime] = true + } + + for _, val := range protectedHeader.Critical { + if _, ok := mustMarkedCrit[val]; ok { + delete(mustMarkedCrit, val) + } else { + if _, ok := protectedHeader.ExtendedAttributes[val]; !ok { + return &signature.InvalidSignatureError{ + Msg: fmt.Sprintf("%q header is marked critical but not present", val)} + } + } + } + + // validate all required critical headers headers(as per spec) are marked as critical. + if len(mustMarkedCrit) != 0 { + // This is not taken care by VerifySignerInfo method + keys := make([]string, 0, len(mustMarkedCrit)) + for k := range mustMarkedCrit { + keys = append(keys, k) + } + return &signature.InvalidSignatureError{Msg: fmt.Sprintf("these required headers are not marked as critical: %v", keys)} + } + + return nil +} + +func getSignatureAlgorithm(alg string) (signature.Algorithm, error) { + signatureAlg, ok := jwsAlgSignatureAlgMap[alg] + if !ok { + return 0, &signature.UnsupportedSignatureAlgoError{Alg: alg} + } + + return signatureAlg, nil +} + +func getExtendedAttributes(attrs map[string]interface{}, critical []string) []signature.Attribute { + extendedAttr := make([]signature.Attribute, 0, len(attrs)) + for key, value := range attrs { + extendedAttr = append(extendedAttr, signature.Attribute{ + Key: key, + Critical: contains(critical, key), + Value: value, + }) + } + return extendedAttr +} + +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +func generateJWS(compact string, req *signature.SignRequest, certs []*x509.Certificate) (*jwsEnvelope, error) { + parts := strings.Split(compact, ".") + if len(parts) != 3 { + // this should never happen + return nil, fmt.Errorf( + "unexpected error occurred while generating a JWS-JSON serialization from compact serialization. want: len(parts) == 3, got: len(parts) == %d", len(parts)) + } + + rawCerts := make([][]byte, len(certs)) + for i, cert := range certs { + rawCerts[i] = cert.Raw + } + + return &jwsEnvelope{ + Protected: parts[0], + Payload: parts[1], + Signature: parts[2], + Header: jwsUnprotectedHeader{ + CertChain: rawCerts, + SigningAgent: req.SigningAgent, + }, + }, nil +} + +// getSignerAttributes merge extended signed attributes and protected header to be signed attributes. +func getSignedAttributes(req *signature.SignRequest, algorithm string) (map[string]interface{}, error) { + extAttrs := make(map[string]interface{}) + crit := []string{headerKeySigningScheme} + + // write extended signed attributes to the extAttrs map + for _, elm := range req.ExtendedSignedAttributes { + extAttrs[elm.Key] = elm.Value + if elm.Critical { + crit = append(crit, elm.Key) + } + } + + jwsProtectedHeader := jwsProtectedHeader{ + Algorithm: algorithm, + ContentType: req.Payload.ContentType, + SigningScheme: req.SigningScheme, + } + + switch req.SigningScheme { + case signature.SigningSchemeX509: + jwsProtectedHeader.SigningTime = &req.SigningTime + case signature.SigningSchemeX509SigningAuthority: + crit = append(crit, headerKeyAuthenticSigningTime) + jwsProtectedHeader.AuthenticSigningTime = &req.SigningTime + default: + return nil, fmt.Errorf("unsupported SigningScheme: `%v`", req.SigningScheme) + } + + if !req.Expiry.IsZero() { + crit = append(crit, headerKeyExpiry) + jwsProtectedHeader.Expiry = &req.Expiry + } + + jwsProtectedHeader.Critical = crit + m, err := convertToMap(jwsProtectedHeader) + if err != nil { + return nil, fmt.Errorf("unexpected error occurred while creating protected headers, Error: %s", err.Error()) + } + + return mergeMaps(m, extAttrs) +} + +func convertToMap(i interface{}) (map[string]interface{}, error) { + s, err := json.Marshal(i) + if err != nil { + return nil, err + } + + var m map[string]interface{} + err = json.Unmarshal(s, &m) + return m, err +} + +func mergeMaps(maps ...map[string]interface{}) (map[string]interface{}, error) { + result := make(map[string]interface{}) + for _, m := range maps { + for k, v := range m { + if _, ok := result[k]; ok { + return nil, fmt.Errorf("attribute key:%s repeated", k) + } + result[k] = v + } + } + return result, nil +} + +// compactJWS converts Flattened JWS JSON Serialization Syntax (section-7.2.2) to +// JWS Compact Serialization (section-7.1) +// +// [RFC 7515]: https://www.rfc-editor.org/rfc/rfc7515.html +func compactJWS(envelope *jwsEnvelope) string { + return strings.Join([]string{ + envelope.Protected, + envelope.Payload, + envelope.Signature}, ".") +} diff --git a/signature/jws/jws_test.go b/signature/jws/jws_test.go new file mode 100644 index 00000000..3074924e --- /dev/null +++ b/signature/jws/jws_test.go @@ -0,0 +1,55 @@ +package jws + +import ( + "encoding/json" + "math" + "testing" +) + +func Test_convertToMap(t *testing.T) { + type S struct { + A string + B int + C float64 + } + t.Run("invalid value", func(t *testing.T) { + _, err := convertToMap(math.Inf(1)) + if err == nil { + t.Fatal("should cause error") + } + }) + + t.Run("normal case", func(t *testing.T) { + testStruct := S{ + A: "test string", + B: 1, + C: 1.1, + } + // generate map + m, err := convertToMap(&testStruct) + checkNoError(t, err) + + // convert map to struct + bytes, err := json.Marshal(m) + checkNoError(t, err) + + var newStruct S + err = json.Unmarshal(bytes, &newStruct) + checkNoError(t, err) + + // check new struct equal with original struct + if newStruct != testStruct { + t.Fatal("convertToMap error") + } + }) +} + +func Test_generateJWSError(t *testing.T) { + _, err := generateJWS("", nil, nil) + checkErrorEqual(t, "unexpected error occurred while generating a JWS-JSON serialization from compact serialization", err.Error()) +} + +func Test_getSignatureAlgorithmError(t *testing.T) { + _, err := getSignatureAlgorithm("ES222") + checkErrorEqual(t, `signature algorithm "ES222" is not supported`, err.Error()) +} diff --git a/signature/jws/jwt.go b/signature/jws/jwt.go new file mode 100644 index 00000000..e848f285 --- /dev/null +++ b/signature/jws/jwt.go @@ -0,0 +1,152 @@ +package jws + +import ( + "crypto" + "crypto/x509" + "encoding/base64" + "errors" + "fmt" + + "github.com/golang-jwt/jwt/v4" + "github.com/notaryproject/notation-core-go/signature" +) + +// signingMethod is the interface for jwt.SigingMethod with additional method to +// access certificate chain after calling Sign(). +type signingMethod interface { + jwt.SigningMethod + + // CertificateChain returns the certificate chain. + // + // It should be called after calling Sign(). + CertificateChain() ([]*x509.Certificate, error) + + // PrivateKey returns the private key. + PrivateKey() crypto.PrivateKey +} + +// remoteSigningMethod wraps the remote signer to be a SigningMethod +type remoteSigningMethod struct { + signer signature.Signer + certs []*x509.Certificate + algorithm string +} + +func newRemoteSigningMethod(signer signature.Signer) (signingMethod, error) { + algorithm, err := extractJwtAlgorithm(signer) + if err != nil { + return nil, err + } + return &remoteSigningMethod{ + signer: signer, + algorithm: algorithm, + }, nil +} + +// Verify doesn't need to be implemented. +func (s *remoteSigningMethod) Verify(signingString, signature string, key interface{}) error { + return errors.New("not implemented") +} + +// Sign hashes the signingString and call the remote signer to sign the digest. +func (s *remoteSigningMethod) Sign(signingString string, key interface{}) (string, error) { + // sign by external signer + sig, certs, err := s.signer.Sign([]byte(signingString)) + if err != nil { + return "", err + } + s.certs = certs + return base64.RawURLEncoding.EncodeToString(sig), nil +} + +// Alg implements jwt.SigningMethod interface. +func (s *remoteSigningMethod) Alg() string { + return s.algorithm +} + +// CertificateChain returns the certificate chain. +// +// It should be called after Sign(). +func (s *remoteSigningMethod) CertificateChain() ([]*x509.Certificate, error) { + if s.certs == nil { + return nil, &signature.InvalidSignRequestError{Msg: "certificate chain is not set"} + } + return s.certs, nil +} + +// PrivateKey returns nil for remote signer. +func (s *remoteSigningMethod) PrivateKey() crypto.PrivateKey { + return nil +} + +// localSigningMethod wraps the local signer to be a SigningMethod. +type localSigningMethod struct { + jwt.SigningMethod + signer signature.LocalSigner +} + +func newLocalSigningMethod(signer signature.LocalSigner) (signingMethod, error) { + alg, err := extractJwtAlgorithm(signer) + if err != nil { + return nil, err + } + + return &localSigningMethod{ + SigningMethod: jwt.GetSigningMethod(alg), + signer: signer, + }, nil +} + +// CertificateChain returns the certificate chain. +func (s *localSigningMethod) CertificateChain() ([]*x509.Certificate, error) { + return s.signer.CertificateChain() +} + +// PrivateKey returns the private key. +func (s *localSigningMethod) PrivateKey() crypto.PrivateKey { + return s.signer.PrivateKey() +} + +// getSigningMethod return signingMethod for the given signer. +func getSigningMethod(signer signature.Signer) (signingMethod, error) { + if localSigner, ok := signer.(signature.LocalSigner); ok { + // for local signer + return newLocalSigningMethod(localSigner) + } + // for remote signer + return newRemoteSigningMethod(signer) +} + +// verifyJWT verifies the JWT token against the specified verification key. +func verifyJWT(tokenString string, publicKey interface{}) error { + parser := jwt.NewParser( + jwt.WithValidMethods(validMethods), + jwt.WithJSONNumber(), + jwt.WithoutClaimsValidation(), + ) + + if _, err := parser.Parse(tokenString, func(t *jwt.Token) (interface{}, error) { + return publicKey, nil + }); err != nil { + return &signature.SignatureIntegrityError{Err: err} + } + return nil +} + +func extractJwtAlgorithm(signer signature.Signer) (string, error) { + // extract algorithm from signer + keySpec, err := signer.KeySpec() + if err != nil { + return "", err + } + alg := keySpec.SignatureAlgorithm() + + // converts the signature.Algorithm to be jwt package defined + // algorithm name. + jwsAlg, ok := signatureAlgJWSAlgMap[alg] + if !ok { + return "", &signature.UnsupportedSignatureAlgoError{ + Alg: fmt.Sprintf("#%d", alg)} + } + return jwsAlg, nil +} diff --git a/signature/jws/jwt_test.go b/signature/jws/jwt_test.go new file mode 100644 index 00000000..a02ef2a9 --- /dev/null +++ b/signature/jws/jwt_test.go @@ -0,0 +1,124 @@ +package jws + +import ( + "crypto" + "crypto/x509" + "errors" + "testing" + + "github.com/notaryproject/notation-core-go/signature" + "github.com/notaryproject/notation-core-go/testhelper" +) + +type errorLocalSigner struct { + algType signature.KeyType + size int + keySpecError error +} + +// Sign returns error +func (s *errorLocalSigner) Sign(payload []byte) ([]byte, []*x509.Certificate, error) { + return nil, nil, errors.New("sign error") +} + +// KeySpec returns the key specification. +func (s *errorLocalSigner) KeySpec() (signature.KeySpec, error) { + return signature.KeySpec{ + Type: s.algType, + Size: s.size, + }, s.keySpecError +} + +// PrivateKey returns nil. +func (s *errorLocalSigner) PrivateKey() crypto.PrivateKey { + return nil +} + +// CertificateChain returns nil. +func (s *errorLocalSigner) CertificateChain() ([]*x509.Certificate, error) { + return nil, nil +} + +func Test_remoteSigningMethod_Verify(t *testing.T) { + s := &remoteSigningMethod{} // Sign signs the payload and returns the raw signature and certificates. + err := s.Verify("", "", nil) + if err == nil { + t.Fatalf("should panic") + } +} + +func Test_newLocalSigningMethod(t *testing.T) { + signer := errorLocalSigner{} + _, err := newLocalSigningMethod(&signer) + checkErrorEqual(t, `signature algorithm "#0" is not supported`, err.Error()) +} + +func Test_newRemoteSigningMethod(t *testing.T) { + _, err := newRemoteSigningMethod(&errorLocalSigner{}) + checkErrorEqual(t, `signature algorithm "#0" is not supported`, err.Error()) +} + +func Test_remoteSigningMethod_CertificateChain(t *testing.T) { + certs := []*x509.Certificate{ + testhelper.GetRSALeafCertificate().Cert, + } + + signer, err := getSigner(false, certs, testhelper.GetRSALeafCertificate().PrivateKey) + checkNoError(t, err) + + signingScheme, err := newRemoteSigningMethod(signer) + checkNoError(t, err) + + _, err = signingScheme.CertificateChain() + checkErrorEqual(t, "certificate chain is not set", err.Error()) +} + +func Test_remoteSigningMethod_Sign(t *testing.T) { + signer := errorLocalSigner{ + algType: signature.KeyTypeRSA, + size: 2048, + keySpecError: nil, + } + signingScheme, err := newRemoteSigningMethod(&signer) + checkNoError(t, err) + + _, err = signingScheme.Sign("", nil) + checkErrorEqual(t, "sign error", err.Error()) +} +func Test_extractJwtAlgorithm(t *testing.T) { + _, err := extractJwtAlgorithm(&errorLocalSigner{}) + checkErrorEqual(t, `signature algorithm "#0" is not supported`, err.Error()) + + _, err = extractJwtAlgorithm(&errorLocalSigner{ + keySpecError: errors.New("get key spec error"), + }) + checkErrorEqual(t, `get key spec error`, err.Error()) +} + +func Test_verifyJWT(t *testing.T) { + type args struct { + tokenString string + publicKey interface{} + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "invalid signature", + args: args{ + tokenString: "eyJhbGciOiJQUzM4NCIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSIsInRlc3RLZXkiLCJpby5jbmNmLm5vdGFyeS5leHBpcnkiXSwiY3R5IjoiYXBwbGljYXRpb24vdm5kLmNuY2Yubm90YXJ5LnBheWxvYWQudjEranNvbiIsImlvLmNuY2Yubm90YXJ5LmV4cGlyeSI6IjIwMjItMDgtMjRUMTc6MTg6MTUuNDkxNzQ1ODQ1KzA4OjAwIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDIyLTA4LTI0VDE2OjE4OjE1LjQ5MTc0NTgwNCswODowMCIsInRlc3RLZXkiOiJ0ZXN0VmFsdWUiLCJ0ZXN0S2V5MiI6InRlc3RWYWx1ZTIifQ.ImV3b2dJQ0p6ZFdKcVpXTjBJam9nZXdvZ0lDQWdJbTFsWkdsaFZIbHdaU0k2SUNKaGNIQnNhV05oZEdsdmJpOTJibVF1YjJOcExtbHRZV2RsTG0xaGJtbG1aWE4wTG5ZeEsycHpiMjRpTEFvZ0lDQWdJbVJwWjJWemRDSTZJQ0p6YUdFeU5UWTZOek5qT0RBek9UTXdaV0V6WW1FeFpUVTBZbU15TldNeVltUmpOVE5sWkdRd01qZzBZell5WldRMk5URm1aVGRpTURBek5qbGtZVFV4T1dFell6TXpNeUlzQ2lBZ0lDQWljMmw2WlNJNklERTJOekkwTEFvZ0lDQWdJbUZ1Ym05MFlYUnBiMjV6SWpvZ2V3b2dJQ0FnSUNBZ0lDSnBieTUzWVdKaWFYUXRibVYwZDI5eWEzTXVZblZwYkdSSlpDSTZJQ0l4TWpNaUNpQWdJQ0I5Q2lBZ2ZRcDlDZ2s9Ig.YmF1_5dMW4YWK2fzct1dp25lTy8p0qdSmR-O2fZsf29ohiLYGUVXfvRjEgERzZvDd49aOYQvrEgGvoU9FfK2KIqHrJ8kliI00wd4kuK57aE83pszBMOOrZqAjqkdyoj7dswmwJSyjMC9fhwh_AwrrOnrBjw4U0vGTrImMQEwHfVq0MWLCuw9YpFkytLPeCl8n825EtqMzwYYTUzdQfQJO_ZZrS34n8tK0IRZrX2LjrYz9HqR_UFgVqf_G9qwJpekYyd9Aacl9y4x7zzI-R-bADFgztyAYeWRmE75qI26OgG-ss4wfG-ZbchEm6FYU8py64bsLmJtK9muPd9ZU7SXQOEVzxtXoQFnUhT9AgaNNoxnSnU25mMjAeuGDj0Xn_Gv7f24PyDk9ZEE3WjrguJyzaP6P4jYugXr6Afq10HXRpI_cE8B-6USGpiRH9iJLE04xumWpjWup9p5fv3Fnt3Au1dhbgaDvrSGMHmmCSW4dk7_87Q4LGkGcbn0zNINydcg", + publicKey: testhelper.GetRSALeafCertificate().Cert.PublicKey, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := verifyJWT(tt.args.tokenString, tt.args.publicKey); (err != nil) != tt.wantErr { + t.Errorf("verifyJWT() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/signature/jws/testdata/conformance.json b/signature/jws/testdata/conformance.json new file mode 100644 index 00000000..2e2b3a5d --- /dev/null +++ b/signature/jws/testdata/conformance.json @@ -0,0 +1,8 @@ +{ + "payload": "eyJrZXkiOiJoZWxsbyBKV1MifQ", + "protected": "eyJhbGciOiJFUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSIsInNpZ25lZENyaXRLZXkxIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5Il0sImN0eSI6ImFwcGxpY2F0aW9uL3ZuZC5jbmNmLm5vdGFyeS5wYXlsb2FkLnYxK2pzb24iLCJpby5jbmNmLm5vdGFyeS5leHBpcnkiOiIyMDk5LTA4LTMwVDEzOjUwOjAwWiIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdTY2hlbWUiOiJub3RhcnkueDUwOSIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdUaW1lIjoiMjAyMi0wOC0yOVQxMzo1MDowMFoiLCJzaWduZWRDcml0S2V5MSI6InNpZ25lZENyaXRWYWx1ZTEiLCJzaWduZWRLZXkxIjoic2lnbmVkVmFsdWUxIiwic2lnbmVkS2V5MiI6InNpZ25lZFZhbHVlMSIsInNpZ25lZEtleTMiOiJzaWduZWRWYWx1ZTEiLCJzaWduZWRLZXk0Ijoic2lnbmVkVmFsdWUxIn0", + "header": { + "io.cncf.notary.signingAgent": "NotationConformanceTest/1.0.0" + }, + "signature": "zWcjEO1QYe1aNIcfIv_nHjCwb-5gjjHTkL8aUjBq3kU_nP6YtDfKX6Sjl90DLG7NR0-uB1Tn6oj_6Ws1LhCuIQ" +} \ No newline at end of file diff --git a/signature/jws/types.go b/signature/jws/types.go new file mode 100644 index 00000000..64fcca2d --- /dev/null +++ b/signature/jws/types.go @@ -0,0 +1,115 @@ +package jws + +import ( + "time" + + "github.com/golang-jwt/jwt/v4" + "github.com/notaryproject/notation-core-go/signature" +) + +const ( + headerKeyAlg = "alg" + headerKeyCty = "cty" + headerKeyCrit = "crit" + headerKeyExpiry = "io.cncf.notary.expiry" + headerKeySigningTime = "io.cncf.notary.signingTime" + headerKeySigningScheme = "io.cncf.notary.signingScheme" + headerKeyAuthenticSigningTime = "io.cncf.notary.authenticSigningTime" +) + +// headerKeys includes all system aware keys for JWS protected header +// [JWS envelope]: https://github.com/notaryproject/notaryproject/blob/main/signature-envelope-jws.md#protected-headers +var headerKeys = []string{ + headerKeyAlg, + headerKeyCty, + headerKeyCrit, + headerKeyExpiry, + headerKeySigningTime, + headerKeySigningScheme, + headerKeyAuthenticSigningTime, +} + +// jwsProtectedHeader contains the set of protected headers. +type jwsProtectedHeader struct { + // Defines which algorithm was used to generate the signature. + Algorithm string `json:"alg"` + + // Media type of the secured content (the payload). + ContentType string `json:"cty"` + + // Lists the headers that implementation MUST understand and process. + Critical []string `json:"crit,omitempty"` + + // The "best by use" time for the artifact, as defined by the signer. + Expiry *time.Time `json:"io.cncf.notary.expiry,omitempty"` + + // Specifies the Notary v2 Signing Scheme used by the signature. + SigningScheme signature.SigningScheme `json:"io.cncf.notary.signingScheme"` + + // The time at which the signature was generated. only valid when signing scheme is `notary.x509` + SigningTime *time.Time `json:"io.cncf.notary.signingTime,omitempty"` + + // The time at which the signature was generated. only valid when signing scheme is `notary.x509.signingAuthority` + AuthenticSigningTime *time.Time `json:"io.cncf.notary.authenticSigningTime,omitempty"` + + // The user defined attributes. + ExtendedAttributes map[string]interface{} `json:"-"` +} + +// jwsUnprotectedHeader contains the set of unprotected headers. +type jwsUnprotectedHeader struct { + // RFC3161 time stamp token Base64-encoded. + TimestampSignature []byte `json:"io.cncf.notary.timestampSignature,omitempty"` + + // List of X.509 Base64-DER-encoded certificates + // as defined at https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6. + CertChain [][]byte `json:"x5c"` + + // SigningAgent used for signing + SigningAgent string `json:"io.cncf.notary.signingAgent,omitempty"` +} + +// jwsEnvelope is the final Signature envelope. +type jwsEnvelope struct { + // JWSPayload Base64URL-encoded. Raw data should be JSON format. + Payload string `json:"payload"` + + // jwsProtectedHeader Base64URL-encoded. + Protected string `json:"protected"` + + // Signature metadata that is not integrity Protected + Header jwsUnprotectedHeader `json:"header"` + + // Base64URL-encoded Signature. + Signature string `json:"signature"` +} + +var ( + ps256 = jwt.SigningMethodPS256.Name + ps384 = jwt.SigningMethodPS384.Name + ps512 = jwt.SigningMethodPS512.Name + es256 = jwt.SigningMethodES256.Name + es384 = jwt.SigningMethodES384.Name + es512 = jwt.SigningMethodES512.Name +) + +var validMethods = []string{ps256, ps384, ps512, es256, es384, es512} + +var signatureAlgJWSAlgMap = map[signature.Algorithm]string{ + signature.AlgorithmPS256: ps256, + signature.AlgorithmPS384: ps384, + signature.AlgorithmPS512: ps512, + signature.AlgorithmES256: es256, + signature.AlgorithmES384: es384, + signature.AlgorithmES512: es512, +} + +var jwsAlgSignatureAlgMap = reverseMap(signatureAlgJWSAlgMap) + +func reverseMap(m map[signature.Algorithm]string) map[string]signature.Algorithm { + n := make(map[string]signature.Algorithm, len(m)) + for k, v := range m { + n[v] = k + } + return n +} diff --git a/signature/jws_test.go b/signature/jws_test.go deleted file mode 100644 index f6c554a0..00000000 --- a/signature/jws_test.go +++ /dev/null @@ -1,273 +0,0 @@ -package signature - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/json" - "errors" - "fmt" - "reflect" - "strconv" - "testing" - - "github.com/notaryproject/notation-core-go/testhelper" -) - -// Tests various scenarios around newJWSEnvelopeFromBytes method -func TestNewJWSEnvelopeFromBytes(t *testing.T) { - t.Run("newJWSEnvelopeFromBytes", func(t *testing.T) { - if _, err := newJWSEnvelopeFromBytes([]byte(TestValidSig)); err != nil { - t.Errorf("Error found: %q", err) - } - }) - - t.Run("newJWSEnvelopeFromBytes Error", func(t *testing.T) { - if _, err := newJWSEnvelopeFromBytes([]byte("Malformed")); err == nil { - t.Errorf("Expected error but not found") - } - }) -} - -// Tests various scenarios around validateIntegrity method -func TestValidateIntegrity(t *testing.T) { - t.Run("with newJWSEnvelope() returns error", func(t *testing.T) { - env := jwsEnvelope{} - err := env.validateIntegrity() - if !(err != nil && errors.As(err, new(SignatureNotFoundError))) { - t.Errorf("Expected SignatureNotFoundError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with NewJWSEnvelopeFromBytes works", func(t *testing.T) { - env, _ := newJWSEnvelopeFromBytes([]byte(TestValidSig)) - err := env.validateIntegrity() - if err != nil { - t.Errorf("validateIntegrity(). Error = %s", err) - } - }) - - t.Run("with invalid base64 bytes sig envelope returns error", func(t *testing.T) { - env, _ := newJWSEnvelopeFromBytes([]byte("{\"Payload\":\"Hi!\",\"Protected\":\"Hi\",\"Header\":{},\"Signature\":\"Hi!\"}")) - err := env.validateIntegrity() - if !(err != nil && errors.As(err, new(InvalidSignatureError))) { - t.Errorf("Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with incomplete sig envelope returns error", func(t *testing.T) { - env, _ := newJWSEnvelopeFromBytes([]byte("{\"Payload\":\"eyJhbGciOiJIUzI1NiJ9\",\"Protected\":\"eyJhbGciOiJQUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiXSwiaW8uY25jZi5ub3Rhcnkuc2luaW5nVGltZSI6IjIwMDYtMDEtMDJUMTU6MDQ6MDVaIn0\",\"Header\":{},\"Signature\":\"YjGj\"}")) - if err := env.validateIntegrity(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { - t.Errorf("Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with tempered payload returns error", func(t *testing.T) { - env, _ := newJWSEnvelopeFromBytes([]byte(TestTamperedSig)) - if err := env.validateIntegrity(); !(err != nil && errors.As(err, new(SignatureIntegrityError))) { - t.Errorf("Expected SignatureIntegrityError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with wrong certificate returns error", func(t *testing.T) { - var jwsInternal jwsInternalEnvelope - json.Unmarshal([]byte(TestValidSig), &jwsInternal) - jwsInternal.Header.CertChain[0] = testhelper.GetRSALeafCertificate().Cert.Raw - tempered, _ := json.Marshal(jwsInternal) - env, _ := newJWSEnvelopeFromBytes(tempered) - if err := env.validateIntegrity(); !(err != nil && errors.As(err, new(SignatureIntegrityError))) { - t.Errorf("Expected SignatureIntegrityError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with invalid certificate returns error", func(t *testing.T) { - malformedSig := "{\"payload\":\"eyJ0YXJnZXRBcnRpZmFjdCI6eyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLm1hbmlmZXN0LnYxK2pzb24iLCJkaWdlc3QiOiJzaGEyNTY6NzNjODAzOTMwZWEzYmExZTU0YmMyNWMyYmRjNTNlZGQwMjg0YzYyZWQ2NTFmZTdiMDAzNjlkYTUxOWEzYzMzMyIsInNpemUiOjE2NzI0LCJhbm5vdGF0aW9ucyI6eyJpby53YWJiaXQtbmV0d29ya3MuYnVpbGRJZCI6IjEyMyJ9fX0\",\"protected\":\"eyJhbGciOiJQUzM4NCIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSIsInNpZ25lZENyaXRLZXkxIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luIiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luTWluVmVyc2lvbiJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IjoiMjAyMi0wOC0wNlQxMDowNTowNy0wNzowMCIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdTY2hlbWUiOiJub3RhcnkueDUwOS5zaWduaW5nQXV0aG9yaXR5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDIyLTA4LTA1VDEwOjA1OjA3LTA3OjAwIiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luIjoiSG9sYSBQbHVnaW4iLCJpby5jbmNmLm5vdGFyeS52ZXJpZmljYXRpb25QbHVnaW5NaW5WZXJzaW9uIjoiMS4xLjEiLCJzaWduZWRDcml0S2V5MSI6InNpZ25lZFZhbHVlMSIsInNpZ25lZEtleTEiOiJzaWduZWRLZXkyIn0\",\"header\":{\"x5c\":[\"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\"],\"io.cncf.notary.SigningAgent\":\"NotationUnitTest/1.0.0\"},\"signature\":\"K5r5b2bJF15kV2Qe5NXf42SCI5_V9K0sCuHSd1bg2OFIOp3FcupjYT4yb26jsV2aE9lrsn8FNxoP-PqkV385klZ_xnTzhRO0T3S7bCL_wu2ZtzuRKp43yOjPc7TPdbd2Q1BKd5rIS05RtxfZTYF1gGIWyRMMc8pos-EgBGhlEXNK78IsH7Eh__bk6pFlY0y5TsKDx8-9h85OKL910CKtCyjP3JgLmB_STxc6iz7iSC8lBmiq_fra3lhfwgDTwTWL2I82-SNFGf3baANppjLP-W1f6ckV9PaFmbPz8hMZ_kYXMRk100IkeSz5inK8rfbCFPHeA6evjydPNO35noIY1ETy7AppB8HlctY903u_iRGh4ur4mKf4snduQbpDr9EARG0c_6styaiwhxkshkrHLKov0C_ZZPNqAZ5ItN2QuBShyNtaKzWPCPjF4EPANVnFjdEH8Up4WpShMX3-N1wQb3IQmNf9kU04YFwkTJn8HECFseGRmZAvG8x0W5PcQik5\"}" - - env, _ := newJWSEnvelopeFromBytes([]byte(malformedSig)) - if err := env.validateIntegrity(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { - t.Errorf("validateIntegrity(). Expected SignatureIntegrityError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with malformed signature returns error", func(t *testing.T) { - x509WithAuthSigningTime := "{\"payload\":\"eyJ0YXJnZXRBcnRpZmFjdCI6eyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLm1hbmlmZXN0LnYxK2pzb24iLCJkaWdlc3QiOiJzaGEyNTY6NzNjODAzOTMwZWEzYmExZTU0YmMyNWMyYmRjNTNlZGQwMjg0YzYyZWQ2NTFmZTdiMDAzNjlkYTUxOWEzYzMzMyIsInNpemUiOjE2NzI0LCJhbm5vdGF0aW9ucyI6eyJpby53YWJiaXQtbmV0d29ya3MuYnVpbGRJZCI6IjEyMyJ9fX0\",\"protected\":\"eyJhbGciOiJQUzM4NCIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSIsInNpZ25lZENyaXRLZXkxIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luIiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luTWluVmVyc2lvbiJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3RhcnkuYXV0aGVudGljU2lnbmluZ1RpbWUiOiIyMDIyLTA4LTA1VDEwOjAwOjQ2LTA3OjAwIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IjoiMjAyMi0wOC0wNlQxMDowMDo0Ni0wNzowMCIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdTY2hlbWUiOiJub3RhcnkueDUwOSIsImlvLmNuY2Yubm90YXJ5LnZlcmlmaWNhdGlvblBsdWdpbiI6IkhvbGEgUGx1Z2luIiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luTWluVmVyc2lvbiI6IjEuMS4xIiwic2lnbmVkQ3JpdEtleTEiOiJzaWduZWRWYWx1ZTEiLCJzaWduZWRLZXkxIjoic2lnbmVkS2V5MiJ9\",\"header\":{\"x5c\":[\"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\"],\"io.cncf.notary.SigningAgent\":\"NotationUnitTest/1.0.0\"},\"signature\":\"sopnDN-hfcaBXChHwGBAK6nmkDYazYgs8dxeb6jD9zWLVd5sE0Vzzv5LDlk0fHTGrufVPw4xx_Y3m1qFBxUMONJqj0ROb7vE--HLcNTPdGgRu7CeuE7D_SCxqTKF0PmzwXbibr2PNJpQ_aS4bBwrG6amjk2GuxNy8v-GCRVFQy_DcwGtDfjjbN_wdwlsxyCGX7PL8Vh97-H4xHabpVKKGIV4FNC0a_3vTeAsuwcOxwXTIs5SLFQ1EHTW2cSI9auv11qBo6HSVZtd7_cRoMKJDnS26eQ_nkz-FaxMh3uyxXFiXf3TlNrZeEOERmAinUr8dwp3TSGcZRJeJPqIa5Zfvk1F9VapwHJkN87UiLlKnspgF2X1HMYELCLL15ntPYJme0EbOo2blQ4Iqo1oMzln92L4TPqqmuDT9uWpDGzuzSc1Gb5K6SGrsVzbqO3ECKoPA_YGHYelZ_YXK0EVSUM9rV0VWkVeMYi5KiQDErTEXm7Rh01XSAUMpe0zxJaQ0-vm\"}" - x509SaWithSigningTime := "{\"payload\":\"eyJ0YXJnZXRBcnRpZmFjdCI6eyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLm1hbmlmZXN0LnYxK2pzb24iLCJkaWdlc3QiOiJzaGEyNTY6NzNjODAzOTMwZWEzYmExZTU0YmMyNWMyYmRjNTNlZGQwMjg0YzYyZWQ2NTFmZTdiMDAzNjlkYTUxOWEzYzMzMyIsInNpemUiOjE2NzI0LCJhbm5vdGF0aW9ucyI6eyJpby53YWJiaXQtbmV0d29ya3MuYnVpbGRJZCI6IjEyMyJ9fX0\",\"protected\":\"eyJhbGciOiJQUzM4NCIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSIsInNpZ25lZENyaXRLZXkxIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luIiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luTWluVmVyc2lvbiJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkucGF5bG9hZC52MStqc29uIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IjoiMjAyMi0wOC0wNlQxMDowNTowNy0wNzowMCIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdTY2hlbWUiOiJub3RhcnkueDUwOS5zaWduaW5nQXV0aG9yaXR5IiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDIyLTA4LTA1VDEwOjA1OjA3LTA3OjAwIiwiaW8uY25jZi5ub3RhcnkudmVyaWZpY2F0aW9uUGx1Z2luIjoiSG9sYSBQbHVnaW4iLCJpby5jbmNmLm5vdGFyeS52ZXJpZmljYXRpb25QbHVnaW5NaW5WZXJzaW9uIjoiMS4xLjEiLCJzaWduZWRDcml0S2V5MSI6InNpZ25lZFZhbHVlMSIsInNpZ25lZEtleTEiOiJzaWduZWRLZXkyIn0\",\"header\":{\"x5c\":[\"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\"],\"io.cncf.notary.SigningAgent\":\"NotationUnitTest/1.0.0\"},\"signature\":\"K5r5b2bJF15kV2Qe5NXf42SCI5_V9K0sCuHSd1bg2OFIOp3FcupjYT4yb26jsV2aE9lrsn8FNxoP-PqkV385klZ_xnTzhRO0T3S7bCL_wu2ZtzuRKp43yOjPc7TPdbd2Q1BKd5rIS05RtxfZTYF1gGIWyRMMc8pos-EgBGhlEXNK78IsH7Eh__bk6pFlY0y5TsKDx8-9h85OKL910CKtCyjP3JgLmB_STxc6iz7iSC8lBmiq_fra3lhfwgDTwTWL2I82-SNFGf3baANppjLP-W1f6ckV9PaFmbPz8hMZ_kYXMRk100IkeSz5inK8rfbCFPHeA6evjydPNO35noIY1ETy7AppB8HlctY903u_iRGh4ur4mKf4snduQbpDr9EARG0c_6styaiwhxkshkrHLKov0C_ZZPNqAZ5ItN2QuBShyNtaKzWPCPjF4EPANVnFjdEH8Up4WpShMX3-N1wQb3IQmNf9kU04YFwkTJn8HECFseGRmZAvG8x0W5PcQik5\"}" - x509SaMissingSigningTime := "{\"payload\":\"eyJ0YXJnZXRBcnRpZmFjdCI6eyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLm1hbmlmZXN0LnYxK2pzb24iLCJkaWdlc3QiOiJzaGEyNTY6NzNjODAzOTMwZWEzYmExZTU0YmMyNWMyYmRjNTNlZGQwMjg0YzYyZWQ2NTFmZTdiMDAzNjlkYTUxOWEzYzMzMyIsInNpemUiOjE2NzI0LCJhbm5vdGF0aW9ucyI6eyJpby53YWJiaXQtbmV0d29ya3MuYnVpbGRJZCI6IjEyMyJ9fX0\",\"protected\":\"eyJhbGciOiJQUzM4NCIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSIsInNpZ25lZENyaXRLZXkxIiwiaW8uY25jZi5ub3RhcnkuYXV0aGVudGljU2lnbmluZ1RpbWUiLCJpby5jbmNmLm5vdGFyeS5leHBpcnkiLCJpby5jbmNmLm5vdGFyeS52ZXJpZmljYXRpb25QbHVnaW4iLCJpby5jbmNmLm5vdGFyeS52ZXJpZmljYXRpb25QbHVnaW5NaW5WZXJzaW9uIl0sImN0eSI6ImFwcGxpY2F0aW9uL3ZuZC5jbmNmLm5vdGFyeS5wYXlsb2FkLnYxK2pzb24iLCJpby5jbmNmLm5vdGFyeS5leHBpcnkiOiIyMDIyLTA4LTA2VDIwOjAyOjU2LTA3OjAwIiwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSI6Im5vdGFyeS54NTA5LnNpZ25pbmdBdXRob3JpdHkiLCJpby5jbmNmLm5vdGFyeS52ZXJpZmljYXRpb25QbHVnaW4iOiJIb2xhIFBsdWdpbiIsImlvLmNuY2Yubm90YXJ5LnZlcmlmaWNhdGlvblBsdWdpbk1pblZlcnNpb24iOiIxLjEuMSIsInNpZ25lZENyaXRLZXkxIjoic2lnbmVkVmFsdWUxIiwic2lnbmVkS2V5MSI6InNpZ25lZEtleTIifQ\",\"header\":{\"x5c\":[\"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\"],\"io.cncf.notary.SigningAgent\":\"NotationUnitTest/1.0.0\"},\"signature\":\"AXGzB9wvKlrF7urv9qcFDluz4obIbZa53cPrjc4BzheRNojex6oHMbhBpoGYGQtzzE1sTHa4JeF5hGMBi14s1HAH4vpWWTWbLMwwzcDhCmjuAJBMkuZ5JPZ-vq86v6r8di4IKJGncNC53E8VqMHwbCO32Z7v3af7jlQWpRDcWPImkI1HM1NZtiHv7V5JdGGwHsBN8s_u-xrAK_gZytf1QfVmuRl34mU3N-sHjk00pxdyAzknDfc21-MDBk-waQ4BONFuF6h7VxCSvz7uckj2GOAiVJYKe_0ek6bfkzFIXMDHlMUlBzlLmxiGqa7D9GSdreKVDdoPdiizDXJmWKRP8UFSwkvYPHmnu8AoFTkZZjFbkQEsUnQerHJsXIBAndq3BzsD84xVygv5IrcX3VQY8hSi55tjuf7spi5Bkrqbcj_NtwowX_mGLQXA6SOdBPkd4UXo47bzwXRccU2CRhiwbig46eak6ZRVpiPWzyWL1dwuKSH6gRq5zJpH5QojotHz\"}\n" - for _, sig := range []string{x509WithAuthSigningTime, x509SaWithSigningTime, x509SaMissingSigningTime} { - env, _ := newJWSEnvelopeFromBytes([]byte(sig)) - if err := env.validateIntegrity(); err != nil { - t.Errorf("validateIntegrity(). Error = %s", err) - } - - if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { - t.Errorf("getSignerInfo. Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) - } - } - }) -} - -// Tests various scenarios around getSignerInfo method -func TestGetSignerInfo(t *testing.T) { - t.Run("with newJWSEnvelope before sign returns error", func(t *testing.T) { - env := jwsEnvelope{} - _, err := env.getSignerInfo() - if !(err != nil && errors.As(err, new(SignatureNotFoundError))) { - t.Errorf("Expected SignatureNotFoundError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with newJWSEnvelope after sign works", func(t *testing.T) { - env := jwsEnvelope{} - env.signPayload(getSignRequest()) - _, err := env.getSignerInfo() - if err != nil { - t.Errorf("getSignerInfo(). Error = %s", err) - } - }) - - t.Run("with NewJWSEnvelopeFromBytes works", func(t *testing.T) { - env, _ := newJWSEnvelopeFromBytes([]byte(TestValidSig)) - _, err := env.getSignerInfo() - if err != nil { - t.Errorf("getSignerInfo(). Error = %s", err) - } - }) - - t.Run("with invalid base64 bytes sig envelope returns error", func(t *testing.T) { - env, _ := newJWSEnvelopeFromBytes([]byte("{\"Payload\":\"Hi!\",\"Protected\":\"Hi\",\"Header\":{},\"Signature\":\"Hi!\"}")) - if _, err := env.getSignerInfo(); err == nil { - t.Errorf("Expected error but not found") - } - }) - - t.Run("with invalid singing time returns error", func(t *testing.T) { - env, _ := newJWSEnvelopeFromBytes([]byte("{\"Payload\":\"eyJhbGciOiJIUzI1NiJ9\",\"Protected\":\"eyJhbGciOiJQUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiXSwiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiOiIyMDA2LS0wMlQxNTowNDowNVoifQ\"" + - ",\"Header\":{},\"Signature\":\"YjGj\"}")) - if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { - t.Errorf("Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with missing crit header returns error", func(t *testing.T) { - sig := "{\"payload\":\"eyJ0YXJnZXRBcnRpZmFjdCI6eyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLm1hbmlmZXN0LnYxK2pzb24iLCJkaWdlc3QiOiJzaGEyNTY6NzNjODAzOTMwZWEzYmExZTU0YmMyNWMyYmRjNTNlZGQwMjg0YzYyZWQ2NTFmZTdiMDAzNjlkYTUxOWEzYzMzMyIsInNpemUiOjE2NzI0LCJhbm5vdGF0aW9ucyI6eyJpby53YWJiaXQtbmV0d29ya3MuYnVpbGRJZCI6IjEyMyJ9fX0\"," + - "\"protected\":\"eyJhbGciOiJQUzM4NCIsImN0eSI6ImFwcGxpY2F0aW9uL3ZuZC5jbmNmLm5vdGFyeS52Mi5qd3MudjEiLCJpby5jbmNmLm5vdGFyeS5zaWduaW5nVGltZSI6IjIwMjItMDctMDZUMjA6MjU6NDYtMDc6MDAifQ\"," + - "\"header\":{" + - "\"x5c\":[" + - "\"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\"]}," + - "\"signature\":\"jmobgyxycStcafuQ6_2RIixMUNH5CCm_AgnoITqrFFDgLbIx3ItMFoQCs7YRhtwdu5B_OQF5tQ_ZWAu4EFJ_yJelpNqxJLM4S5dyprZ0_s5i_FyBsDQ91SV-pS8gdhIeCUfbE7CwsY1JxisHk8YlK-dPvtOAzEfGlyIR80tQ6YB9nuK6QQSjmmRH8e4p5j_HpvddizLZJRXgna2b3ijHGOzdXlvimdFbVKKZE7fO-WYJVpGqkU-WCUARioo-j95coaLtBbk0b3I85JqEpVupzujGeViosG6-qcPOnO-d9izAQn6TtR6hqMDHqvYDvbCFXOBcWn7pSHH-9pS4q8-xAf6BqOPfgUH9n-na2e0X5vYHCaAKkWzKzEXnimBlPJh7LPjr-nm4_vjrY5Sj8ZEa_J6keKwZENPCQ-BqZBPrQbEbw9qdaOLYEsZl8FWTK-jTOVmpw1sXWYh3WzYh1HzPcqVcdAFk0LluwXVxde1By1cMxZ3xlKj7j7zKqkCWaQJB\"}\n" - env, _ := newJWSEnvelopeFromBytes([]byte(sig)) - if err := env.validateIntegrity(); err != nil { - t.Errorf("validateIntegrity(). Error: %s", err.Error()) - } - - if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { - t.Errorf("Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with malformed alg header returns error", func(t *testing.T) { - env, _ := newJWSEnvelopeFromBytes([]byte("{\"Payload\":\"eyJhbGciOiJIUzI1NiJ9\",\"Protected\":\"eyJhbGciOjEzLCJjcml0IjpbImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdUaW1lIl0sImlvLmNuY2Yubm90YXJ5LnNpbmluZ1RpbWUiOiIyMDA2LTAxLTAyVDE1OjA0OjA1WiJ9\"" + - ",\"Header\":{},\"Signature\":\"YjGj\"}")) - if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { - t.Errorf("Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with malformed cty header returns error", func(t *testing.T) { - env, _ := newJWSEnvelopeFromBytes([]byte("{\"Payload\":\"eyJhbGciOiJIUzI1NiJ9\",\"Protected\":\"eyJhbGciOiJQUzUxMiIsImN0eSI6MTIzLCJjcml0IjpbImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdUaW1lIl0sImlvLmNuY2Yubm90YXJ5LnNpbmluZ1RpbWUiOiIyMDA2LTAxLTAyVDE1OjA0OjA1WiJ9\"" + - ",\"Header\":{},\"Signature\":\"YjGj\"}")) - if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(InvalidSignatureError))) { - t.Errorf("Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) - } - }) -} - -// Tests various scenarios around signPayload method -func TestSignPayload(t *testing.T) { - env := jwsEnvelope{} - t.Run("using rsa key with newJWSEnvelope works", func(t *testing.T) { - req := getSignRequest() - _, err := env.signPayload(req) - if err != nil { - t.Errorf("getSignerInfo(). Error = %s", err) - } - }) - - t.Run("using ec key with newJWSEnvelope works", func(t *testing.T) { - certs := []*x509.Certificate{testhelper.GetECLeafCertificate().Cert, testhelper.GetECRootCertificate().Cert} - req := getSignRequest() - req.SignatureProvider, _ = NewLocalSignatureProvider(certs, testhelper.GetECLeafCertificate().PrivateKey) - _, err := env.signPayload(req) - if err != nil { - t.Errorf("getSignerInfo(). Error = %s", err) - } - }) -} - -func TestSignPayloadError(t *testing.T) { - env := jwsEnvelope{} - req := getSignRequest() - t.Run("when SignatureProvider'KeySpec returns error", func(t *testing.T) { - req.SignatureProvider = ErrorSignatureProvider{KeySpecError: true} - if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(InvalidSignRequestError))) { - t.Errorf("signPayload(). Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("when SignatureProvider'SignError returns error", func(t *testing.T) { - req.SignatureProvider = ErrorSignatureProvider{SignError: true} - if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(InvalidSignRequestError))) { - t.Errorf("signPayload(). Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("when SignatureProvider'Sign returns invalid certificate chain", func(t *testing.T) { - req.SignatureProvider = ErrorSignatureProvider{InvalidCertChain: true} - if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(InvalidSignRequestError))) { - t.Errorf("signPayload(). Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("when SignatureProvider'KeySpec returns invalid value", func(t *testing.T) { - req.SignatureProvider = ErrorSignatureProvider{InvalidKeySpec: true} - if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(UnsupportedSignatureAlgoError))) { - t.Errorf("signPayload(). Expected MalformedSignatureError but found %q", reflect.TypeOf(err)) - } - }) -} - -type ErrorSignatureProvider struct { - KeySpecError bool - SignError bool - InvalidCertChain bool - InvalidKeySpec bool -} - -func (sp ErrorSignatureProvider) KeySpec() (KeySpec, error) { - if sp.KeySpecError { - return RSA_2048, fmt.Errorf("intentional KeySpec() error") - } - - if sp.InvalidKeySpec { - return "", nil - } - return RSA_2048, nil -} - -func (sp ErrorSignatureProvider) Sign(bytes []byte) ([]byte, []*x509.Certificate, error) { - if sp.SignError { - return nil, nil, fmt.Errorf("intentional Sign() error") - } - - rsaRoot := testhelper.GetRSARootCertificate() - pk, _ := rsa.GenerateKey(rand.Reader, 2048) - certTuple := testhelper.GetRSACertTupleWithPK(pk, "TestSignPayloadError_"+strconv.Itoa(pk.Size()), &rsaRoot) - if sp.InvalidCertChain { - lsp, _ := NewLocalSignatureProvider([]*x509.Certificate{certTuple.Cert, testhelper.GetECRootCertificate().Cert}, pk) - return lsp.Sign(bytes) - } - - lsp, _ := NewLocalSignatureProvider([]*x509.Certificate{certTuple.Cert, rsaRoot.Cert}, pk) - return lsp.Sign(bytes) -} diff --git a/signature/jwt.go b/signature/jwt.go deleted file mode 100644 index 394c1dec..00000000 --- a/signature/jwt.go +++ /dev/null @@ -1,71 +0,0 @@ -package signature - -import ( - "crypto" - "crypto/ecdsa" - "crypto/rsa" - "fmt" - - "github.com/golang-jwt/jwt/v4" -) - -// verifyJWT verifies the JWT token against the specified verification key -func verifyJWT(tokenString string, key crypto.PublicKey) error { - signingMethod, err := getSigningMethod(key) - if err != nil { - return err - } - - // parse and verify token - parser := &jwt.Parser{ - ValidMethods: []string{"PS256", "PS384", "PS512", "ES256", "ES384", "ES512"}, - UseJSONNumber: true, - SkipClaimsValidation: true, - } - - if _, err := parser.Parse(tokenString, func(t *jwt.Token) (interface{}, error) { - if t.Method.Alg() != signingMethod.Alg() { - return nil, InvalidSignatureError{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 nil -} - -// getSigningMethod picks up a recommended algorithm for given public keys. -func getSigningMethod(key crypto.PublicKey) (jwt.SigningMethod, error) { - switch key := key.(type) { - case *rsa.PublicKey: - switch key.Size() { - case 256: - return jwt.SigningMethodPS256, nil - case 384: - return jwt.SigningMethodPS384, nil - case 512: - return jwt.SigningMethodPS512, nil - default: - return nil, UnsupportedSigningKeyError{ - Msg: fmt.Sprintf("rsa key size %d is not supported", key.Size()), - } - } - case *ecdsa.PublicKey: - switch bitSize := key.Curve.Params().BitSize; bitSize { - case jwt.SigningMethodES256.CurveBits: - return jwt.SigningMethodES256, nil - case jwt.SigningMethodES384.CurveBits: - return jwt.SigningMethodES384, nil - case jwt.SigningMethodES512.CurveBits: - return jwt.SigningMethodES512, nil - default: - return nil, UnsupportedSigningKeyError{ - Msg: fmt.Sprintf("ecdsa key size %d is not supported", bitSize), - } - } - } - return nil, UnsupportedSigningKeyError{} -} diff --git a/signature/jwt_test.go b/signature/jwt_test.go deleted file mode 100644 index 15cb0aeb..00000000 --- a/signature/jwt_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package signature - -import ( - "crypto/ecdsa" - "crypto/ed25519" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "errors" - "reflect" - "testing" - - "github.com/golang-jwt/jwt/v4" -) - -func TestVerifyJWTError(t *testing.T) { - t.Run("with_unsupported_signingKey", func(t *testing.T) { - edKey, _, _ := ed25519.GenerateKey(rand.Reader) - err := verifyJWT("", &edKey) - if !(err != nil && errors.As(err, new(UnsupportedSigningKeyError))) { - t.Errorf("verifyJWT(). Expected UnsupportedSigningKeyError but but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with_signature_containing_unsupported_alg", func(t *testing.T) { - jwtSig := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" - rsaKey, _ := rsa.GenerateKey(rand.Reader, 2048) - err := verifyJWT(jwtSig, &rsaKey.PublicKey) - if !(err != nil && errors.As(err, new(SignatureIntegrityError))) { - t.Errorf("verifyJWT(). Expected UnsupportedSigningKeyError but but found %q %s", reflect.TypeOf(err), err) - } - }) -} - -func TestGetSigningMethod(t *testing.T) { - for k, v := range map[int]*jwt.SigningMethodRSAPSS{ - 2048: jwt.SigningMethodPS256, - 3072: jwt.SigningMethodPS384, - 4096: jwt.SigningMethodPS512} { - key, _ := rsa.GenerateKey(rand.Reader, k) - m, err := getSigningMethod(&key.PublicKey) - if m != v || err != nil { - t.Fatalf("GenerateKey(). error = %v", err) - } - } - - for k, v := range map[elliptic.Curve]*jwt.SigningMethodECDSA{ - elliptic.P256(): jwt.SigningMethodES256, - elliptic.P384(): jwt.SigningMethodES384, - elliptic.P521(): jwt.SigningMethodES512} { - key, _ := ecdsa.GenerateKey(k, rand.Reader) - m, err := getSigningMethod(&key.PublicKey) - if m != v || err != nil { - t.Fatalf("GenerateKey(). error = %v", err) - } - } -} - -func TestGetSigningMethodError(t *testing.T) { - t.Run("with_unsupported_rsa_Key", func(t *testing.T) { - rsaKey, _ := rsa.GenerateKey(rand.Reader, 6114) - _, err := getSigningMethod(&rsaKey.PublicKey) - if !(err != nil && errors.As(err, new(UnsupportedSigningKeyError))) { - t.Errorf("GenerateKey(). Expected UnsupportedSigningKeyError but but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with_unsupported_ec_Key", func(t *testing.T) { - esKey, _ := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) - _, err := getSigningMethod(&esKey.PublicKey) - if !(err != nil && errors.As(err, new(UnsupportedSigningKeyError))) { - t.Errorf("GenerateKey(). Expected UnsupportedSigningKeyError but but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("with_unsupported_ed_Key", func(t *testing.T) { - edKey, _, _ := ed25519.GenerateKey(rand.Reader) - _, err := getSigningMethod(&edKey) - if !(err != nil && errors.As(err, new(UnsupportedSigningKeyError))) { - t.Errorf("GenerateKey(). Expected UnsupportedSigningKeyError but but found %q", reflect.TypeOf(err)) - } - }) -} diff --git a/signature/signaturetest/algorithm.go b/signature/signaturetest/algorithm.go new file mode 100644 index 00000000..93a8e9c2 --- /dev/null +++ b/signature/signaturetest/algorithm.go @@ -0,0 +1,18 @@ +package signaturetest + +import "github.com/notaryproject/notation-core-go/signature" + +// KeyTypes contains supported key type +var KeyTypes = []signature.KeyType{signature.KeyTypeRSA, signature.KeyTypeEC} + +// GetKeySizes returns the supported key size for the named keyType +func GetKeySizes(keyType signature.KeyType) []int { + switch keyType { + case signature.KeyTypeRSA: + return []int{2048, 3072, 4096} + case signature.KeyTypeEC: + return []int{256, 384, 521} + default: + return nil + } +} diff --git a/signature/signaturetest/signer.go b/signature/signaturetest/signer.go new file mode 100644 index 00000000..3596d153 --- /dev/null +++ b/signature/signaturetest/signer.go @@ -0,0 +1,52 @@ +package signaturetest + +import ( + "crypto/elliptic" + "crypto/x509" + "fmt" + + "github.com/notaryproject/notation-core-go/signature" + "github.com/notaryproject/notation-core-go/testhelper" +) + +// GetTestLocalSigner returns the local signer with given keyType and size for testing +func GetTestLocalSigner(keyType signature.KeyType, size int) (signature.Signer, error) { + switch keyType { + case signature.KeyTypeEC: + switch size { + case 256: + leafCertTuple := testhelper.GetECCertTuple(elliptic.P256()) + certs := []*x509.Certificate{leafCertTuple.Cert, testhelper.GetECRootCertificate().Cert} + return signature.NewLocalSigner(certs, leafCertTuple.PrivateKey) + case 384: + leafCertTuple := testhelper.GetECCertTuple(elliptic.P384()) + certs := []*x509.Certificate{leafCertTuple.Cert, testhelper.GetECRootCertificate().Cert} + return signature.NewLocalSigner(certs, leafCertTuple.PrivateKey) + case 521: + leafCertTuple := testhelper.GetECCertTuple(elliptic.P521()) + certs := []*x509.Certificate{leafCertTuple.Cert, testhelper.GetECRootCertificate().Cert} + return signature.NewLocalSigner(certs, leafCertTuple.PrivateKey) + default: + return nil, fmt.Errorf("key size not supported") + } + case signature.KeyTypeRSA: + switch size { + case 2048: + leafCertTuple := testhelper.GetRSACertTuple(2048) + certs := []*x509.Certificate{leafCertTuple.Cert, testhelper.GetRSARootCertificate().Cert} + return signature.NewLocalSigner(certs, leafCertTuple.PrivateKey) + case 3072: + leafCertTuple := testhelper.GetRSACertTuple(3072) + certs := []*x509.Certificate{leafCertTuple.Cert, testhelper.GetRSARootCertificate().Cert} + return signature.NewLocalSigner(certs, leafCertTuple.PrivateKey) + case 4096: + leafCertTuple := testhelper.GetRSACertTuple(4096) + certs := []*x509.Certificate{leafCertTuple.Cert, testhelper.GetRSARootCertificate().Cert} + return signature.NewLocalSigner(certs, leafCertTuple.PrivateKey) + default: + return nil, fmt.Errorf("key size not supported") + } + default: + return nil, fmt.Errorf("keyType not supported") + } +} diff --git a/signature/types.go b/signature/types.go index 30a0d02d..923ecf5f 100644 --- a/signature/types.go +++ b/signature/types.go @@ -65,6 +65,8 @@ type Attribute struct { // SignRequest is used to generate Signature. type SignRequest struct { // Payload is the payload to be signed. + // + // For JWS envelope, Payload.Content is limited to be JSON format. Payload Payload // Signer is the signer used to sign the digest. @@ -128,6 +130,8 @@ type Payload struct { ContentType string // Content contains the raw bytes of the payload. + // + // For JWS envelope, Content is limited to be JSON format. Content []byte } diff --git a/signature/utils.go b/signature/utils.go deleted file mode 100644 index c07847f3..00000000 --- a/signature/utils.go +++ /dev/null @@ -1,84 +0,0 @@ -package signature - -import ( - "crypto" - "crypto/ecdsa" - "crypto/rand" - "crypto/rsa" - "crypto/x509" -) - -// 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, &InvalidArgumentError{Param: "certs"} - } - - ks, err := ExtractKeySpec(certs[0]) - if err != nil { - return nil, err - } - - return &LocalSignatureProvider{ - key: pk, - certs: certs, - keySpec: ks, - }, nil -} - -// LocalSignatureProvider implements SignatureEnvelope's SignatureProvider to facilitate signing using local certificates and private key. -type LocalSignatureProvider struct { - keySpec KeySpec - key crypto.PrivateKey - certs []*x509.Certificate -} - -func (l *LocalSignatureProvider) Sign(bytes []byte) ([]byte, []*x509.Certificate, error) { - // calculate hash - hasher := l.keySpec.SignatureAlgorithm().Hash().HashFunc() - h := hasher.New() - h.Write(bytes) - hash := h.Sum(nil) - - // sign - switch key := l.key.(type) { - case *rsa.PrivateKey: - sig, err := rsa.SignPSS(rand.Reader, key, hasher.HashFunc(), hash, &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}) - if err != nil { - return nil, nil, err - } - return sig, l.certs, nil - case *ecdsa.PrivateKey: - r, s, err := ecdsa.Sign(rand.Reader, key, hash) - if err != nil { - return nil, nil, err - } - - curveBits := key.Curve.Params().BitSize - keyBytes := curveBits / 8 - if curveBits%8 > 0 { - keyBytes += 1 - } - - out := make([]byte, 2*keyBytes) - r.FillBytes(out[0:keyBytes]) // r is assigned to the first half of output. - s.FillBytes(out[keyBytes:]) // s is assigned to the second half of output. - return out, l.certs, nil - } - - return nil, nil, UnsupportedSigningKeyError{} -} - -func (l *LocalSignatureProvider) KeySpec() (KeySpec, error) { - return l.keySpec, nil -} - -// getSignatureAlgorithm picks up a recommended signing algorithm for given certificate. -func getSignatureAlgorithm(signingCert *x509.Certificate) (Algorithm, error) { - keySpec, err := ExtractKeySpec(signingCert) - if err != nil { - return 0, err - } - - return keySpec.SignatureAlgorithm(), nil -} diff --git a/signature/utils_test.go b/signature/utils_test.go deleted file mode 100644 index cbdd1324..00000000 --- a/signature/utils_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package signature - -import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "errors" - "fmt" - "math" - "math/big" - "reflect" - "strconv" - "testing" - - "github.com/notaryproject/notation-core-go/internal/crypto/hashutil" - "github.com/notaryproject/notation-core-go/testhelper" -) - -// Tests local signature provider for various size of RSA and EC certificates -func TestLocalSignatureProvider(t *testing.T) { - payload := []byte("SignMe!") - rsaRoot := testhelper.GetRSARootCertificate() - for k, v := range map[int]crypto.Hash{2048: crypto.SHA256, 3072: crypto.SHA384, 4096: crypto.SHA512} { - pk, _ := rsa.GenerateKey(rand.Reader, k) - certTuple := testhelper.GetRSACertTupleWithPK(pk, "TestDeriveSignatureAlgorithm_"+strconv.Itoa(pk.Size()), &rsaRoot) - t.Run(fmt.Sprintf("for RSA certificates of size %d", pk.Size()), func(t *testing.T) { - lsp, err := NewLocalSignatureProvider([]*x509.Certificate{certTuple.Cert, rsaRoot.Cert}, pk) - if err != nil { - t.Errorf("NewLocalSignatureProvider(). Error: %s", err) - } - - sig, certs, err := lsp.Sign(payload) - if err != nil { - t.Errorf("Sign(). Error: %s", err) - } - - if !(certs[0] == certTuple.Cert && certs[1] == rsaRoot.Cert) { - t.Error("Signing certificates Mismatch") - } - - hs, _ := hashutil.ComputeHash(v, payload) - err = rsa.VerifyPSS(&pk.PublicKey, v, hs, sig, &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}) - if err != nil { - t.Errorf("Invalid signature generated. Error: %s", err) - } - }) - } - - ecRoot := testhelper.GetECRootCertificate() - for k, v := range map[elliptic.Curve]crypto.Hash{elliptic.P256(): crypto.SHA256, elliptic.P384(): crypto.SHA384, elliptic.P521(): crypto.SHA512} { - pk, _ := ecdsa.GenerateKey(k, rand.Reader) - certTuple := testhelper.GetECDSACertTupleWithPK(pk, "TestDeriveSignatureAlgorithm_"+strconv.Itoa(pk.Params().BitSize), &ecRoot) - - t.Run(fmt.Sprintf("for EC certificates of size %d", pk.Params().BitSize), func(t *testing.T) { - lsp, err := NewLocalSignatureProvider([]*x509.Certificate{certTuple.Cert, ecRoot.Cert}, pk) - if err != nil { - t.Errorf("NewLocalSignatureProvider(). Error: %s", err) - } - - sig, certs, err := lsp.Sign(payload) - if err != nil { - t.Errorf("Sign(). Error: %s", err) - } - - if !(certs[0] == certTuple.Cert && certs[1] == ecRoot.Cert) { - t.Error("Signing certificates Mismatch") - } - - hs, _ := hashutil.ComputeHash(v, payload) - keysize := int(math.Ceil(float64(pk.Curve.Params().BitSize) / float64(8))) - r := big.NewInt(0).SetBytes(sig[:keysize]) - s := big.NewInt(0).SetBytes(sig[keysize:]) - ok := ecdsa.Verify(&pk.PublicKey, hs, r, s) - if !ok { - t.Errorf("Invalid signature generated. Error: %s", err) - } - }) - } -} - -// Tests various scenarios around generating a signature envelope -func TestLocalSignatureProviderError(t *testing.T) { - t.Run("for unsupported RSA certificate", func(t *testing.T) { - k := 1024 - rsaRoot := testhelper.GetRSARootCertificate() - pk, _ := rsa.GenerateKey(rand.Reader, k) - certTuple := testhelper.GetRSACertTupleWithPK(pk, "TestDeriveSignatureAlgorithm_"+strconv.Itoa(pk.Size()), &rsaRoot) - - _, err := NewLocalSignatureProvider([]*x509.Certificate{certTuple.Cert, rsaRoot.Cert}, pk) - if !(err != nil && errors.As(err, new(UnsupportedSigningKeyError))) { - t.Errorf("Expected UnsupportedSigningKeyError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("for unsupported EC certificate", func(t *testing.T) { - ecRoot := testhelper.GetECRootCertificate() - k := elliptic.P224() - pk, _ := ecdsa.GenerateKey(k, rand.Reader) - certTuple := testhelper.GetECDSACertTupleWithPK(pk, "TestDeriveSignatureAlgorithm_"+strconv.Itoa(pk.Params().BitSize), &ecRoot) - - _, err := NewLocalSignatureProvider([]*x509.Certificate{certTuple.Cert, ecRoot.Cert}, pk) - if !(err != nil && errors.As(err, new(UnsupportedSigningKeyError))) { - t.Errorf("Expected UnsupportedSigningKeyError but found %q", reflect.TypeOf(err)) - } - }) -} - -func TestDeriveSignatureAlgorithm(t *testing.T) { - rsaRoot := testhelper.GetRSARootCertificate() - for _, v := range []int{2048, 3072, 4096} { - pk, _ := rsa.GenerateKey(rand.Reader, v) - certTuple := testhelper.GetRSACertTupleWithPK(pk, "TestDeriveSignatureAlgorithm_"+strconv.Itoa(pk.Size()), &rsaRoot) - t.Run(fmt.Sprintf("for RSA certificates of size %d", pk.Size()), func(t *testing.T) { - _, err := getSignatureAlgorithm(certTuple.Cert) - if err != nil { - t.Errorf("getSignatureAlgorithm(). Error: %s", err) - } - }) - } - - ecRoot := testhelper.GetECRootCertificate() - for _, v := range []elliptic.Curve{elliptic.P256(), elliptic.P384(), elliptic.P521()} { - pk, _ := ecdsa.GenerateKey(v, rand.Reader) - certTuple := testhelper.GetECDSACertTupleWithPK(pk, "TestDeriveSignatureAlgorithm_"+strconv.Itoa(pk.Params().BitSize), &ecRoot) - t.Run(fmt.Sprintf("for EC certificates of size %d", pk.Params().BitSize), func(t *testing.T) { - _, err := getSignatureAlgorithm(certTuple.Cert) - if err != nil { - t.Errorf("getSignatureAlgorithm(). Error: %s", err) - } - }) - } -} - -func TestDeriveSignatureAlgorithmError(t *testing.T) { - t.Run("for unsupported RSA certificate", func(t *testing.T) { - rsaRoot := testhelper.GetRSARootCertificate() - pk, _ := rsa.GenerateKey(rand.Reader, 1024) - certTuple := testhelper.GetRSACertTupleWithPK(pk, "TestDeriveSignatureAlgorithm_"+strconv.Itoa(pk.Size()), &rsaRoot) - - _, err := getSignatureAlgorithm(certTuple.Cert) - if !(err != nil && errors.As(err, new(UnsupportedSigningKeyError))) { - t.Errorf("Expected UnsupportedSigningKeyError but found %q", reflect.TypeOf(err)) - } - }) - - t.Run("for unsupported EC certificate", func(t *testing.T) { - ecRoot := testhelper.GetECRootCertificate() - pk, _ := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) - certTuple := testhelper.GetECDSACertTupleWithPK(pk, "TestDeriveSignatureAlgorithm_"+strconv.Itoa(pk.Params().BitSize), &ecRoot) - - _, err := getSignatureAlgorithm(certTuple.Cert) - if !(err != nil && errors.As(err, new(UnsupportedSigningKeyError))) { - t.Errorf("Expected UnsupportedSigningKeyError but found %q", reflect.TypeOf(err)) - } - }) -}