diff --git a/go.mod b/go.mod index 9bbb7b4f..59fd3b15 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/notaryproject/notation-core-go go 1.17 + +require github.com/golang-jwt/jwt/v4 v4.4.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..e2a8f55b --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= +github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= diff --git a/internal/testhelper/certificatetest.go b/internal/testhelper/certificatetest.go new file mode 100644 index 00000000..b4fb0b6b --- /dev/null +++ b/internal/testhelper/certificatetest.go @@ -0,0 +1,165 @@ +package testhelper + +import ( + _ "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "math/big" + "time" +) + +var ( + root CertTuple + leaf CertTuple + ecdsaRoot ECCertTuple + ecdsaLeaf ECCertTuple + unsupported CertTuple +) + +type CertTuple struct { + Cert *x509.Certificate + PrivateKey *rsa.PrivateKey +} + +type ECCertTuple struct { + Cert *x509.Certificate + PrivateKey *ecdsa.PrivateKey +} + +func init() { + setupCertificates() +} + +func GetRootCertificate() CertTuple { + return root +} + +func GetLeafCertificate() CertTuple { + return leaf +} + +func GetECRootCertificate() ECCertTuple { + return ecdsaRoot +} + +func GetECLeafCertificate() ECCertTuple { + return ecdsaLeaf +} + +func GetUnsupportedCertificate() CertTuple { + return unsupported +} + +func setupCertificates() { + root = getCertTuple("Notation Test Root", nil) + leaf = getCertTuple("Notation Test Leaf Cert", &root) + ecdsaRoot = getECCertTuple("Notation Test Root2", nil) + ecdsaLeaf = getECCertTuple("Notation Test Leaf Cert", &ecdsaRoot) + + k, _ := rsa.GenerateKey(rand.Reader, 1024) + unsupported = getCertTupleWithPK(k, "Notation Unsupported Root", nil) +} + +func getCertTupleWithPK(privKey *rsa.PrivateKey, cn string, issuer *CertTuple) CertTuple { + var certBytes []byte + if issuer != nil { + template := &x509.Certificate{ + SerialNumber: big.NewInt(2), + Subject: pkix.Name{ + Organization: []string{"Notary"}, + Country: []string{"US"}, + Province: []string{"WA"}, + Locality: []string{"Seattle"}, + CommonName: cn, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(0, 0, 1), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + } + certBytes, _ = x509.CreateCertificate(rand.Reader, template, issuer.Cert, &privKey.PublicKey, issuer.PrivateKey) + } else { + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Notary"}, + Country: []string{"US"}, + Province: []string{"WA"}, + Locality: []string{"Seattle"}, + CommonName: "Notation Test Root", + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(0, 1, 0), + KeyUsage: x509.KeyUsageCertSign, + BasicConstraintsValid: true, + MaxPathLen: 1, + IsCA: true, + } + certBytes, _ = x509.CreateCertificate(rand.Reader, template, template, &privKey.PublicKey, privKey) + } + + cert, _ := x509.ParseCertificate(certBytes) + return CertTuple{ + Cert: cert, + PrivateKey: privKey, + } +} + +func getEcdsaCertTupleWithPK(privKey *ecdsa.PrivateKey, cn string, issuer *ECCertTuple) ECCertTuple { + var certBytes []byte + if issuer != nil { + template := &x509.Certificate{ + SerialNumber: big.NewInt(2), + Subject: pkix.Name{ + Organization: []string{"Notary"}, + Country: []string{"US"}, + Province: []string{"WA"}, + Locality: []string{"Seattle"}, + CommonName: cn, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(0, 0, 1), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + } + certBytes, _ = x509.CreateCertificate(rand.Reader, template, issuer.Cert, &privKey.PublicKey, issuer.PrivateKey) + } else { + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Notary"}, + Country: []string{"US"}, + Province: []string{"WA"}, + Locality: []string{"Seattle"}, + CommonName: "Notation EC Test Root", + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(0, 1, 0), + KeyUsage: x509.KeyUsageCertSign, + BasicConstraintsValid: true, + MaxPathLen: 1, + IsCA: true, + } + certBytes, _ = x509.CreateCertificate(rand.Reader, template, template, &privKey.PublicKey, privKey) + } + + cert, _ := x509.ParseCertificate(certBytes) + return ECCertTuple{ + Cert: cert, + PrivateKey: privKey, + } +} + +func getCertTuple(cn string, issuer *CertTuple) CertTuple { + pk, _ := rsa.GenerateKey(rand.Reader, 3072) + return getCertTupleWithPK(pk, cn, issuer) +} + +func getECCertTuple(cn string, issuer *ECCertTuple) ECCertTuple { + k, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + return getEcdsaCertTupleWithPK(k, cn, issuer) +} diff --git a/signer/errors.go b/signer/errors.go new file mode 100644 index 00000000..66a45e1d --- /dev/null +++ b/signer/errors.go @@ -0,0 +1,106 @@ +package signer + +import "fmt" + +// InvalidSignatureError is used when the Signature associated is no longer valid. +type InvalidSignatureError struct { + err error +} + +func (e InvalidSignatureError) 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 +} + +func (e MalformedSignatureError) Error() string { + if len(e.msg) != 0 { + return e.msg + } else { + return "signature envelope format is malformed" + } +} + +// UnsupportedSignatureFormatError is used when Signature envelope is not supported. +type UnsupportedSignatureFormatError struct { + mediaType string +} + +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 signed. +type SignatureNotFoundError struct{} + +func (e SignatureNotFoundError) Error() string { + return "signature envelope not present." +} + +// UntrustedSignatureError is used when signature is not generated using trusted certificates. +type UntrustedSignatureError struct{} + +func (e UntrustedSignatureError) Error() string { + return "signature not generated using specified trusted certificates" +} + +// UnsupportedOperationError is used when an operation is not supported. +type UnsupportedOperationError struct { + operation string +} + +func (e UnsupportedOperationError) Error() string { + return fmt.Sprintf("%q operation is not supported", e.operation) +} + +// UnSupportedSigningKeyError is used when a signing key is not supported +type UnSupportedSigningKeyError struct { + keyType string +} + +func (e UnSupportedSigningKeyError) Error() string { + if len(e.keyType) != 0 { + return fmt.Sprintf("%q signing key is not supported", e.keyType) + } else { + return "signing key is not supported" + } +} + +// MalformedArgumentError is used when an argument to a function is malformed. +type MalformedArgumentError 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()) + } else { + return fmt.Sprintf("%q param is malformed", e.param) + } +} + +// MalformedSignRequestError is used when SignRequest is malformed. +type MalformedSignRequestError struct { + msg string +} + +func (e MalformedSignRequestError) Error() string { + if len(e.msg) != 0 { + return e.msg + } else { + return "SignRequest is malformed" + } +} + +// SignatureAlgoNotSupportedError is used when signing algo is not supported. +type SignatureAlgoNotSupportedError struct { + alg string +} + +func (e SignatureAlgoNotSupportedError) Error() string { + return fmt.Sprintf("%q algorithm is not supported", e.alg) +} diff --git a/signer/jws.go b/signer/jws.go new file mode 100644 index 00000000..91e18390 --- /dev/null +++ b/signer/jws.go @@ -0,0 +1,475 @@ +package signer + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" + "time" + + "github.com/golang-jwt/jwt/v4" +) + +const ( + JWS_JSON_MEDIA_TYPE signatureMediaType = "application/jose+json" +) + +const ( + expiryHeaderKey = "io.cncf.notary.expiry" + signingTimeHeaderKey = "io.cncf.notary.signingTime" + critHeaderKey = "crit" + algHeaderKey = "alg" + ctyHeaderKey = "cty" +) + +// JWSEnvelope represents the JWSEnvelope-JSON envelope +type JWSEnvelope struct { + internalEnv jwsInternalEnvelope +} + +func newJWSEnvelopeFromBytes(envelopeBytes []byte) (JWSEnvelope, error) { + jwsInternal, err := newJwsInternalEnvelopeFromBytes(envelopeBytes) + if err != nil { + return JWSEnvelope{}, err + } + + return JWSEnvelope{internalEnv: jwsInternal}, nil +} + +func newJWSEnvelope() JWSEnvelope { + return JWSEnvelope{internalEnv: newJwsInternalEnvelope()} +} + +func (jws *JWSEnvelope) validateIntegrity() error { + if reflect.DeepEqual(jws.internalEnv, newJwsInternalEnvelope()) { + return SignatureNotFoundError{} + } + + sigInfo, err := jws.getSignerInfo() + if err != nil { + return err + } + + if valError := validateSignerInfo(sigInfo); valError != nil { + return valError + } + + // verify JWT + compact := strings.Join([]string{jws.internalEnv.Protected, jws.internalEnv.Payload, jws.internalEnv.Signature}, ".") + return verifyJWT(compact, sigInfo.CertificateChain[0].PublicKey) +} + +func (jws *JWSEnvelope) signPayload(req SignRequest) ([]byte, error) { + leafPublicKey := req.CertificateChain[0].PublicKey + m := make(map[string]interface{}) + if err := json.Unmarshal(req.Payload, &m); err != nil { + return []byte{}, err + } + signingMethod, err := getSigningMethod(leafPublicKey) + if err != nil { + return nil, err + } + + signedAttrs := getSignedAttrs(req, signingMethod) + compact, _ := signJWT(m, signedAttrs, signingMethod, req.SignatureProvider) + + j, err := generateJws(compact, req) + 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() (SignerInfo, error) { + signInfo := SignerInfo{} + if reflect.DeepEqual(jws.internalEnv, newJwsInternalEnvelope()) { + return signInfo, SignatureNotFoundError{} + } + + // parse payload + if payload, err := base64.RawURLEncoding.DecodeString(jws.internalEnv.Payload); err != nil { + return signInfo, err + } else { + signInfo.Payload = payload + } + + // parse protected headers + if protected, err := base64.RawURLEncoding.DecodeString(jws.internalEnv.Protected); err != nil { + return signInfo, err + } else { + var pHeaders map[string]interface{} + if err = json.Unmarshal(protected, &pHeaders); err != nil { + return signInfo, err + } + if err := populateProtectedHeaders(pHeaders, &signInfo); err != nil { + return signInfo, err + } + } + + // parse signature + if sig, err := base64.RawURLEncoding.DecodeString(jws.internalEnv.Signature); err != nil { + return signInfo, err + } else { + signInfo.Signature = sig + } + + // parse headers + certs := make([]x509.Certificate, 0) + for _, certBytes := range jws.internalEnv.Header.CertChain { + if cert, err := x509.ParseCertificate(certBytes); err != nil { + return signInfo, err + } else { + certs = append(certs, *cert) + } + } + signInfo.CertificateChain = certs + signInfo.UnsignedAttributes.SigningAgent = jws.internalEnv.Header.SigningAgent + signInfo.TimestampSignature = jws.internalEnv.Header.TimestampSignature + + return signInfo, nil +} + +func populateProtectedHeaders(pHeaders map[string]interface{}, signInfo *SignerInfo) error { + crit, err := getAndValidateCriticalHeaders(pHeaders) + if err != nil { + return err + } + if err := populateAlg(pHeaders, &signInfo.SignatureAlgorithm); err != nil { + return err + } + if err := populateString(pHeaders, ctyHeaderKey, &signInfo.PayloadContentType); err != nil { + return err + } + if err := populateTime(pHeaders, signingTimeHeaderKey, &signInfo.SignedAttributes.SigningTime); err != nil { + return err + } + if err := populateTime(pHeaders, expiryHeaderKey, &signInfo.SignedAttributes.Expiry); err != nil { + return err + } + + // This should be last entry and populates crit and other protected headers + if err := populateExtendedAttributes(pHeaders, crit, &signInfo.SignedAttributes.ExtendedAttributes); err != nil { + return err + } + + return nil +} + +func populateString(data map[string]interface{}, s string, holder *string) error { + if val, ok := data[s]; ok { + valString, ok := val.(string) + if !ok { + return MalformedSignatureError{msg: fmt.Sprintf("failed to parse %q in protected headers", s)} + } + *holder = valString + delete(data, s) + } + return nil +} + +func populateAlg(data map[string]interface{}, holder *signatureAlgorithm) error { + var alg string + if err := populateString(data, "alg", &alg); err != nil { + return err + } + if sigAlgo, err := getAlgo(alg); err != nil { + return err + } else { + *holder = sigAlgo + delete(data, "alg") + } + + return nil +} + +func populateTime(data map[string]interface{}, s string, holder *time.Time) error { + if val, ok := data[s]; ok { + if value, err := time.Parse(time.RFC3339, val.(string)); err != nil { + return MalformedSignatureError{msg: fmt.Sprintf("failed to parse time for %s attribute, it's not in RFC3339 format", s)} + } else { + *holder = value + delete(data, s) + } + } + return nil +} + +func populateExtendedAttributes(data map[string]interface{}, critical []string, holder *[]Attribute) error { + extendedAttr := make([]Attribute, 0) + for _, val := range critical { + extendedAttr = append(extendedAttr, Attribute{ + Key: val, + Critical: true, + Value: data[val], + }) + delete(data, val) + } + delete(data, critHeaderKey) + + for key, val := range data { + extendedAttr = append(extendedAttr, Attribute{ + Key: key, + Critical: false, + Value: val, + }) + } + + *holder = extendedAttr + return nil +} + +func getAndValidateCriticalHeaders(pHeaders map[string]interface{}) ([]string, error) { + // using map for performance and that's the reason all values are bool true + headersMarkedCrit := map[string]bool{signingTimeHeaderKey: true} + if _, ok := pHeaders[expiryHeaderKey]; ok { + headersMarkedCrit[expiryHeaderKey] = true + } + + var crit []string + if val, ok := pHeaders[critHeaderKey]; ok { + critical := reflect.ValueOf(val) + for i := 0; i < critical.Len(); i++ { + var val = critical.Index(i).Interface().(string) + if _, ok := headersMarkedCrit[val]; ok { + delete(headersMarkedCrit, val) + } else { + crit = append(crit, val) + } + } + + if len(headersMarkedCrit) != 0 { + // This is not taken care by VerifySignerInfo method + return crit, MalformedSignatureError{"required headers not marked critical"} + } + return crit, nil + } else { + // This is not taken care by VerifySignerInfo method + return crit, MalformedSignatureError{"missing `crit` header"} + } +} + +func getSignedAttrs(req SignRequest, method jwt.SigningMethod) map[string]interface{} { + attrs := make(map[string]interface{}) + attrs[algHeaderKey] = method.Alg() + attrs[signingTimeHeaderKey] = req.SigningTime.Format(time.RFC3339) + var crit = []string{signingTimeHeaderKey} + if !req.Expiry.IsZero() { + attrs[expiryHeaderKey] = req.Expiry.Format(time.RFC3339) + crit = append(crit, expiryHeaderKey) + } + attrs[ctyHeaderKey] = req.PayloadContentType + + for _, elm := range req.ExtendedSignedAttrs { + attrs[elm.Key] = elm.Value + if elm.Critical { + crit = append(crit, elm.Key) + } + } + + attrs[critHeaderKey] = crit + return attrs +} + +// *********************************************************************** +// JWSEnvelope-JSON specific code +// *********************************************************************** +const ( + // JWS_PAYLOAD_CONTENT_TYPE describes the media type of the JWSEnvelope envelope. + JWS_PAYLOAD_CONTENT_TYPE = "application/vnd.cncf.notary.v2.jws.v1" +) + +// JWSEnvelope 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"` +} + +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) { + jws := jwsInternalEnvelope{} + if err := json.Unmarshal(b, &jws); err != nil { + return jws, err + } + return jws, nil +} + +func newJwsInternalEnvelope() jwsInternalEnvelope { + return jwsInternalEnvelope{} +} + +func generateJws(compact string, req SignRequest) (jwsInternalEnvelope, error) { + parts := strings.Split(compact, ".") + if len(parts) != 3 { + // this should never happen + return jwsInternalEnvelope{}, errors.New("invalid compact serialization") + } + + rawCerts := make([][]byte, len(req.CertificateChain)) + for i, cert := range req.CertificateChain { + rawCerts[i] = cert.Raw + } + + return jwsInternalEnvelope{ + Protected: parts[0], + Payload: parts[1], + Signature: parts[2], + Header: jwsUnprotectedHeader{ + CertChain: rawCerts, + SigningAgent: req.SigningAgent, + }, + }, nil +} + +func getAlgo(alg string) (signatureAlgorithm, error) { + switch alg { + case "PS256": + return RSASSA_PSS_SHA_256, nil + case "PS384": + return RSASSA_PSS_SHA_384, nil + case "PS512": + return RSASSA_PSS_SHA_512, nil + case "ES256": + return ECDSA_SHA_256, nil + case "ES384": + return ECDSA_SHA_384, nil + case "ES512": + return ECDSA_SHA_512, nil + } + + return RSASSA_PSS_SHA_512, SignatureAlgoNotSupportedError{alg: alg} +} + +// *********************************************************************** +// JWT specific code +// *********************************************************************** +// signJWT signs the given payload and headers using the given signing method and signature provider +func signJWT(payload map[string]interface{}, headers map[string]interface{}, method jwt.SigningMethod, sigPro SignatureProvider) (string, error) { + var claims jwt.MapClaims = payload + token := &jwt.Token{ + Header: headers, + Claims: claims, + } + + token.Method = SigningMethodForSign{algo: method.Alg(), sigProvider: sigPro} + compact, err := token.SignedString("DummyNotUsed") + if err != nil { + return "", err + } + return compact, nil +} + +// verifyJWT verifies the JWT token against the specified verification key +func verifyJWT(tokenString string, key crypto.PublicKey) error { + signingMethod, _ := getSigningMethod(key) + + // 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, MalformedSignatureError{msg: fmt.Sprintf("unexpected signing method: %v: require %v", t.Method.Alg(), signingMethod.Alg())} + } + + // override default signing method with key-specific method + t.Method = signingMethod + return key, nil + }); err != nil { + return err + } + return nil +} + +// SigningMethodForSign It's only used during signature generation operation. It's required by JWT library we are using +type SigningMethodForSign struct { + algo string + sigProvider SignatureProvider +} + +func (s SigningMethodForSign) Verify(_, _ string, _ interface{}) error { + return UnsupportedOperationError{} +} + +func (s SigningMethodForSign) Sign(signingString string, _ interface{}) (string, error) { + if seg, err := s.sigProvider.Sign([]byte(signingString)); err != nil { + return "", err + } else { + return base64.RawURLEncoding.EncodeToString(seg), nil + } + +} + +func (s SigningMethodForSign) Alg() string { + return s.algo +} + +// getSigningMethod picks up a recommended algorithm for given public keys. +// It's only used during signature verification operation. +func getSigningMethod(key interface{}) (jwt.SigningMethod, error) { + if k, ok := key.(interface { + Public() crypto.PublicKey + }); ok { + key = k.Public() + } + + 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{keyType: "rsa"} + } + case *ecdsa.PublicKey: + switch key.Curve.Params().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{keyType: "ecdsa"} + } + } + return nil, UnSupportedSigningKeyError{} +} diff --git a/signer/jws_test.go b/signer/jws_test.go new file mode 100644 index 00000000..229da586 --- /dev/null +++ b/signer/jws_test.go @@ -0,0 +1,154 @@ +package signer + +import ( + "crypto/x509" + "errors" + "github.com/notaryproject/notation-core-go/internal/testhelper" + "testing" +) + +func TestNewJWSEnvelope(t *testing.T) { + newJWSEnvelope() +} + +func TestNewJWSEnvelopeFromBytes(t *testing.T) { + t.Run("newJWSEnvelopeFromBytes", func(t *testing.T) { + if _, err := newJWSEnvelopeFromBytes([]byte(TEST_VALID_SIG)); err != nil { + t.Errorf("Error found") + } + }) + + t.Run("newJWSEnvelopeFromBytes Error", func(t *testing.T) { + if _, err := newJWSEnvelopeFromBytes([]byte("Malformed")); err == nil { + t.Errorf("Expected error but not found") + } + }) +} + +func TestValidateIntegrity(t *testing.T) { + t.Run("with newJWSEnvelope() returns error", func(t *testing.T) { + env := newJWSEnvelope() + err := env.validateIntegrity() + if !(err != nil && errors.As(err, new(SignatureNotFoundError))) { + t.Errorf("Expected SignatureNotFoundError but not found") + } + }) + + t.Run("with NewJWSEnvelopeFromBytes works", func(t *testing.T) { + env, _ := newJWSEnvelopeFromBytes([]byte(TEST_VALID_SIG)) + 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!\"}")) + if err := env.validateIntegrity(); err == nil { + t.Errorf("Expected error but not found") + } + }) + + 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))) { + t.Errorf("Expected MalformedSignatureError but not found") + } + }) +} + +func TestGetSignerInfo(t *testing.T) { + t.Run("with newJWSEnvelope() before sign returns error", func(t *testing.T) { + env := newJWSEnvelope() + _, err := env.getSignerInfo() + if !(err != nil && errors.As(err, new(SignatureNotFoundError))) { + t.Errorf("Expected SignatureNotFoundError but not found") + } + }) + + t.Run("with newJWSEnvelope() after sign sign works", func(t *testing.T) { + env := newJWSEnvelope() + _, err := env.getSignerInfo() + env.signPayload(getSignRequest()) + if !(err != nil && errors.As(err, new(SignatureNotFoundError))) { + t.Errorf("getSignerInfo(). Error = %s", err) + } + }) + + t.Run("with NewJWSEnvelopeFromBytes works", func(t *testing.T) { + env, _ := newJWSEnvelopeFromBytes([]byte(TEST_VALID_SIG)) + _, 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(MalformedSignatureError))) { + t.Errorf("Expected error but not found") + } + }) + + t.Run("with missing crit header returns error", func(t *testing.T) { + env, _ := newJWSEnvelopeFromBytes([]byte("{\"Payload\":\"eyJhbGciOiJIUzI1NiJ9\",\"Protected\":\"eyJpc3MiOiJEaW5vQ2hpZXNhLmdpdGh1Yi5pbyIsInN1YiI6Im9sYWYiLCJhdWQiOiJhdWRyZXkiLCJpYXQiOjE2NTQ1ODYyODIsImV4cCI6MTY1NDU4Njg4Mn0\"" + + ",\"Header\":{},\"Signature\":\"YjGj\"}")) + if _, err := env.getSignerInfo(); !(err != nil && errors.As(err, new(MalformedSignatureError))) { + t.Errorf("Expected error but not found") + } + }) + + 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))) { + t.Errorf("Expected error but not found") + } + }) + + 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))) { + t.Errorf("Expected error but not found") + } + }) +} + +func TestSignPayload(t *testing.T) { + t.Run("using rsa key with newJWSEnvelope() works", func(t *testing.T) { + env := newJWSEnvelope() + 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) { + env := newJWSEnvelope() + req := getSignRequest() + req.CertificateChain = []x509.Certificate{*testhelper.GetECLeafCertificate().Cert, *testhelper.GetECRootCertificate().Cert} + _, err := env.signPayload(req) + if err != nil { + t.Errorf("getSignerInfo(). Error = %s", err) + } + }) + + t.Run("with unsupported certificate returns error", func(t *testing.T) { + env := newJWSEnvelope() + req := getSignRequest() + req.CertificateChain = []x509.Certificate{*testhelper.GetUnsupportedCertificate().Cert} + if _, err := env.signPayload(req); !(err != nil && errors.As(err, new(UnSupportedSigningKeyError))) { + t.Errorf("Expected UnSupportedSigningKeyError but not found") + } + }) +} diff --git a/signer/signer.go b/signer/signer.go new file mode 100644 index 00000000..fa8eaa9d --- /dev/null +++ b/signer/signer.go @@ -0,0 +1,243 @@ +package signer + +import ( + "crypto/x509" + "fmt" + "time" +) + +type signatureMediaType string + +// signatureAlgorithm lists supported Signature algorithms. +type signatureAlgorithm string + +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" +) + +// SignerInfo represents a parsed signature envelope that is agnostic to signature envelope format. +type SignerInfo struct { + Payload []byte + PayloadContentType string + SignedAttributes SignedAttributes + UnsignedAttributes UnsignedAttributes + SignatureAlgorithm signatureAlgorithm + CertificateChain []x509.Certificate + Signature []byte + TimestampSignature []byte +} + +// SignedAttributes represents signed metadata in the Signature envelope +type SignedAttributes struct { + SigningTime time.Time + Expiry time.Time + ExtendedAttributes []Attribute +} + +// 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 string + CertificateChain []x509.Certificate + SignatureProvider SignatureProvider + SigningTime time.Time + Expiry time.Time + ExtendedSignedAttrs []Attribute + SigningAgent string +} + +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, error) +} + +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 validation and validates that cert-chain stored in Signature leads to given set of trusted root. +func (s *SignatureEnvelope) Verify(trustedCerts []x509.Certificate) (x509.Certificate, error) { + if len(s.rawSignatureEnvelope) == 0 { + return x509.Certificate{}, SignatureNotFoundError{} + } + + if len(trustedCerts) == 0 { + return x509.Certificate{}, MalformedArgumentError{param: "trustedCerts"} + } + + integrityError := s.internalEnvelope.validateIntegrity() + if integrityError != nil { + return x509.Certificate{}, InvalidSignatureError{err: integrityError} + } + + singerInfo, singerInfoErr := s.internalEnvelope.getSignerInfo() + if singerInfoErr != nil { + return x509.Certificate{}, integrityError + } + + certChain := singerInfo.CertificateChain + //TODO Implement validation on cert chain + fmt.Println("Yet to implement cert-chain validation") + + return verifySigner(certChain, trustedCerts) +} + +// Sign generates Signature using given SignRequest. +func (s *SignatureEnvelope) Sign(req SignRequest) ([]byte, error) { + if err := validateSignRequest(req); err != nil { + return []byte{}, err + } + + sig, err := s.internalEnvelope.signPayload(req) + if sig != nil { + s.rawSignatureEnvelope = sig + } + return sig, err +} + +// GetSignerInfo returns information about the Signature envelope +func (s SignatureEnvelope) GetSignerInfo() (SignerInfo, error) { + if len(s.rawSignatureEnvelope) == 0 { + return SignerInfo{}, SignatureNotFoundError{} + } + + signInfo, err := s.internalEnvelope.getSignerInfo() + if err != nil { + return signInfo, err + } + if err := validateSignerInfo(signInfo); err != nil { + return signInfo, err + } + return signInfo, nil +} + +// validateSignerInfo performs basic set of validations on SignerInfo struct. +func validateSignerInfo(info SignerInfo) error { + if len(info.Payload) == 0 { + return MalformedSignatureError{msg: "payload not present"} + } + + // TODO: perform PayloadContentType value validations + if len(info.PayloadContentType) == 0 { + return MalformedSignatureError{msg: "signature content type not present or is empty"} + } + + signTime := info.SignedAttributes.SigningTime + if signTime.IsZero() { + return MalformedSignatureError{msg: "singing-time not present"} + } + + expTime := info.SignedAttributes.Expiry + if !expTime.IsZero() { + if expTime.Before(signTime) || expTime.Equal(signTime) { + return MalformedSignatureError{msg: "expiry cannot be equal or before the signing time"} + } + } + + if len(info.CertificateChain) == 0 { + return MalformedSignatureError{msg: "certificate-chain not present or is empty"} + } + + if len(info.Signature) == 0 { + return MalformedSignatureError{msg: "signature not present or is empty"} + } + + return nil +} + +// validateSignRequest performs basic set of validations on SignRequest struct. +func validateSignRequest(req SignRequest) error { + if len(req.Payload) == 0 { + return MalformedSignRequestError{msg: "payload not present"} + } + + // TODO: perform PayloadContentType value validations + if len(req.PayloadContentType) == 0 { + return MalformedSignRequestError{msg: "signature content type not present or is empty"} + } + + if len(req.CertificateChain) == 0 { + return MalformedSignRequestError{msg: "certificate-chain not present or is empty"} + } + + signTime := req.SigningTime + if signTime.IsZero() { + return MalformedSignRequestError{msg: "singing-time not present"} + } + + expTime := req.Expiry + if !expTime.IsZero() { + if expTime.Before(signTime) || expTime.Equal(signTime) { + return MalformedSignRequestError{msg: "expiry cannot be equal or before the signing time"} + } + } + + if req.SignatureProvider == nil { + return MalformedSignRequestError{msg: "SignatureProvider is nil"} + } + return nil +} + +// NewSignatureEnvelopeFromBytes is used for signature verification workflow +func NewSignatureEnvelopeFromBytes(envelopeBytes []byte, envelopeMediaType signatureMediaType) (SignatureEnvelope, error) { + switch envelopeMediaType { + case JWS_JSON_MEDIA_TYPE: + internal, err := newJWSEnvelopeFromBytes(envelopeBytes) + if err != nil { + return SignatureEnvelope{}, MalformedArgumentError{"envelopeBytes", err} + } + return SignatureEnvelope{envelopeBytes, &internal}, nil + default: + return SignatureEnvelope{}, UnsupportedSignatureFormatError{mediaType: string(envelopeMediaType)} + } +} + +// NewSignatureEnvelope is used for signature generation workflow +func NewSignatureEnvelope(envelopeMediaType signatureMediaType) (SignatureEnvelope, error) { + switch envelopeMediaType { + case JWS_JSON_MEDIA_TYPE: + internal := newJWSEnvelope() + return SignatureEnvelope{internalEnvelope: &internal}, nil + default: + return SignatureEnvelope{}, UnsupportedSignatureFormatError{mediaType: string(envelopeMediaType)} + } +} + +// verifySigner verifies the certificate chain in the signature matches with one of trusted certificate +func verifySigner(sigCerts []x509.Certificate, trustedCerts []x509.Certificate) (x509.Certificate, error) { + for _, trust := range trustedCerts { + for _, sig := range sigCerts { + if trust.Equal(&sig) { + return trust, nil + } + } + } + return x509.Certificate{}, UntrustedSignatureError{} +} diff --git a/signer/signer_test.go b/signer/signer_test.go new file mode 100644 index 00000000..452e5c60 --- /dev/null +++ b/signer/signer_test.go @@ -0,0 +1,369 @@ +package signer + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" + "github.com/notaryproject/notation-core-go/internal/testhelper" + "reflect" + "strings" + "testing" + "time" +) + +const ( + TEST_PAYLOAD = "{\n \"iss\": \"DinoChiesa.github.io\",\n \"sub\": \"olaf\",\n \"aud\": \"audrey\",\n \"iat\": 1654586282,\n \"exp\": 1654586882\n}" + TEST_VALID_SIG = "{\"Payload\":\"eyJhdWQiOiJhdWRyZXkiLCJleHAiOjE2NTQ1ODY4ODIsImlhdCI6MTY1NDU4NjI4MiwiaXNzIjoiRGlub0NoaWVzYS5naXRodWIuaW8iLCJzdWIiOiJvbGFmIn0\"," + + "\"Protected\":\"eyJhbGciOiJQUzUxMiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiLCJpby5jbmNmLm5vdGFyeS5leHBpcnkiLCJzaWduZWRDcml0S2V5MSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkudjIuandzLnYxIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IjoiMjAyMi0wNi0xMlQxODoyNDo1NS0wNzowMCIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdUaW1lIjoiMjAyMi0wNi0xMVQxODoyNDo1NS0wNzowMCIsInNpZ25lZENyaXRLZXkxIjoic2lnbmVkVmFsdWUxIiwic2lnbmVkS2V5MSI6InNpZ25lZEtleTIifQ\"," + + "\"Header\":{" + + " \"x5c\":[" + + " \"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\"]," + + " \"io.cncf.notary.SigningAgent\":\"NotationUnitTest/1.0.0\"}," + + "\"Signature\":\"YjGjKbLFTpaPAyKQoubOCeVNkUH7dGCXboedKcdVRqvrX3YOSm-2134KqCQInyIh9mstOK-IgibMTrFX4nOMfrgZcbPLmnNpuJjmjwQ8cY77mMIOnYWvYtWDR3uiLt7292LbHjviNzNBqdaYClaLH0ASE5CHXQZj8zMfEPkwS_iCV81qHITNfrtpWjRiCNOidLHfCVv-agr7ztGp6AcS3FWy3KnreWQ-cJmCTfU92DpBpeVwf_2Q4h8q_NiNqPf1xWgtzvXgMkbXx8IzuxKRevIld0o9pyIwLL4CSKgRD0eV37K99sA1Cggru4hiV9Orlp-JHlYpkdabBiLmrSeOZNpiq9JySCJDQu76p5C3xwgtbbHheXRTY--C2eTjcUj4g5DIdPgN-GkeFEt6RbHhrWFSVnoI_qBpt7Oz50dnAaON5Os2QY4BSoOlEplNXrSpNpbwTEafquF6PKIJIrIXInUrSHIuk13vqGX2LqrfbFhSGwkY7JertPULrIa1vnZn_gkDpv_D9jRvORz3OgMYU7DdOUan7V74gnyhKJZVJ6LmYj27mLpVn-gEg48eezGpDI7x2b-vtNt6TxVItDsnSjwrlpzHIJrZSj_wOfyed7niOxArcQ_qHd-euUxHL79ZBm5kCdi7AwioUs8Sv0lTrqzgfYoRvcpQ96i8Ql-PwUU\"}\n" + TEST_INVALID_SIG = "{\"Payload\":\"eyJhdWQiOiJhdWRyZXkiLCJleHAiOjE2NTQ1ODY4ODIsImlhdCI6MTY1NDU4NjI4MiwiaXNzIjoiRGlub0NoaWVzYS5naXRodWIuaW8iLCJzdWIiOiJvbGFmIn0\"," + + "\"Protected\":\"eyJhbGciOiJQUzUxMiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1RpbWUiLCJpby5jbmNmLm5vdGFyeS5leHBpcnkiLCJzaWduZWRDcml0S2V5MSJdLCJjdHkiOiJhcHBsaWNhdGlvbi92bmQuY25jZi5ub3RhcnkudjIuandzLnYxIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5IjoiMjAyMi0wNi0xMlQxODoyNDo1NS0wNzowMCIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdUaW1lIjoiMjAyMi0wNi0xMVQxODoyNDo1NS0wNzowMCIsInNpZ25lZENyaXRLZXkxIjoic2lnbmVkVmFsdWUxIiwic2lnbmVkS2V5MSI6InNpZ25lZEtleTIifQ\"," + + "\"Header\":{" + + " \"x5c\":[" + + " \"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\"]," + + " \"io.cncf.notary.SigningAgent\":\"NotationUnitTest/1.0.0\"}," + + "\"Signature\":\"PAyKQoubOCeVNkUH7dGCXboedKcdVRqvrX3YOSm-2134KqCQInyIh9mstOK-IgibMTrFX4nOMfrgZcbPLmnNpuJjmjwQ8cY77mMIOnYWvYtWDR3uiLt7292LbHjviNzNBqdaYClaLH0ASE5CHXQZj8zMfEPkwS_iCV81qHITNfrtpWjRiCNOidLHfCVv-agr7ztGp6AcS3FWy3KnreWQ-cJmCTfU92DpBpeVwf_2Q4h8q_NiNqPf1xWgtzvXgMkbXx8IzuxKRevIld0o9pyIwLL4CSKgRD0eV37K99sA1Cggru4hiV9Orlp-JHlYpkdabBiLmrSeOZNpiq9JySCJDQu76p5C3xwgtbbHheXRTY--C2eTjcUj4g5DIdPgN-GkeFEt6RbHhrWFSVnoI_qBpt7Oz50dnAaON5Os2QY4BSoOlEplNXrSpNpbwTEafquF6PKIJIrIXInUrSHIuk13vqGX2LqrfbFhSGwkY7JertPULrIa1vnZn_gkDpv_D9jRvORz3OgMYU7DdOUan7V74gnyhKJZVJ6LmYj27mLpVn-gEg48eezGpDI7x2b-vtNt6TxVItDsnSjwrlpzHIJrZSj_wOfyed7niOxArcQ_qHd-euUxHL79ZBm5kCdi7AwioUs8Sv0lTrqzgfYoRvcpQ96i8Ql-PwUU\"}\n" +) + +func TestNewSignatureEnvelopeFromBytesError(t *testing.T) { + _, err := NewSignatureEnvelopeFromBytes([]byte("Malformed"), JWS_JSON_MEDIA_TYPE) + if !(err != nil && errors.As(err, new(MalformedArgumentError))) { + t.Errorf("Expected MalformedArgumentError but not found") + } +} + +func TestSign(t *testing.T) { + env, err := NewSignatureEnvelope(JWS_JSON_MEDIA_TYPE) + if err != nil { + t.Fatalf("NewSignatureEnvelope() error = %v", err) + } + + req := getSignRequest() + verifySignWithRequest(env, req, t) + + req = getSignRequest() + req.Expiry = time.Time{} + verifySignWithRequest(env, req, t) + + req = getSignRequest() + req.SigningAgent = "" + verifySignWithRequest(env, req, t) + + req = getSignRequest() + req.ExtendedSignedAttrs = nil + verifySignWithRequest(env, req, t) +} + +func TestSignErrors(t *testing.T) { + env, _ := NewSignatureEnvelope(JWS_JSON_MEDIA_TYPE) + 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 CertificateChain is absent", func(t *testing.T) { + req = getSignRequest() + req.CertificateChain = 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) + }) +} + +func TestVerify(t *testing.T) { + certs := "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" + + var trustedCertsBytes []byte + for _, element := range strings.Split(certs, ",") { + certBytes, _ := base64.StdEncoding.DecodeString(element) + trustedCertsBytes = append(trustedCertsBytes, certBytes...) + } + trustedCerts, _ := x509.ParseCertificates(trustedCertsBytes) + + env, err := NewSignatureEnvelopeFromBytes([]byte(TEST_VALID_SIG), JWS_JSON_MEDIA_TYPE) + if err != nil { + t.Fatalf("NewSignatureEnvelopeFromBytes() error = %v", err) + } + + cert, err := env.Verify([]x509.Certificate{*trustedCerts[1]}) + if err != nil { + t.Fatalf("Verify() error = %v", err) + } + + if !cert.Equal(trustedCerts[1]) { + t.Fatalf("Expected cert with subject %q but found cert with subject %q", cert.Subject, testhelper.GetRootCertificate().Cert.Subject) + } + + info, err := env.GetSignerInfo() + if err != nil { + t.Fatalf("GetSignerInfo() error = %v", err) + } + + req := getSignRequest() + req.SigningTime, err = time.Parse(time.RFC3339, "2022-06-11T18:24:55-07:00") + req.Expiry = req.SigningTime.AddDate(0, 0, 1) + req.CertificateChain = []x509.Certificate{*trustedCerts[0], *trustedCerts[1]} + verifySignerInfo(info, req, t) +} + +func TestVerifyErrors(t *testing.T) { + t.Run("when trustedCerts is absent", func(t *testing.T) { + env, _ := NewSignatureEnvelopeFromBytes([]byte("{}"), JWS_JSON_MEDIA_TYPE) + _, err := env.Verify([]x509.Certificate{}) + if !(err != nil && errors.As(err, new(MalformedArgumentError))) { + t.Errorf("Expected MalformedArgumentError but not found") + } + }) + + t.Run("when trustedCerts are not trusted", func(t *testing.T) { + env, _ := NewSignatureEnvelopeFromBytes([]byte(TEST_VALID_SIG), JWS_JSON_MEDIA_TYPE) + if _, err := env.Verify([]x509.Certificate{*testhelper.GetECRootCertificate().Cert}); err != nil { + if !errors.As(err, &UntrustedSignatureError{}) { + t.Errorf("Expected %T but found %T", &UntrustedSignatureError{}, err) + } + } else { + t.Errorf("Expected UntrustedSignatureError but not found") + } + }) + + t.Run("when invalid signature is provided", func(t *testing.T) { + env, _ := NewSignatureEnvelopeFromBytes([]byte(TEST_INVALID_SIG), JWS_JSON_MEDIA_TYPE) + if _, err := env.Verify([]x509.Certificate{*testhelper.GetECRootCertificate().Cert}); err != nil { + if !errors.As(err, &InvalidSignatureError{}) { + t.Errorf("Expected %T but found %T", InvalidSignatureError{}, err) + } + } else { + t.Errorf("Expected MalformedArgumentError but not found") + } + }) +} + +func TestSignAndVerify(t *testing.T) { + t.Run("with RSA certificate", func(t *testing.T) { + // Sign + env, err := NewSignatureEnvelope(JWS_JSON_MEDIA_TYPE) + 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 + cert, err := env.Verify([]x509.Certificate{*testhelper.GetRootCertificate().Cert}) + if err != nil { + t.Fatalf("Verify() error = %v", err) + } + + if !cert.Equal(testhelper.GetRootCertificate().Cert) { + t.Fatalf("Expected cert with subject %q but found cert with subject %q", cert.Subject, testhelper.GetRootCertificate().Cert.Subject) + } + + 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(JWS_JSON_MEDIA_TYPE) + if err != nil { + t.Fatalf("NewSignatureEnvelope() error = %v", err) + } + + req := getSignRequest() + req.CertificateChain = []x509.Certificate{*testhelper.GetECLeafCertificate().Cert, *testhelper.GetECRootCertificate().Cert} + req.SignatureProvider = MySigner{ecdsaKey: 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 + cert, err := env.Verify([]x509.Certificate{*testhelper.GetECRootCertificate().Cert}) + if err != nil { + t.Fatalf("Verify() error = %v", err) + } + + if !cert.Equal(testhelper.GetECRootCertificate().Cert) { + t.Fatalf("Expected cert with subject %q but found cert with subject %q", cert.Subject, testhelper.GetRootCertificate().Cert.Subject) + } + + info, err := env.GetSignerInfo() + if err != nil { + t.Fatalf("GetSignerInfo() error = %v", err) + } + + verifySignerInfo(info, req, t) + }) +} + +func TestGetSignerInfoErrors(t *testing.T) { + env, _ := NewSignatureEnvelope(JWS_JSON_MEDIA_TYPE) + 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 not found") + } + }) + + t.Run("when called GetSignerInfo after failed sign or verify call.", func(t *testing.T) { + req := getSignRequest() + req.Payload = []byte("Sad") + env.Sign(req) + env.Verify([]x509.Certificate{*testhelper.GetECRootCertificate().Cert}) + _, err := env.GetSignerInfo() + if !(err != nil && errors.As(err, new(SignatureNotFoundError))) { + t.Errorf("Expected SignatureNotFoundError but not found") + } + }) +} + +type MySigner struct { + rsaKey *rsa.PrivateKey + ecdsaKey *ecdsa.PrivateKey +} + +func (m MySigner) Sign(bytes []byte) ([]byte, error) { + hasher := crypto.SHA384.New() + hasher.Write(bytes) + if m.ecdsaKey == nil { + // Sign the string and return the encoded bytes + return rsa.SignPSS(rand.Reader, m.rsaKey, crypto.SHA384, hasher.Sum(nil), &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}) + } else { + // Sign the string and return r, s + r, s, _ := ecdsa.Sign(rand.Reader, m.ecdsaKey, hasher.Sum(nil)) + curveBits := m.ecdsaKey.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, nil + } +} + +func getSignRequest() SignRequest { + return SignRequest{ + Payload: []byte(TEST_PAYLOAD), + PayloadContentType: JWS_PAYLOAD_CONTENT_TYPE, + CertificateChain: []x509.Certificate{*testhelper.GetLeafCertificate().Cert, *testhelper.GetRootCertificate().Cert}, + 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: MySigner{rsaKey: testhelper.GetLeafCertificate().PrivateKey}, + } +} + +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) + } + + if !reflect.DeepEqual(request.CertificateChain, signInfo.CertificateChain) { + t.Errorf("Mistmatch between expected and actual CertificateChain") + } + + 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) + } + + 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 !reflect.DeepEqual(request.ExtendedSignedAttrs, signInfo.SignedAttributes.ExtendedAttributes) { + if !(len(request.ExtendedSignedAttrs) == 0 && len(signInfo.SignedAttributes.ExtendedAttributes) == 0) { + t.Errorf("Mistmatch between expected and actual ExtendedAttributes") + } + } + + if request.PayloadContentType != signInfo.PayloadContentType { + t.Errorf("PayloadContentType: expected value %q but found %q", request.PayloadContentType, signInfo.PayloadContentType) + } + + // 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) + } + + var signerInfoPay map[string]interface{} + if err := json.Unmarshal(signInfo.Payload, &signerInfoPay); err != nil { + t.Log(err) + } + + if !reflect.DeepEqual(signerInfoPay, signerInfoPay) { + t.Errorf("Payload: expected value %q but found %q", requestPay, signerInfoPay) + } +} + +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) + } + + info, err := env.GetSignerInfo() + if err != nil { + t.Fatalf("GetSignerInfo() error = %v", err) + } + + 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 not found") + } +}