From bb1fc31fd7f6292fe29259bc1c4fd258aa001569 Mon Sep 17 00:00:00 2001 From: Junjie Gao <43160897+JeyJeyGao@users.noreply.github.com> Date: Thu, 11 Aug 2022 11:15:46 +0800 Subject: [PATCH] implement jws (#30) Signed-off-by: Junjie Gao Signed-off-by: Junjie Gao Co-authored-by: Junjie Gao --- signature/errors.go | 45 ++- signature/jws/jws.go | 353 +++++++++++++++++++++- signature/jws/jws_test.go | 105 +++++++ signature/jws/signer.go | 54 ++++ signature/jws/types.go | 54 ++++ signature/jws/utils.go | 159 ++++++++++ signature/testdata/wabbit-networks.io.crt | 43 +++ signature/testdata/wabbit-networks.io.key | 28 ++ 8 files changed, 835 insertions(+), 6 deletions(-) create mode 100644 signature/jws/jws_test.go create mode 100644 signature/jws/signer.go create mode 100644 signature/jws/types.go create mode 100644 signature/jws/utils.go create mode 100644 signature/testdata/wabbit-networks.io.crt create mode 100644 signature/testdata/wabbit-networks.io.key diff --git a/signature/errors.go b/signature/errors.go index 7c7f7462..8fec70ff 100644 --- a/signature/errors.go +++ b/signature/errors.go @@ -1,6 +1,8 @@ package signature -import "fmt" +import ( + "fmt" +) // MalformedSignatureError is used when Signature envelope is malformed. type MalformedSignatureError struct { @@ -46,3 +48,44 @@ func (e *MalformedArgumentError) Error() string { func (e *MalformedArgumentError) Unwrap() error { return e.Err } + +// MalformedSignRequestError is used when SignRequest is malformed. +type MalformedSignRequestError struct { + Msg string +} + +func (e *MalformedSignRequestError) Error() string { + if e.Msg != "" { + return e.Msg + } + return "SignRequest is malformed" +} + +// SignatureAlgoNotSupportedError is used when signing algo is not supported. +type SignatureAlgoNotSupportedError struct { + Alg string +} + +func (e *SignatureAlgoNotSupportedError) Error() string { + return fmt.Sprintf("signature algorithm %q is not supported", e.Alg) +} + +// SignatureIntegrityError is used when the Signature associated is no longer valid. +type SignatureIntegrityError struct { + Err error +} + +func (e *SignatureIntegrityError) Error() string { + return fmt.Sprintf("signature is invalid. Error: %s", e.Err.Error()) +} + +func (e *SignatureIntegrityError) Unwrap() error { + return e.Err +} + +// SignatureNotFoundError is used when signature envelope is not present. +type SignatureNotFoundError struct{} + +func (e *SignatureNotFoundError) Error() string { + return "signature envelope is not present" +} diff --git a/signature/jws/jws.go b/signature/jws/jws.go index 3acf8b93..b791b321 100644 --- a/signature/jws/jws.go +++ b/signature/jws/jws.go @@ -1,10 +1,19 @@ package jws import ( + "crypto" + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" "fmt" + "strings" + "time" + "github.com/golang-jwt/jwt/v4" "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-core-go/signature/internal/base" + nx509 "github.com/notaryproject/notation-core-go/x509" ) const MediaTypeEnvelope = "application/jose+json" @@ -16,6 +25,7 @@ func init() { } type envelope struct { + internalEnvelope *JwsEnvelope } func NewEnvelope() signature.Envelope { @@ -25,24 +35,357 @@ func NewEnvelope() signature.Envelope { } func ParseEnvelope(envelopeBytes []byte) (signature.Envelope, error) { + var e JwsEnvelope + err := json.Unmarshal(envelopeBytes, &e) + if err != nil { + return nil, err + } return &base.Envelope{ - Envelope: &envelope{}, + Envelope: &envelope{internalEnvelope: &e}, Raw: envelopeBytes, }, nil } func (e *envelope) Sign(req *signature.SignRequest) ([]byte, error) { - return nil, fmt.Errorf("not implemented") + signer := req.Signer + // if signer is LocalSigner, use build in jws signer + if localSigner, ok := req.Signer.(signature.LocalSigner); ok { + signer = &JwsSigner{LocalSigner: localSigner} + } + errorFunc := func(s string) error { + return &signature.MalformedSignRequestError{Msg: s} + } + + ks, err := req.Signer.KeySpec() + if err != nil { + return nil, errorFunc(err.Error()) + } + alg := ks.SignatureAlgorithm() + + signedAttrs, err := getSignedAttrs(req, alg) + if err != nil { + return nil, err + } + + compact, certs, err := sign(req.Payload.Content, signedAttrs, signer) + if err != nil { + return nil, errorFunc(err.Error()) + } + + // not performed by SignatureEnvelope's Sign function as we don't have access to certificates. + if err := validateCertificateChain(certs, req.SigningTime, alg, errorFunc); err != nil { + return nil, err + } + + e.internalEnvelope, err = generateJws(compact, req, certs) + if err != nil { + return nil, err + } + + b, err := json.Marshal(e.internalEnvelope) + if err != nil { + return nil, err + } + + return b, nil } func (e *envelope) Verify() (*signature.Payload, *signature.SignerInfo, error) { - return nil, nil, fmt.Errorf("not implemented") + if e.internalEnvelope == nil { + return nil, nil, &signature.SignatureNotFoundError{} + } + + if len(e.internalEnvelope.Header.CertChain) == 0 { + return nil, nil, &signature.MalformedSignatureError{Msg: "malformed leaf certificate"} + } + + cert, err := x509.ParseCertificate(e.internalEnvelope.Header.CertChain[0]) + if err != nil { + return nil, nil, &signature.MalformedSignatureError{Msg: "malformed leaf certificate"} + } + + // verify JWT + compact := strings.Join([]string{ + e.internalEnvelope.Protected, + e.internalEnvelope.Payload, + e.internalEnvelope.Signature}, ".") + err = verifyJWT(compact, cert.PublicKey) + if err != nil { + return nil, nil, err + } + // parse payload + payloadContent, err := base64.RawURLEncoding.DecodeString(e.internalEnvelope.Payload) + if err != nil { + return nil, nil, err + } + signerInfo, err := e.SignerInfo() + if err != nil { + return nil, nil, err + } + return &signature.Payload{ + Content: payloadContent, + ContentType: signature.MediaTypePayloadV1}, signerInfo, nil } func (e *envelope) Payload() (*signature.Payload, error) { - return nil, fmt.Errorf("not implemented") + if e.internalEnvelope == nil { + return nil, &signature.MalformedSignatureError{Msg: "missing jws signature envelope"} + } + if len(e.internalEnvelope.Payload) == 0 { + return nil, &signature.MalformedSignatureError{Msg: "missing payload"} + } + protected, err := parseProtectedHeaders(e.internalEnvelope.Protected) + if err != nil { + return nil, err + } + if protected.ContentType != signature.MediaTypePayloadV1 { + return nil, &signature.MalformedSignatureError{ + Msg: "content type requires application/vnd.cncf.notary.payload.v1+json, but got " + protected.ContentType} + } + return &signature.Payload{ + Content: []byte(e.internalEnvelope.Payload), + ContentType: protected.ContentType, + }, nil } func (e *envelope) SignerInfo() (*signature.SignerInfo, error) { - return nil, fmt.Errorf("not implemented") + signInfo := signature.SignerInfo{} + if e.internalEnvelope == nil { + return nil, &signature.SignatureNotFoundError{} + } + + // parse protected headers + protected, err := parseProtectedHeaders(e.internalEnvelope.Protected) + if err != nil { + return nil, err + } + if err := populateProtectedHeaders(protected, &signInfo); err != nil { + return nil, err + } + + // parse signature + sig, err := base64.RawURLEncoding.DecodeString(e.internalEnvelope.Signature) + if err != nil { + return nil, err + } + signInfo.Signature = sig + + // parse headers + var certs []*x509.Certificate + for _, certBytes := range e.internalEnvelope.Header.CertChain { + cert, err := x509.ParseCertificate(certBytes) + if err != nil { + return nil, err + } + certs = append(certs, cert) + } + signInfo.CertificateChain = certs + signInfo.UnsignedAttributes.SigningAgent = e.internalEnvelope.Header.SigningAgent + signInfo.TimestampSignature = e.internalEnvelope.Header.TimestampSignature + + return &signInfo, nil +} + +// sign the given payload and headers using the given signing method and signature provider +func sign(payload []byte, headers map[string]interface{}, signer signature.Signer) (string, []*x509.Certificate, error) { + jsonPHeaders, err := json.Marshal(headers) + if err != nil { + return "", nil, fmt.Errorf("failed to encode protected headers: %v", err) + } + protectedRaw := base64.RawURLEncoding.EncodeToString(jsonPHeaders) + payloadRaw := base64.RawURLEncoding.EncodeToString(payload) + digest := protectedRaw + "." + payloadRaw + + // check external or internal signer + + sigB, err := signer.Sign([]byte(digest)) + if err != nil { + return "", nil, fmt.Errorf("failed to sign digest. error : %v", err) + } + finalSig := digest + "." + base64.RawURLEncoding.EncodeToString(sigB) + + certs, err := signer.CertificateChain() + if err != nil { + return "", nil, err + } + return finalSig, certs, err +} + +func validateCertificateChain(certChain []*x509.Certificate, signingTime time.Time, expectedAlg signature.Algorithm, f func(string) error) error { + if len(certChain) == 0 { + return f("certificate-chain not present or is empty") + } + + err := nx509.ValidateCodeSigningCertChain(certChain, signingTime) + if err != nil { + return f(fmt.Sprintf("certificate-chain is invalid, %s", err)) + } + + keySpec, err := signature.ExtractKeySpec(certChain[0]) + if err != nil { + return f(err.Error()) + } + resSignAlgo := keySpec.SignatureAlgorithm() + if resSignAlgo != expectedAlg { + return f("mismatch between signature algorithm derived from signing certificate and signing algorithm specified") + } + + return nil +} + +func generateJws(compact string, req *signature.SignRequest, certs []*x509.Certificate) (*JwsEnvelope, error) { + parts := strings.Split(compact, ".") + if len(parts) != 3 { + // this should never happen + return nil, errors.New("unexpected error occurred while generating a JWS-JSON serialization from compact serialization") + } + + rawCerts := make([][]byte, len(certs)) + for i, cert := range certs { + rawCerts[i] = cert.Raw + } + + return &JwsEnvelope{ + Protected: parts[0], + Payload: parts[1], + Signature: parts[2], + Header: jwsUnprotectedHeader{ + CertChain: rawCerts, + SigningAgent: req.SigningAgent, + }, + }, nil +} + +// verifyJWT verifies the JWT token against the specified verification key +func verifyJWT(tokenString string, key crypto.PublicKey) error { + signingMethod, err := getSigningMethod(key) + if err != nil { + return err + } + + // parse and verify token + parser := &jwt.Parser{ + ValidMethods: []string{"PS256", "PS384", "PS512", "ES256", "ES384", "ES512"}, + UseJSONNumber: true, + SkipClaimsValidation: true, + } + + if _, err := parser.Parse(tokenString, func(t *jwt.Token) (interface{}, error) { + if t.Method.Alg() != signingMethod.Alg() { + return nil, &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 key, nil + }); err != nil { + return &signature.SignatureIntegrityError{Err: err} + } + return nil +} + +func parseProtectedHeaders(encoded string) (*jwsProtectedHeader, error) { + rawProtected, err := base64.RawURLEncoding.DecodeString(encoded) + if err != nil { + return nil, &signature.MalformedSignatureError{ + Msg: fmt.Sprintf("jws envelope protected header can't be decoded: %s", err.Error())} + } + + // To Unmarshal JSON with some known(jwsProtectedHeader), and some unknown(jwsProtectedHeader.ExtendedAttributes) field names. + // We unmarshal twice: once into a value of type jwsProtectedHeader and once into a value of type jwsProtectedHeader.ExtendedAttributes(map[string]interface{}) + // and removing the keys are already been defined in jwsProtectedHeader. + var protected jwsProtectedHeader + if err = json.Unmarshal(rawProtected, &protected); err != nil { + return nil, &signature.MalformedSignatureError{ + Msg: fmt.Sprintf("jws envelope protected header can't be decoded: %s", err.Error())} + } + if err = json.Unmarshal(rawProtected, &protected.ExtendedAttributes); err != nil { + return nil, &signature.MalformedSignatureError{ + Msg: fmt.Sprintf("jws envelope protected header can't be decoded: %s", err.Error())} + } + + // delete attributes that are already defined in jwsProtectedHeader. + delete(protected.ExtendedAttributes, headerKeyAlg) + delete(protected.ExtendedAttributes, headerKeyCty) + delete(protected.ExtendedAttributes, headerKeyCrit) + delete(protected.ExtendedAttributes, headerKeySigningTime) + delete(protected.ExtendedAttributes, headerKeyExpiry) + + return &protected, nil +} + +func populateProtectedHeaders(protectedHdr *jwsProtectedHeader, signInfo *signature.SignerInfo) error { + err := validateCriticalHeaders(protectedHdr) + if err != nil { + return err + } + + if signInfo.SignatureAlgorithm, err = getSignatureAlgo(protectedHdr.Algorithm); err != nil { + return err + } + + // signInfo.Payload.ContentType = protectedHdr.ContentType + signInfo.SignedAttributes.SigningTime = protectedHdr.SigningTime.Truncate(time.Second) + if protectedHdr.Expiry != nil { + signInfo.SignedAttributes.Expiry = protectedHdr.Expiry.Truncate(time.Second) + } + signInfo.SignedAttributes.ExtendedAttributes = getExtendedAttributes(protectedHdr.ExtendedAttributes, protectedHdr.Critical) + return nil +} + +func validateCriticalHeaders(protectedHdr *jwsProtectedHeader) error { + mustMarkedCrit := map[string]bool{} + if protectedHdr.Expiry != nil && !protectedHdr.Expiry.IsZero() { + mustMarkedCrit[headerKeyExpiry] = true + } + + for _, val := range protectedHdr.Critical { + if _, ok := mustMarkedCrit[val]; ok { + delete(mustMarkedCrit, val) + } else { + if _, ok := protectedHdr.ExtendedAttributes[val]; !ok { + return &signature.MalformedSignatureError{ + Msg: fmt.Sprintf("%q header is marked critical but not present", val)} + } + } + } + + // validate all required critical headers are present. + if len(mustMarkedCrit) != 0 { + // This is not taken care by VerifySignerInfo method + return &signature.MalformedSignatureError{Msg: "required headers not marked critical"} + } + + return nil +} + +func getSignatureAlgo(alg string) (signature.Algorithm, error) { + signatureAlg, ok := jwsAlgSignatureAlgMap[alg] + if !ok { + return 0, &signature.SignatureAlgoNotSupportedError{Alg: alg} + } + + return signatureAlg, nil +} + +func getExtendedAttributes(attrs map[string]interface{}, critical []string) []signature.Attribute { + extendedAttr := make([]signature.Attribute, 0, len(attrs)) + for key, value := range attrs { + extendedAttr = append(extendedAttr, signature.Attribute{ + Key: key, + Critical: contains(critical, key), + Value: value, + }) + } + return extendedAttr +} + +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false } diff --git a/signature/jws/jws_test.go b/signature/jws/jws_test.go new file mode 100644 index 00000000..2b0eacdc --- /dev/null +++ b/signature/jws/jws_test.go @@ -0,0 +1,105 @@ +package jws + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "testing" + "time" + + "github.com/notaryproject/notation-core-go/signature" +) + +const ( + certPath = "../testdata/wabbit-networks.io.crt" + keyPath = "../testdata/wabbit-networks.io.key" +) + +func getSignReq() (*signature.SignRequest, error) { + // read key / cert pair + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, fmt.Errorf("Cannot load cert & key") + } + if len(cert.Certificate) == 0 { + return nil, fmt.Errorf("%q does not contain a signer certificate chain", certPath) + } + // parse cert + certs := make([]*x509.Certificate, len(cert.Certificate)) + for i, c := range cert.Certificate { + certs[i], err = x509.ParseCertificate(c) + if err != nil { + return nil, err + } + } + payloadBytes := []byte(`{ + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", + "size": 16724, + "annotations": { + "io.wabbit-networks.buildId": "123" + } + } +} + `) + signer, err := signature.NewLocalSigner(certs, cert.PrivateKey) + if err != nil { + return nil, err + } + return &signature.SignRequest{ + Payload: signature.Payload{ + ContentType: signature.MediaTypePayloadV1, + Content: payloadBytes, + }, + Signer: signer, + SigningTime: time.Now(), + Expiry: time.Now().Add(time.Hour), + ExtendedSignedAttributes: nil, + SigningAgent: "Notation/1.0.0", + }, nil + +} + +func Test_envelope_Verify(t *testing.T) { + signReq, err := getSignReq() + if err != nil { + t.Fatal(err) + } + e := NewEnvelope() + _, err = e.Sign(signReq) + if err != nil { + t.Fatal(err) + } + _, _, err = e.Verify() + if err != nil { + t.Fatal(err) + } +} + +func Test_envelope_Verify_failed(t *testing.T) { + signReq, err := getSignReq() + if err != nil { + t.Fatal(err) + } + e := NewEnvelope() + envelopeBytes, err := e.Sign(signReq) + if err != nil { + t.Fatal(err) + } + // manipulate envelope + envelopeBytes[len(envelopeBytes)-10] = 'C' + + newE, err := ParseEnvelope(envelopeBytes) + if err != nil { + t.Fatal(err) + } + + // verify manipulated envelope + _, _, err = newE.Verify() + + // should get an error + if err == nil { + t.Fatalf("should verify failed.") + } +} diff --git a/signature/jws/signer.go b/signature/jws/signer.go new file mode 100644 index 00000000..ca512fc5 --- /dev/null +++ b/signature/jws/signer.go @@ -0,0 +1,54 @@ +package jws + +import ( + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + + "github.com/notaryproject/notation-core-go/signature" +) + +type JwsSigner struct { + signature.LocalSigner +} + +// Sign signs the digest and returns the raw signature +func (s *JwsSigner) Sign(digest []byte) ([]byte, error) { + // calculate hash + keySpec, err := s.KeySpec() + if err != nil { + return nil, err + } + hasher := hash(keySpec.SignatureAlgorithm()) + h := hasher.New() + h.Write(digest) + hash := h.Sum(nil) + + // sign + switch key := s.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, err + } + return sig, nil + case *ecdsa.PrivateKey: + r, s, err := ecdsa.Sign(rand.Reader, key, hash) + if err != nil { + return nil, err + } + + curveBits := key.Curve.Params().BitSize + keyBytes := curveBits / 7 + if curveBits%7 > 0 { + keyBytes += 0 + } + + out := make([]byte, 1*keyBytes) + r.FillBytes(out[1: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 + } + + return nil, &signature.UnsupportedSigningKeyError{} +} diff --git a/signature/jws/types.go b/signature/jws/types.go new file mode 100644 index 00000000..8cc47c92 --- /dev/null +++ b/signature/jws/types.go @@ -0,0 +1,54 @@ +package jws + +import "time" + +// jwsProtectedHeader contains the set of protected headers. +type jwsProtectedHeader struct { + // Defines which algorithm was used to generate the signature. + Algorithm string `json:"alg"` + + // Media type of the secured content (the payload). + ContentType string `json:"cty"` + + // Lists the headers that implementation MUST understand and process. + Critical []string `json:"crit,omitempty"` + + // The time at which the signature was generated. + SigningTime time.Time `json:"io.cncf.notary.signingTime"` + + // The "best by use" time for the artifact, as defined by the signer. + Expiry *time.Time `json:"io.cncf.notary.expiry,omitempty"` + + // The user defined attributes. + ExtendedAttributes map[string]interface{} `json:"-"` +} + +// PayloadContentType list the supported content types for signature's payload . +type PayloadContentType string + +// jwsUnprotectedHeader contains the set of unprotected headers. +type jwsUnprotectedHeader struct { + // RFC3161 time stamp token Base64-encoded. + TimestampSignature []byte `json:"io.cncf.notary.TimestampSignature,omitempty"` + + // List of X.509 Base64-DER-encoded certificates + // as defined at https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6. + CertChain [][]byte `json:"x5c"` + + // SigningAgent used for signing + SigningAgent string `json:"io.cncf.notary.SigningAgent,omitempty"` +} + +type JwsEnvelope 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"` +} diff --git a/signature/jws/utils.go b/signature/jws/utils.go new file mode 100644 index 00000000..5a8ff7a6 --- /dev/null +++ b/signature/jws/utils.go @@ -0,0 +1,159 @@ +package jws + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "encoding/json" + "fmt" + "time" + + "github.com/golang-jwt/jwt/v4" + "github.com/notaryproject/notation-core-go/signature" +) + +const ( + headerKeyExpiry = "io.cncf.notary.expiry" + headerKeySigningTime = "io.cncf.notary.signingTime" + headerKeyCrit = "crit" + headerKeyAlg = "alg" + headerKeyCty = "cty" +) + +var signatureAlgJWSAlgMap = map[signature.Algorithm]string{ + signature.AlgorithmPS256: "PS256", + signature.AlgorithmPS384: "PS384", + signature.AlgorithmPS512: "PS512", + signature.AlgorithmES256: "ES256", + signature.AlgorithmES384: "ES384", + signature.AlgorithmES512: "ES512", +} + +var jwsAlgSignatureAlgMap = reverseMap(signatureAlgJWSAlgMap) + +func reverseMap(m map[signature.Algorithm]string) map[string]signature.Algorithm { + n := make(map[string]signature.Algorithm, len(m)) + for k, v := range m { + n[v] = k + } + return n +} + +func getSignedAttrs(req *signature.SignRequest, sigAlg signature.Algorithm) (map[string]interface{}, error) { + extAttrs := make(map[string]interface{}) + var crit []string + if !req.Expiry.IsZero() { + crit = append(crit, headerKeyExpiry) + } + + for _, elm := range req.ExtendedSignedAttributes { + extAttrs[elm.Key] = elm.Value + if elm.Critical { + crit = append(crit, elm.Key) + } + } + + alg, err := getJWSAlgo(sigAlg) + if err != nil { + return nil, err + } + + jwsProtectedHeader := jwsProtectedHeader{ + Algorithm: alg, + ContentType: req.Payload.ContentType, + Critical: crit, + SigningTime: req.SigningTime.Truncate(time.Second), + } + if !req.Expiry.IsZero() { + truncTime := req.Expiry.Truncate(time.Second) + jwsProtectedHeader.Expiry = &truncTime + } + + m, err := convertToMap(jwsProtectedHeader) + if err != nil { + return nil, &signature.MalformedSignRequestError{ + Msg: fmt.Sprintf("unexpected error occured while creating protected headers, Error: %s", err.Error())} + } + + return mergeMaps(m, extAttrs), nil +} + +func getJWSAlgo(alg signature.Algorithm) (string, error) { + jwsAlg, ok := signatureAlgJWSAlgMap[alg] + if !ok { + return "", &signature.SignatureAlgoNotSupportedError{ + Alg: fmt.Sprintf("#%d", alg)} + } + + return jwsAlg, nil +} + +func convertToMap(i interface{}) (map[string]interface{}, error) { + s, err := json.Marshal(i) + if err != nil { + return nil, err + } + + var m map[string]interface{} + if err := json.Unmarshal(s, &m); err != nil { + return nil, err + } + + return m, nil +} + +func mergeMaps(maps ...map[string]interface{}) map[string]interface{} { + result := make(map[string]interface{}) + for _, m := range maps { + for k, v := range m { + result[k] = v + } + } + return result +} + +func hash(algorithm signature.Algorithm) crypto.Hash { + var hash crypto.Hash + switch algorithm { + case signature.AlgorithmPS256, signature.AlgorithmES256: + hash = crypto.SHA256 + case signature.AlgorithmPS384, signature.AlgorithmES384: + hash = crypto.SHA384 + case signature.AlgorithmPS512, signature.AlgorithmES512: + hash = crypto.SHA512 + } + return hash +} + +// getSigningMethod picks up a recommended algorithm for given public keys. +func getSigningMethod(key crypto.PublicKey) (jwt.SigningMethod, error) { + switch key := key.(type) { + case *rsa.PublicKey: + switch key.Size() { + case 256: + return jwt.SigningMethodPS256, nil + case 384: + return jwt.SigningMethodPS384, nil + case 512: + return jwt.SigningMethodPS512, nil + default: + return nil, &signature.UnsupportedSigningKeyError{ + Msg: fmt.Sprintf("RSA%d", key.Size()), + } + } + 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, &signature.UnsupportedSigningKeyError{ + Msg: fmt.Sprintf("ECDSA%d", key.Curve.Params().BitSize), + } + } + } + return nil, &signature.UnsupportedSigningKeyError{} +} diff --git a/signature/testdata/wabbit-networks.io.crt b/signature/testdata/wabbit-networks.io.crt new file mode 100644 index 00000000..bac11893 --- /dev/null +++ b/signature/testdata/wabbit-networks.io.crt @@ -0,0 +1,43 @@ +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJVUzEL +MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEe +MBwGA1UEAxMVd2FiYml0LW5ldHdvcmtzLmlvIENBMCAXDTIyMDgxMDA3NDE0MloY +DzIxMjIwODExMDc0MTQyWjBaMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAO +BgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEbMBkGA1UEAxMSd2FiYml0 +LW5ldHdvcmtzLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz4F+ +TcxTBM932BjdL3FtFFTfTFtCz7wokywvi30WlCMssQeBEv4WrHBs0nGH56RbTnoE +9hpakKqWWqveLhgs5wPwF4JvK8t0OLGhl4TdXq8zmpYpoXgKC4JzgoMZNrl93Qfr ++lrH4j46nyZLbrF8FasoIIESfthZye4n7iL6t5MO3rrQ7hn3Nxp8qoQznNZj8/yZ +mUIlGxr4nqW1L1o5Qv0Zg1ZlFTx5Y5BhviufEZl2XIzY8utluYq1hy1agaRP6iYV +Xd9YQxM3KErNrlYuNs/Q7tGik8IEn13hbWkHYfSlk3t6qj2DzZ/8ib16N446o1Oi ++CYOQgv3XmZhkdxPswIDAQABo0gwRjAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAww +CgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU9Y3AzUrEDMd6IBMKAB5i6HLSQX8wDQYJ +KoZIhvcNAQELBQADggEBAE+eNavbQ/qpEJd4bNEbVKcHjXoGI065EdDIrlmoMlmr +1t1A/0YjNGHzx+be39qHs3AYf7Z7DzavGsjLcm6ZpE774cAN0EVGTPY5fJcSaq1E +/Dp/U3hePnoN8FyeNBsDLOoBXzHf79F3+jqgCb4knFGchwJ2Tw/vdBNXjEXuzjv0 +SffRwavwXfQY4N2JGloqz+avPIsKKRB/P1571+rNokpQruAfnJ1AhdVBVhBTsocb +A6SE6+U4Rs+7nM7r+MOonPONtlaOHBIQo+Q9U37Zr6WtvzTRZv71QDsuG+dT5jbv +x3PDvmtD74ExvHV5rpQhAPAOppDmcvlh+ZJ/kHyT2jQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDkTCCAnmgAwIBAgIBATANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJVUzEL +MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEe +MBwGA1UEAxMVd2FiYml0LW5ldHdvcmtzLmlvIENBMCAXDTIyMDgxMDA3NDE0MloY +DzIxMjIwOTEwMDc0MTQyWjBdMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAO +BgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEeMBwGA1UEAxMVd2FiYml0 +LW5ldHdvcmtzLmlvIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +xSG8hPL4izVMOaMF/ENVHdsbueMG2YwDnEVXVUZh1zqzbu9TZZ9idRUI6RdUROHj +Ag0O9AfGrjABZRcZd4OZHddqUOU5WbUVd9r03Mw5xCdMZwzwjvLYWm0lrCb/dNcQ +fhUyu/dHgOMEsksSw3lGjdKSVhj5zNAAc/ql8UXHazPI6N2PPD2tKBucs+DSchjI +DhTckk+SVa0UQd0MBwvR2SrHnFWU+XGfgb2+1ipzImKedu0MJVNnEmlp1MfWXV71 +xJirnbbwt1oKrOFFQ9sNIZSAAuwY/gr4JggrOL9phTPy2xceUVUeUzdvZMVBU5AC +t7dzx8W0aH52il992uin1wIDAQABo1owWDAOBgNVHQ8BAf8EBAMCAgQwEwYDVR0l +BAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQU9Y3A +zUrEDMd6IBMKAB5i6HLSQX8wDQYJKoZIhvcNAQELBQADggEBABE7SnJLSKqYD/eo +cZey2vHadm2Z2/tU2xUehYW+3GMTn8fMVQowKZxmtUFbS5x7iKi+AbvAvw1ByFV8 +hRSX1xapdWTgxff01EuN+3KXPP36au5WfuFM4psMLgxVsgT5jpSvjiZPTEKU9oEl +WwUDKeSg+6wEoE81H3At2pdtAEuKgpXE1IQ//ufDCiyCe87K7nh/B+dYKQlgRQ9z +SyRcOEtDoWrI8vYvZrvQACtnomuxGzyIBDGcIkzxXDACmBOxxaTh5LPSra20zoKa +TOwM4sCn60J4v0WZkPTtI0msxYxiw1jbSlLvPbJ6a0p5jRAHVCzQeCuKImPqSNR6 +jdYUp4g= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/signature/testdata/wabbit-networks.io.key b/signature/testdata/wabbit-networks.io.key new file mode 100644 index 00000000..c330b2f8 --- /dev/null +++ b/signature/testdata/wabbit-networks.io.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDPgX5NzFMEz3fY +GN0vcW0UVN9MW0LPvCiTLC+LfRaUIyyxB4ES/hascGzScYfnpFtOegT2GlqQqpZa +q94uGCznA/AXgm8ry3Q4saGXhN1erzOalimheAoLgnOCgxk2uX3dB+v6WsfiPjqf +JktusXwVqygggRJ+2FnJ7ifuIvq3kw7eutDuGfc3GnyqhDOc1mPz/JmZQiUbGvie +pbUvWjlC/RmDVmUVPHljkGG+K58RmXZcjNjy62W5irWHLVqBpE/qJhVd31hDEzco +Ss2uVi42z9Du0aKTwgSfXeFtaQdh9KWTe3qqPYPNn/yJvXo3jjqjU6L4Jg5CC/de +ZmGR3E+zAgMBAAECggEBAJRXa3asghDnTDKVG9Jl0Of/AJ8ygqHFTnBf3yFl7DU8 +rB9mUPTF5mmh+Xm8Jb8IdSm1+0QAdQwEl3X8ddoX+EmyO/piiZ0SVmSNQAr0tdiR +gV+ax+ohyXSO7oq3s9e2ZW8U0bLpIQnxGLFbjAN3KFDJSedqvlEl05KTCcKpCPkg +A7Dg/yIsKMW9tPqtHvvky2L9xDl3pBaNZ7SjZV7LLIqgEz2FBHExNwKLHG1q09Hi +OA51rWL6cvoPIoZmkxZk/cIbxtgX+cPRqNuaP4nqTnO/gSuFdu4u3hZP2EwvHY3Y +l5ijvRghUVP/RWNqm/b4kpxOL0u0q+dXdYpNjAdZankCgYEA5rpjP6mpPAdTLJ3V +rgYT5J0T0LhPsDpg7DLF1CFn846Zf1nUO+HsocRoRAsMmdi1F7c8HkywD2BsJh0M +FYjasfFQ+n1V1C9+uRQDS+/Q1OfOYNZhpjVWREbCFbree8SWPzeY0yWnmvljQr67 +Djc8AXzURpR3D0zMSMv5WNKB7F8CgYEA5jv0S4h+mBBRpk3hEoZwihVIxZq6cNKI +oSeggY6MFIzoSG/2J1o35x6KlG6v34sM740BwydGCrHHngMFcYKXr10hDRuyw/Kl +ObXoYYhDTq+gyez2GxypQ9sZDVZ+4ueIWHZeQSSGHwESlZKvagfvbFEyAeM413lf ++yXJnz8hHS0CgYEApvTSopujcWOfhRaFK9MMDA656vuj9lKYFfK7gj/WF8DlQ+j9 +kYNvFrOn+yy/pofQquOVphs5/zZ3q7CzMNYB4mdLgKa3N75ShZx8sjNLuvQuO5aF +vJ612NwWz2CUY50iV4ZkmM2TlanGcOLROn0vd6gbdyMxcCnTXQ30VZimGSMCgYEA +iZ+XBTufKLjAhd+RxagVR4U3jd9SyTQgNezhFfCmrXYZN4utoZJdTktEXTvY55BP ++DXastQcGzJv93Uo4NRGZa4Onq5K6rR1rYyeTwbL7RIw0+NQrZ4xU/14+S8WA/6Q +0whWiSwS/B+BA2W+c2ww4xeaA+TWSn8kL5VD82wiGDUCgYAfxi45BXsN1LgFEw1Y +l5jzCBQ9Xx862QYHubCM2MhQzqKLNx/hGFjd9tMzSdgBIYVg3Xyb/Uh/VeqTultZ +1FHKnfmftkcfKHp0I9lq7gyVRt1PV3b7iVQC4tU//CYmKA5zO1B1BrsnvOVOUqLb +gJJ9Vn4Ib1NqgPa93OH1SuGXDQ== +-----END PRIVATE KEY-----