diff --git a/signature/algorithm.go b/signature/algorithm.go index 476de3d1..c6299c45 100644 --- a/signature/algorithm.go +++ b/signature/algorithm.go @@ -31,6 +31,18 @@ const ( KeyTypeEC // KeyType EC ) +// String is the stringer function for KeyType +func (keyType KeyType) String() string { + switch keyType { + case KeyTypeRSA: + return "RSA" + case KeyTypeEC: + return "ECDSA" + default: + return fmt.Sprintf("unknown key type: %d", keyType) + } +} + // KeySpec defines a key type and size. type KeySpec struct { Type KeyType diff --git a/signature/algorithm_test.go b/signature/algorithm_test.go index 40066772..6555f49b 100644 --- a/signature/algorithm_test.go +++ b/signature/algorithm_test.go @@ -229,3 +229,28 @@ func TestSignatureAlgorithm(t *testing.T) { }) } } + +func TestKeyTypeStringer(t *testing.T) { + testCase := []struct { + keyType KeyType + str string + }{ + { + keyType: KeyTypeEC, + str: "ECDSA", + }, + { + keyType: KeyTypeRSA, + str: "RSA", + }, + { + keyType: 10, + str: "unknown key type: 10", + }, + } + for _, tt := range testCase { + if tt.keyType.String() != tt.str { + t.Fatalf("keyType: %s stringer test failed", tt.keyType) + } + } +} diff --git a/signature/cose/envelope_test.go b/signature/cose/envelope_test.go index 95fb4889..28fb0cc6 100644 --- a/signature/cose/envelope_test.go +++ b/signature/cose/envelope_test.go @@ -2,7 +2,6 @@ package cose import ( "crypto" - "crypto/elliptic" "crypto/x509" "errors" "fmt" @@ -10,6 +9,7 @@ import ( "time" "github.com/notaryproject/notation-core-go/signature" + "github.com/notaryproject/notation-core-go/signature/signaturetest" "github.com/notaryproject/notation-core-go/testhelper" "github.com/veraison/go-cose" ) @@ -20,7 +20,6 @@ const ( var ( signingSchemeString = []string{"notary.x509", "notary.x509.signingAuthority"} - keyType = []signature.KeyType{signature.KeyTypeRSA, signature.KeyTypeEC} ) func TestParseEnvelopeError(t *testing.T) { @@ -39,14 +38,8 @@ func TestParseEnvelopeError(t *testing.T) { func TestSign(t *testing.T) { env := envelope{} for _, signingScheme := range signingSchemeString { - for _, keyType := range keyType { - var size []int - if keyType == signature.KeyTypeRSA { - size = []int{2048, 3072, 4096} - } else { - size = []int{256, 384, 521} - } - for _, size := range size { + for _, keyType := range signaturetest.KeyTypes { + for _, size := range signaturetest.GetKeySizes(keyType) { t.Run(fmt.Sprintf("with %s scheme, %v keyType, %v keySize when all arguments are present", signingScheme, keyType, size), func(t *testing.T) { signRequest, err := newSignRequest(signingScheme, keyType, size) if err != nil { @@ -59,7 +52,7 @@ func TestSign(t *testing.T) { }) t.Run(fmt.Sprintf("with %s scheme, %v keyType, %v keySize when minimal arguments are present", signingScheme, keyType, size), func(t *testing.T) { - signer, err := getTestSigner(keyType, size) + signer, err := signaturetest.GetTestLocalSigner(keyType, size) if err != nil { t.Fatalf("Sign() failed. Error = %s", err) } @@ -654,14 +647,8 @@ func TestSignerInfoErrors(t *testing.T) { func TestSignAndVerify(t *testing.T) { env := envelope{} for _, signingScheme := range signingSchemeString { - for _, keyType := range keyType { - var size []int - if keyType == signature.KeyTypeRSA { - size = []int{2048, 3072, 4096} - } else { - size = []int{256, 384, 521} - } - for _, size := range size { + for _, keyType := range signaturetest.KeyTypes { + for _, size := range signaturetest.GetKeySizes(keyType) { t.Run(fmt.Sprintf("with %s scheme, %v keyType, %v keySize", signingScheme, keyType, size), func(t *testing.T) { // Sign signRequest, err := newSignRequest(signingScheme, keyType, size) @@ -686,14 +673,8 @@ func TestSignAndVerify(t *testing.T) { func TestSignAndParseVerify(t *testing.T) { for _, signingScheme := range signingSchemeString { - for _, keyType := range keyType { - var size []int - if keyType == signature.KeyTypeRSA { - size = []int{2048, 3072, 4096} - } else { - size = []int{256, 384, 521} - } - for _, size := range size { + for _, keyType := range signaturetest.KeyTypes { + for _, size := range signaturetest.GetKeySizes(keyType) { t.Run(fmt.Sprintf("with %s scheme, %v keyType, %v keySize", signingScheme, keyType, size), func(t *testing.T) { //Verify after UnmarshalCBOR env, err := getVerifyCOSE(signingScheme, keyType, size) @@ -712,7 +693,7 @@ func TestSignAndParseVerify(t *testing.T) { } func newSignRequest(signingScheme string, keyType signature.KeyType, size int) (*signature.SignRequest, error) { - signer, err := getTestSigner(keyType, size) + signer, err := signaturetest.GetTestLocalSigner(keyType, size) if err != nil { return nil, err } @@ -733,47 +714,6 @@ func newSignRequest(signingScheme string, keyType signature.KeyType, size int) ( }, nil } -func getTestSigner(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") - } -} - func getSignRequest() (*signature.SignRequest, error) { return newSignRequest("notary.x509", signature.KeyTypeRSA, 3072) } diff --git a/signature/errors.go b/signature/errors.go index 2428c754..9534c006 100644 --- a/signature/errors.go +++ b/signature/errors.go @@ -122,5 +122,15 @@ type EnvelopeKeyRepeatedError struct { // Error returns the formatted error message. func (e *EnvelopeKeyRepeatedError) Error() string { - return fmt.Sprintf("repeated key: %q exists in the envelope.", e.Key) + return fmt.Sprintf(`repeated key: "%s" exists in the both protected header and extended signed attributes.`, e.Key) +} + +// RemoteSigningError is used when remote signer causes the error. +type RemoteSigningError struct { + Msg string +} + +// Error returns formated remote signing error +func (e *RemoteSigningError) Error() string { + return fmt.Sprintf("remote signing error. Error: %s", e.Msg) } diff --git a/signature/errors_test.go b/signature/errors_test.go index c80cdf10..9687d98b 100644 --- a/signature/errors_test.go +++ b/signature/errors_test.go @@ -194,9 +194,18 @@ func TestUnsupportedSignatureFormatError(t *testing.T) { func TestEnvelopeKeyRepeatedError(t *testing.T) { err := &EnvelopeKeyRepeatedError{Key: errMsg} - expectMsg := fmt.Sprintf("repeated key: %q exists in the envelope.", errMsg) + expectMsg := fmt.Sprintf(`repeated key: "%s" exists in the both protected header and extended signed attributes.`, errMsg) if err.Error() != expectMsg { t.Errorf("Expected %v but got %v", expectMsg, err.Error()) } -} \ No newline at end of file +} + +func TestRemoteSigningError(t *testing.T) { + err := &RemoteSigningError{Msg: errMsg} + expectMsg := fmt.Sprintf("remote signing error. Error: %s", 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 index 4c73d7d8..9919698a 100644 --- a/signature/internal/base/envelope.go +++ b/signature/internal/base/envelope.go @@ -171,7 +171,7 @@ func validateSignerInfo(info *signature.SignerInfo) error { ) } -// validateSigningTime checks that sigining time is within the valid range of +// validateSigningTime checks that signing time is within the valid range of // time duration. func validateSigningTime(signingTime, expireTime time.Time) error { if signingTime.IsZero() { diff --git a/signature/jws/envelope.go b/signature/jws/envelope.go index 521ec4b6..3dbb1e17 100644 --- a/signature/jws/envelope.go +++ b/signature/jws/envelope.go @@ -4,7 +4,6 @@ import ( "crypto/x509" "encoding/base64" "encoding/json" - "strings" "github.com/golang-jwt/jwt/v4" "github.com/notaryproject/notation-core-go/signature" @@ -46,24 +45,34 @@ func ParseEnvelope(envelopeBytes []byte) (signature.Envelope, error) { // Sign signs the envelope and return the encoded message func (e *envelope) Sign(req *signature.SignRequest) ([]byte, error) { - // get all attributes ready to be signed - signedAttrs, err := getSignedAttrs(req) - if err != nil { - return nil, err + // check signer type + var ( + method signingMethod + err error + ) + if localSigner, ok := req.Signer.(signature.LocalSigner); ok { + // for local signer + method, err = newLocalSigningMethod(localSigner) + } else { + // for remote signer + method, err = newRemoteSigningMethod(req.Signer) } - - // JWT sign - compact, err := sign(req.Payload.Content, signedAttrs, req.Signer) if err != nil { return nil, &signature.MalformedSignRequestError{Msg: err.Error()} } - // get certificate chain - certs, err := req.Signer.CertificateChain() + // get all attributes ready to be signed + signedAttrs, err := getSignedAttrs(req, method.Alg()) if err != nil { return nil, err } + // JWT sign and get certificate chain + compact, certs, err := sign(req.Payload.Content, signedAttrs, method) + if err != nil { + return nil, &signature.MalformedSignRequestError{Msg: err.Error()} + } + // generate envelope env, err := generateJWS(compact, req, certs) if err != nil { @@ -78,17 +87,6 @@ func (e *envelope) Sign(req *signature.SignRequest) ([]byte, error) { return encoded, 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}, ".") -} - // Verify checks the validity of the envelope and returns the payload and signerInfo func (e *envelope) Verify() (*signature.Payload, *signature.SignerInfo, error) { if e.internalEnvelope == nil { @@ -96,7 +94,7 @@ func (e *envelope) Verify() (*signature.Payload, *signature.SignerInfo, error) { } if len(e.internalEnvelope.Header.CertChain) == 0 { - return nil, nil, &signature.MalformedSignatureError{Msg: "malformed leaf certificate"} + return nil, nil, &signature.MalformedSignatureError{Msg: "certificate chain is not set"} } cert, err := x509.ParseCertificate(e.internalEnvelope.Header.CertChain[0]) @@ -106,7 +104,7 @@ func (e *envelope) Verify() (*signature.Payload, *signature.SignerInfo, error) { // verify JWT compact := compactJWS(e.internalEnvelope) - if err = verifyJWT(compact, cert); err != nil { + if err = verifyJWT(compact, cert.PublicKey); err != nil { return nil, nil, err } @@ -127,7 +125,7 @@ func (e *envelope) Verify() (*signature.Payload, *signature.SignerInfo, error) { // Payload returns the payload of JWS envelope func (e *envelope) Payload() (*signature.Payload, error) { if e.internalEnvelope == nil { - return nil, &signature.MalformedSignatureError{Msg: "missing jws signature envelope"} + return nil, &signature.SignatureNotFoundError{} } // parse protected header to get payload context type protected, err := parseProtectedHeaders(e.internalEnvelope.Protected) @@ -147,7 +145,7 @@ func (e *envelope) Payload() (*signature.Payload, error) { var claims jwtPayload _, _, err = parser.ParseUnverified(tokenString, &claims) if err != nil { - return nil, err + return nil, &signature.MalformedSignatureError{Msg: err.Error()} } return &signature.Payload{ @@ -161,14 +159,14 @@ func (e *envelope) SignerInfo() (*signature.SignerInfo, error) { if e.internalEnvelope == nil { return nil, &signature.SignatureNotFoundError{} } - var signInfo signature.SignerInfo + var signerInfo signature.SignerInfo // parse protected headers protected, err := parseProtectedHeaders(e.internalEnvelope.Protected) if err != nil { return nil, err } - if err := populateProtectedHeaders(protected, &signInfo); err != nil { + if err := populateProtectedHeaders(protected, &signerInfo); err != nil { return nil, err } @@ -180,7 +178,7 @@ func (e *envelope) SignerInfo() (*signature.SignerInfo, error) { if len(sig) == 0 { return nil, &signature.MalformedSignatureError{Msg: "cose envelope missing signature"} } - signInfo.Signature = sig + signerInfo.Signature = sig // parse headers var certs []*x509.Certificate @@ -191,34 +189,28 @@ func (e *envelope) SignerInfo() (*signature.SignerInfo, error) { } certs = append(certs, cert) } - signInfo.CertificateChain = certs - signInfo.UnsignedAttributes.SigningAgent = e.internalEnvelope.Header.SigningAgent - signInfo.UnsignedAttributes.TimestampSignature = e.internalEnvelope.Header.TimestampSignature - - return &signInfo, nil + signerInfo.CertificateChain = certs + signerInfo.UnsignedAttributes.SigningAgent = e.internalEnvelope.Header.SigningAgent + signerInfo.UnsignedAttributes.TimestampSignature = e.internalEnvelope.Header.TimestampSignature + return &signerInfo, nil } -// sign the given payload and headers using the given signing method and signature provider -func sign(payload jwtPayload, headers map[string]interface{}, signer signature.Signer) (string, error) { - var privateKey interface{} - var signingMethod jwt.SigningMethod - if localSigner, ok := signer.(signature.LocalSigner); ok { - // local signer - alg, err := extractJwtAlgorithm(localSigner) - if err != nil { - return "", err - } - signingMethod = jwt.GetSigningMethod(alg) - - // sign with private key - privateKey = localSigner.PrivateKey() - } else { - // remote signer - signingMethod = newRemoteSigningMethod(signer) - } +// sign the given payload and headers using the given signature provider +func sign(payload jwtPayload, headers map[string]interface{}, method signingMethod) (string, []*x509.Certificate, error) { // generate token - token := jwt.NewWithClaims(signingMethod, payload) + token := jwt.NewWithClaims(method, payload) token.Header = headers - return token.SignedString(privateKey) + // 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 index 61acfe63..b5ca15f9 100644 --- a/signature/jws/envelope_test.go +++ b/signature/jws/envelope_test.go @@ -1,20 +1,126 @@ 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(t) + } +} + +func checkErrorEqual(t *testing.T, want, got string) { + if 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) (*signature.SignRequest, error) { - certs := getSigningCerts() +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", @@ -26,10 +132,6 @@ func getSignReq(signingScheme signature.SigningScheme) (*signature.SignRequest, } } `) - signer, err := signature.NewLocalSigner(certs, testhelper.GetRSALeafCertificate().PrivateKey) - if err != nil { - return nil, err - } return &signature.SignRequest{ Payload: signature.Payload{ ContentType: signature.MediaTypePayloadV1, @@ -38,65 +140,456 @@ func getSignReq(signingScheme signature.SigningScheme) (*signature.SignRequest, Signer: signer, SigningTime: time.Now(), Expiry: time.Now().Add(time.Hour), - ExtendedSignedAttributes: nil, + 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 signCore(signingScheme signature.SigningScheme) ([]byte, error) { - signReq, err := getSignReq(signingScheme) +func getEnvelope(signingScheme signature.SigningScheme, isLocal bool, extendedSignedAttribute []signature.Attribute) (*jwsEnvelope, error) { + encoded, err := getEncodedMessage(signingScheme, isLocal, extendedSignedAttribute) if err != nil { return nil, err } - e := NewEnvelope() - return e.Sign(signReq) + var sigEnv jwsEnvelope + err = json.Unmarshal(encoded, &sigEnv) + if err != nil { + return nil, err + } + return &sigEnv, nil } -func verifyCore(encoded []byte) (*signature.Payload, *signature.SignerInfo, error) { - env, err := ParseEnvelope(encoded) +func getEncodedMessage(signingScheme signature.SigningScheme, isLocal bool, extendedSignedAttribute []signature.Attribute) ([]byte, error) { + signer, err := getSigner(isLocal, nil, nil) if err != nil { - return nil, nil, err + return nil, err } - return env.Verify() + + signReq, err := getSignReq(signingScheme, signer, extendedSignedAttribute) + if err != nil { + return nil, err + } + e := envelope{} + return e.Sign(signReq) } -func Test_envelope_Verify_X509(t *testing.T) { - encoded, err := signCore(signature.SigningSchemeX509) +func getSignedEnvelope(signingScheme signature.SigningScheme, isLocal bool, extendedSignedAttribute []signature.Attribute) (*jwsEnvelope, error) { + encoded, err := getEncodedMessage(signingScheme, isLocal, extendedSignedAttribute) if err != nil { - t.Fatal(err) + return nil, err } - _, _, err = verifyCore(encoded) + // + var env jwsEnvelope + err = json.Unmarshal(encoded, &env) if err != nil { - t.Fatal(err) + return nil, err } + return &env, nil } -func Test_envelope_Verify_X509SigningAuthority(t *testing.T) { - encoded, err := signCore(signature.SigningSchemeX509SigningAuthority) +func verifyEnvelope(env *jwsEnvelope) error { + newEncoded, err := json.Marshal(env) if err != nil { - t.Fatal(err) + return err } - _, _, err = verifyCore(encoded) + _, _, err = verifyCore(newEncoded) + return err +} + +func verifyCore(encoded []byte) (*signature.Payload, *signature.SignerInfo, error) { + env, err := ParseEnvelope(encoded) if err != nil { - t.Fatal(err) + return nil, nil, err } + return env.Verify() } -func Test_envelope_Verify_failed(t *testing.T) { - encoded, err := signCore(signature.SigningSchemeX509) - if err != nil { - t.Fatal(t) +func TestNewEnvelope(t *testing.T) { + env := NewEnvelope() + if env == nil { + t.Fatal("should get an JWS envelope") } - // manipulate envelope - encoded[len(encoded)-10] = 'C' +} + +// 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, `repeated key: "cty" exists in the both protected header and extended signed attributes.`, err.Error()) + }) - // verify manipulated envelope - _, _, err = verifyCore(encoded) + t.Run("extended attribute error value", func(t *testing.T) { + _, err := getEncodedMessage(signature.SigningSchemeX509, true, extSignedAttrErrorValue) + checkErrorEqual(t, "json: unsupported value: +Inf", err.Error()) + }) - // should get an error - if err == nil { - t.Fatalf("should verify failed.") + t.Run("unsupported sign algorithm", func(t *testing.T) { + signer := errorLocalSigner{ + algType: signature.KeyTypeRSA, + size: 222, + } + _, err := getEncodedMessage(signature.SigningSchemeX509, true, extSignedAttrRepeated) + signReq, err := getSignReq(signature.SigningSchemeX509, &signer, nil) + if err != nil { + t.Fatal(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 { + for _, size := range signaturetest.GetKeySizes(keyType) { + t.Run(fmt.Sprintf("%s %d", keyType, 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) + + _, _, 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 set", 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, "jws envelope protected header can't be decoded: 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, "jws envelope protected header can't be decoded: 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) + + return newEnv.SignerInfo() + } + + 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, `signature envelope format is malformed. error: "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, `signature envelope format is malformed. error: "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, `signature envelope format is malformed. error: "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, `signature envelope format is malformed. error: "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, `signature envelope format is malformed. error: 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, `signature envelope format is malformed. error: 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 envelope format is malformed. error: 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.SignerInfo() + checkErrorEqual(t, "signature envelope format is malformed. error: 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, `signature envelope format is malformed. error: 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 envelope format is malformed. error: cose envelope missing signature`, 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, `signature envelope format is malformed. error: 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.Payload() + checkErrorEqual(t, "illegal base64 data at input byte 476", err.Error()) + + }) +} + +func TestEmptyEnvelope(t *testing.T) { + wantErr := &signature.SignatureNotFoundError{} + 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.Payload() + 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.SignerInfo() + 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 index 1b2d9ad2..8afb9baf 100644 --- a/signature/jws/jws.go +++ b/signature/jws/jws.go @@ -38,29 +38,29 @@ func parseProtectedHeaders(encoded string) (*jwsProtectedHeader, error) { return &protected, nil } -func populateProtectedHeaders(protectedHeader *jwsProtectedHeader, signInfo *signature.SignerInfo) error { +func populateProtectedHeaders(protectedHeader *jwsProtectedHeader, signerInfo *signature.SignerInfo) error { err := validateProtectedHeaders(protectedHeader) if err != nil { return err } - if signInfo.SignatureAlgorithm, err = getSignatureAlgorithm(protectedHeader.Algorithm); err != nil { + if signerInfo.SignatureAlgorithm, err = getSignatureAlgorithm(protectedHeader.Algorithm); err != nil { return err } - signInfo.SignedAttributes.ExtendedAttributes = getExtendedAttributes(protectedHeader.ExtendedAttributes, protectedHeader.Critical) - signInfo.SignedAttributes.SigningScheme = protectedHeader.SigningScheme + signerInfo.SignedAttributes.ExtendedAttributes = getExtendedAttributes(protectedHeader.ExtendedAttributes, protectedHeader.Critical) + signerInfo.SignedAttributes.SigningScheme = protectedHeader.SigningScheme if protectedHeader.Expiry != nil { - signInfo.SignedAttributes.Expiry = *protectedHeader.Expiry + signerInfo.SignedAttributes.Expiry = *protectedHeader.Expiry } switch protectedHeader.SigningScheme { case signature.SigningSchemeX509: if protectedHeader.SigningTime != nil { - signInfo.SignedAttributes.SigningTime = *protectedHeader.SigningTime + signerInfo.SignedAttributes.SigningTime = *protectedHeader.SigningTime } case signature.SigningSchemeX509SigningAuthority: if protectedHeader.AuthenticSigningTime != nil { - signInfo.SignedAttributes.SigningTime = *protectedHeader.AuthenticSigningTime + signerInfo.SignedAttributes.SigningTime = *protectedHeader.AuthenticSigningTime } } return nil @@ -87,7 +87,7 @@ func validateProtectedHeaders(protectedHeader *jwsProtectedHeader) error { func validateCriticalHeaders(protectedHeader *jwsProtectedHeader) error { if len(protectedHeader.Critical) == 0 { - return &signature.MalformedSignatureError{Msg: "missing `crit` header"} + return &signature.MalformedSignatureError{Msg: `missing "crit" header`} } mustMarkedCrit := map[string]bool{headerKeySigningScheme: true} @@ -177,7 +177,7 @@ func generateJWS(compact string, req *signature.SignRequest, certs []*x509.Certi } // getSignerAttrs merge extended signed attributes and protected header to be signed attributes -func getSignedAttrs(req *signature.SignRequest) (map[string]interface{}, error) { +func getSignedAttrs(req *signature.SignRequest, algorithm string) (map[string]interface{}, error) { extAttrs := make(map[string]interface{}) crit := []string{headerKeySigningScheme} @@ -189,14 +189,8 @@ func getSignedAttrs(req *signature.SignRequest) (map[string]interface{}, error) } } - // extract JWT algorithm name from signer - jwtAlgorithm, err := extractJwtAlgorithm(req.Signer) - if err != nil { - return nil, err - } - jwsProtectedHeader := jwsProtectedHeader{ - Algorithm: jwtAlgorithm, + Algorithm: algorithm, ContentType: req.Payload.ContentType, SigningScheme: req.SigningScheme, } @@ -246,3 +240,14 @@ func mergeMaps(maps ...map[string]interface{}) (map[string]interface{}, error) { } 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 index 56e04cda..a0c9f3c2 100644 --- a/signature/jws/jwt.go +++ b/signature/jws/jwt.go @@ -1,6 +1,7 @@ package jws import ( + "crypto" "crypto/x509" "encoding/base64" "fmt" @@ -9,13 +10,36 @@ import ( "github.com/notaryproject/notation-core-go/signature" ) -// remoteSigningMethod wraps the remote signer to be a jwt.SigningMethod +// 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. + // + // 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 + signer signature.Signer + certs []*x509.Certificate + algorithm string } -func newRemoteSigningMethod(signer signature.Signer) jwt.SigningMethod { - return &remoteSigningMethod{signer: signer} +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. @@ -25,51 +49,66 @@ func (s *remoteSigningMethod) Verify(signingString, signature string, key interf // Sign hashes the signingString and call the remote signer to sign the digest. func (s *remoteSigningMethod) Sign(signingString string, key interface{}) (string, error) { - keySpec, err := s.signer.KeySpec() - if err != nil { - return "", err - } - - // get hasher - hasher := keySpec.SignatureAlgorithm().Hash() - if !hasher.Available() { - return "", &signature.SignatureAlgoNotSupportedError{Alg: hasher.String()} - } - - // calculate hash - h := hasher.New() - h.Write([]byte(signingString)) - hash := h.Sum(nil) - // sign by external signer - sig, err := s.signer.Sign(hash) + sig, certs, err := s.signer.Sign([]byte(signingString)) if err != nil { return "", err } + s.certs = certs return base64.RawURLEncoding.EncodeToString(sig), nil } -// Alg doesn't need to be implemented. +// Alg return the signing algorithm func (s *remoteSigningMethod) Alg() string { - alg, err := extractJwtAlgorithm(s.signer) - if err != nil { - panic(err) - } - return alg + return s.algorithm } -// verifyJWT verifies the JWT token against the specified verification key -func verifyJWT(tokenString string, cert *x509.Certificate) error { - keySpec, err := signature.ExtractKeySpec(cert) - if err != nil { - return err +// CertificateChain returns the certificate chain +// +// should be called after Sign() +func (s *remoteSigningMethod) CertificateChain() ([]*x509.Certificate, error) { + if s.certs == nil { + return nil, &signature.RemoteSigningError{Msg: "certificate chain is not set"} } - jwsAlg, err := convertAlgorithm(keySpec.SignatureAlgorithm()) + 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 + certs []*x509.Certificate +} + +func newLocalSigningMethod(signer signature.LocalSigner) (signingMethod, error) { + alg, err := extractJwtAlgorithm(signer) if err != nil { - return err + return nil, err } - signingMethod := jwt.GetSigningMethod(jwsAlg) + 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() +} + +// 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(), @@ -77,14 +116,7 @@ func verifyJWT(tokenString string, cert *x509.Certificate) error { ) if _, err := parser.ParseWithClaims(tokenString, &jwtPayload{}, func(t *jwt.Token) (interface{}, error) { - if t.Method.Alg() != signingMethod.Alg() { - return nil, &signature.MalformedSignatureError{ - Msg: fmt.Sprintf("unexpected signing method: %v: require %v", t.Method.Alg(), signingMethod.Alg())} - } - - // override default signing method with key-specific method - t.Method = signingMethod - return cert.PublicKey, nil + return publicKey, nil }); err != nil { return &signature.SignatureIntegrityError{Err: err} } @@ -92,16 +124,15 @@ func verifyJWT(tokenString string, cert *x509.Certificate) error { } func extractJwtAlgorithm(signer signature.Signer) (string, error) { + // extract algorithm from signer keySpec, err := signer.KeySpec() if err != nil { return "", err } - return convertAlgorithm(keySpec.SignatureAlgorithm()) -} + alg := keySpec.SignatureAlgorithm() -// convertAlgorithm converts the signature.Algorithm to be jwt package defined -// algorithm name. -func convertAlgorithm(alg signature.Algorithm) (string, error) { + // converts the signature.Algorithm to be jwt package defined + // algorithm name. jwsAlg, ok := signatureAlgJWSAlgMap[alg] if !ok { return "", &signature.SignatureAlgoNotSupportedError{ diff --git a/signature/jws/jwt_test.go b/signature/jws/jwt_test.go new file mode 100644 index 00000000..ea956f11 --- /dev/null +++ b/signature/jws/jwt_test.go @@ -0,0 +1,125 @@ +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) { + defer func() { + if d := recover(); d == nil { + t.Fatal("should panic") + } + }() + s := &remoteSigningMethod{} // Sign signs the payload and returns the raw signature and certificates. + s.Verify("", "", nil) +} + +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) + signingScheme, err := newRemoteSigningMethod(signer) + if err != nil { + t.Fatal(err) + } + _, err = signingScheme.CertificateChain() + checkErrorEqual(t, "remote signing error. Error: 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) + if err != nil { + t.Fatal(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/types.go b/signature/jws/types.go index 327ddb27..8eb23833 100644 --- a/signature/jws/types.go +++ b/signature/jws/types.go @@ -67,6 +67,7 @@ type jwsUnprotectedHeader struct { SigningAgent string `json:"io.cncf.notary.signingAgent,omitempty"` } +// jwsEnvelope is the final Signature envelope. type jwsEnvelope struct { // JWSPayload Base64URL-encoded. Payload string `json:"payload"` diff --git a/signature/jws/types_test.go b/signature/jws/types_test.go new file mode 100644 index 00000000..859f5fe4 --- /dev/null +++ b/signature/jws/types_test.go @@ -0,0 +1,11 @@ +package jws + +import "testing" + +func Test_jwtPayload_Valid(t *testing.T) { + var payload jwtPayload + err := payload.Valid() + if err != nil { + t.Fatal("JWS payload doesn't need to be validated") + } +} 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") + } +}