diff --git a/credential/exchange/request.go b/credential/exchange/request.go index aa61d372..de7de7ec 100644 --- a/credential/exchange/request.go +++ b/credential/exchange/request.go @@ -46,16 +46,21 @@ func BuildPresentationRequest(signer any, pt PresentationRequestType, def Presen if len(opts) > 1 { return nil, fmt.Errorf("only one option supported") } - var audience string + var audience []string if len(opts) == 1 { opt := opts[0] if opt.Type != AudienceOption { return nil, fmt.Errorf("unsupported option type: %s", opt.Type) } var ok bool - audience, ok = opt.Value.(string) - if !ok { - return nil, fmt.Errorf("audience option value must be a string") + audStr, ok := opt.Value.(string) + if ok { + audience = []string{audStr} + } else { + audience, ok = opt.Value.([]string) + if !ok { + return nil, fmt.Errorf("audience option value must be a string or array of strings") + } } } @@ -75,15 +80,15 @@ func BuildPresentationRequest(signer any, pt PresentationRequestType, def Presen } // BuildJWTPresentationRequest builds a JWT representation of a presentation request -func BuildJWTPresentationRequest(signer jwx.Signer, def PresentationDefinition, target string) ([]byte, error) { +func BuildJWTPresentationRequest(signer jwx.Signer, def PresentationDefinition, audience []string) ([]byte, error) { jwtValues := map[string]any{ jwt.JwtIDKey: uuid.NewString(), jwt.IssuerKey: signer.ID, - jwt.AudienceKey: target, + jwt.AudienceKey: audience, PresentationDefinitionKey: def, } - if target != "" { - jwtValues[jwt.AudienceKey] = target + if len(audience) != 0 { + jwtValues[jwt.AudienceKey] = audience } return signer.SignWithDefaults(jwtValues) } diff --git a/credential/exchange/request_test.go b/credential/exchange/request_test.go index 1c26d633..57a95e9f 100644 --- a/credential/exchange/request_test.go +++ b/credential/exchange/request_test.go @@ -18,7 +18,7 @@ func TestBuildPresentationRequest(t *testing.T) { assert.NoError(t, err) testDef := getDummyPresentationDefinition() - requestJWTBytes, err := BuildJWTPresentationRequest(*signer, testDef, "did:test") + requestJWTBytes, err := BuildJWTPresentationRequest(*signer, testDef, []string{"did:test"}) assert.NoError(t, err) assert.NotEmpty(t, requestJWTBytes) diff --git a/credential/exchange/submission.go b/credential/exchange/submission.go index 146e20d8..c4c04895 100644 --- a/credential/exchange/submission.go +++ b/credential/exchange/submission.go @@ -165,7 +165,7 @@ func BuildPresentationSubmission(signer any, requester string, def PresentationD if err != nil { return nil, errors.Wrap(err, "unable to fulfill presentation definition with given credentials") } - return credential.SignVerifiablePresentationJWT(jwtSigner, credential.JWTVVPParameters{Audience: requester}, *vpSubmission) + return credential.SignVerifiablePresentationJWT(jwtSigner, credential.JWTVVPParameters{Audience: []string{requester}}, *vpSubmission) default: return nil, fmt.Errorf("presentation submission embed target <%s> is not implemented", et) } diff --git a/credential/exchange/submission_test.go b/credential/exchange/submission_test.go index d89d5e4e..25f74aa9 100644 --- a/credential/exchange/submission_test.go +++ b/credential/exchange/submission_test.go @@ -8,7 +8,6 @@ import ( "github.com/TBD54566975/ssi-sdk/did" "github.com/TBD54566975/ssi-sdk/util" "github.com/goccy/go-json" - "github.com/lestrrat-go/jwx/v2/jwk" "github.com/oliveagle/jsonpath" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -92,7 +91,7 @@ func TestBuildPresentationSubmission(t *testing.T) { presentationClaim := PresentationClaim{ Token: util.StringPtr(string(credJWT)), JWTFormat: JWTVC.Ptr(), - SignatureAlgorithmOrProofType: signer.GetSigningAlgorithm(), + SignatureAlgorithmOrProofType: signer.ALG, } submissionBytes, err := BuildPresentationSubmission(*signer, signer.ID, def, []PresentationClaim{presentationClaim}, JWTVPTarget) assert.NoError(tt, err) @@ -850,13 +849,10 @@ func getJWKSignerVerifier(t *testing.T) (*jwx.Signer, *jwx.Verifier) { privKey, didKey, err := did.GenerateDIDKey(crypto.Ed25519) require.NoError(t, err) - key, err := jwk.FromRaw(privKey) - require.NoError(t, err) - expanded, err := didKey.Expand() require.NoError(t, err) kid := expanded.VerificationMethod[0].ID - signer, err := jwx.NewJWXSignerFromKey(didKey.String(), kid, key) + signer, err := jwx.NewJWXSigner(didKey.String(), kid, privKey) require.NoError(t, err) verifier, err := signer.ToVerifier(didKey.String()) diff --git a/credential/exchange/verification_test.go b/credential/exchange/verification_test.go index abe3e408..05bca924 100644 --- a/credential/exchange/verification_test.go +++ b/credential/exchange/verification_test.go @@ -137,7 +137,7 @@ func TestVerifyPresentationSubmission(t *testing.T) { presentationClaim := PresentationClaim{ Token: util.StringPtr(string(credJWT)), JWTFormat: JWTVC.Ptr(), - SignatureAlgorithmOrProofType: signer.GetSigningAlgorithm(), + SignatureAlgorithmOrProofType: signer.ALG, } submissionBytes, err := BuildPresentationSubmission(*signer, verifier.ID, def, []PresentationClaim{presentationClaim}, JWTVPTarget) assert.NoError(tt, err) diff --git a/credential/jws.go b/credential/jws.go index 55384cd9..e791a801 100644 --- a/credential/jws.go +++ b/credential/jws.go @@ -21,11 +21,13 @@ func SignVerifiableCredentialJWS(signer jwx.Signer, cred VerifiableCredential) ( } headers := jws.NewHeaders() + if err = headers.Set(jws.KeyIDKey, signer.KID); err != nil { + return nil, errors.Wrap(err, "setting key ID JOSE header") + } if err = headers.Set(jws.ContentTypeKey, VCMediaType); err != nil { return nil, errors.Wrap(err, "setting content type JOSE header") } - - signed, err := jws.Sign(payload, jws.WithKey(jwa.SignatureAlgorithm(signer.GetSigningAlgorithm()), signer.Key, jws.WithProtectedHeaders(headers))) + signed, err := jws.Sign(payload, jws.WithKey(jwa.SignatureAlgorithm(signer.ALG), signer.PrivateKey, jws.WithProtectedHeaders(headers))) if err != nil { return nil, errors.Wrap(err, "signing JWT credential") } diff --git a/credential/jwt.go b/credential/jwt.go index 9d67cad7..be4a9af0 100644 --- a/credential/jwt.go +++ b/credential/jwt.go @@ -9,6 +9,7 @@ import ( "github.com/TBD54566975/ssi-sdk/did" "github.com/goccy/go-json" "github.com/google/uuid" + "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/pkg/errors" @@ -81,7 +82,13 @@ func SignVerifiableCredentialJWT(signer jwx.Signer, cred VerifiableCredential) ( return nil, errors.New("setting credential value") } - signed, err := jwt.Sign(t, jwt.WithKey(signer.SignatureAlgorithm, signer.Key)) + hdrs := jws.NewHeaders() + if signer.KID != "" { + if err := hdrs.Set(jws.KeyIDKey, signer.KID); err != nil { + return nil, errors.Wrap(err, "setting KID protected header") + } + } + signed, err := jwt.Sign(t, jwt.WithKey(jwa.SignatureAlgorithm(signer.ALG), signer.PrivateKey, jws.WithProtectedHeaders(hdrs))) if err != nil { return nil, errors.Wrap(err, "signing JWT credential") } @@ -179,8 +186,8 @@ func ParseVerifiableCredentialFromToken(token jwt.Token) (*VerifiableCredential, // JWTVVPParameters represents additional parameters needed when constructing a JWT VP as opposed to a VP type JWTVVPParameters struct { - // Audience is a required intended audience of the JWT. - Audience string `validate:"required"` + // Audience is an optional audience of the JWT. + Audience []string // Expiration is an optional expiration time of the JWT using the `exp` property. Expiration int } @@ -188,9 +195,6 @@ type JWTVVPParameters struct { // SignVerifiablePresentationJWT transforms a VP into a VP JWT and signs it // According to https://w3c.github.io/vc-jwt/#version-1.1 func SignVerifiablePresentationJWT(signer jwx.Signer, parameters JWTVVPParameters, presentation VerifiablePresentation) ([]byte, error) { - if parameters.Audience == "" { - return nil, errors.New("audience cannot be empty") - } if presentation.IsEmpty() { return nil, errors.New("presentation cannot be empty") } @@ -200,8 +204,14 @@ func SignVerifiablePresentationJWT(signer jwx.Signer, parameters JWTVVPParameter t := jwt.New() // set JWT-VP specific parameters - if err := t.Set(jwt.AudienceKey, parameters.Audience); err != nil { - return nil, errors.Wrap(err, "setting audience value") + + // NOTE: according to the JWT encoding rules (https://www.w3.org/TR/vc-data-model/#jwt-encoding) aud is a required + // property; however, aud is not required according to the JWT spec. Requiring audience limits a number of cases + // where JWT-VPs can be used, so we do not enforce this requirement. + if parameters.Audience != nil { + if err := t.Set(jwt.AudienceKey, parameters.Audience); err != nil { + return nil, errors.Wrap(err, "setting audience value") + } } iatAndNBF := time.Now().Unix() if err := t.Set(jwt.IssuedAtKey, iatAndNBF); err != nil { @@ -241,7 +251,13 @@ func SignVerifiablePresentationJWT(signer jwx.Signer, parameters JWTVVPParameter return nil, errors.Wrap(err, "setting vp value") } - signed, err := jwt.Sign(t, jwt.WithKey(signer.SignatureAlgorithm, signer.Key)) + hdrs := jws.NewHeaders() + if signer.KID != "" { + if err := hdrs.Set(jws.KeyIDKey, signer.KID); err != nil { + return nil, errors.Wrap(err, "setting KID protected header") + } + } + signed, err := jwt.Sign(t, jwt.WithKey(jwa.SignatureAlgorithm(signer.ALG), signer.PrivateKey, jws.WithProtectedHeaders(hdrs))) if err != nil { return nil, errors.Wrap(err, "signing JWT presentation") } @@ -272,13 +288,13 @@ func VerifyVerifiablePresentationJWT(ctx context.Context, verifier jwx.Verifier, // make sure the audience matches the verifier audMatch := false for _, aud := range vpToken.Audience() { - if aud == verifier.ID || aud == verifier.KeyID() { + if aud == verifier.ID || aud == verifier.KID { audMatch = true break } } if !audMatch { - return nil, nil, nil, errors.Errorf("audience mismatch: expected [%s] or [%s], got %s", verifier.ID, verifier.KeyID(), vpToken.Audience()) + return nil, nil, nil, errors.Errorf("audience mismatch: expected [%s] or [%s], got %s", verifier.ID, verifier.KID, vpToken.Audience()) } // verify signature for each credential in the vp diff --git a/credential/jwt_test.go b/credential/jwt_test.go index ef8c6aab..28b23b32 100644 --- a/credential/jwt_test.go +++ b/credential/jwt_test.go @@ -98,7 +98,7 @@ func TestVerifiablePresentationJWT(t *testing.T) { } signer := getTestVectorKey0Signer(tt) - signed, err := SignVerifiablePresentationJWT(signer, JWTVVPParameters{Audience: "bad-audience"}, testPresentation) + signed, err := SignVerifiablePresentationJWT(signer, JWTVVPParameters{Audience: []string{"bad-audience"}}, testPresentation) assert.NoError(tt, err) verifier, err := signer.ToVerifier(signer.ID) @@ -126,7 +126,7 @@ func TestVerifiablePresentationJWT(t *testing.T) { } signer := getTestVectorKey0Signer(tt) - signed, err := SignVerifiablePresentationJWT(signer, JWTVVPParameters{Audience: signer.ID}, testPresentation) + signed, err := SignVerifiablePresentationJWT(signer, JWTVVPParameters{Audience: []string{signer.ID}}, testPresentation) assert.NoError(tt, err) verifier, err := signer.ToVerifier(signer.ID) @@ -204,7 +204,7 @@ func TestVerifiablePresentationJWT(t *testing.T) { // sign the presentation from the subject to the issuer subjectSigner, err := jwx.NewJWXSigner(subjectDID.String(), subjectKID, subjectPrivKey) assert.NoError(tt, err) - signed, err := SignVerifiablePresentationJWT(*subjectSigner, JWTVVPParameters{Audience: issuerDID.String()}, testPresentation) + signed, err := SignVerifiablePresentationJWT(*subjectSigner, JWTVVPParameters{Audience: []string{issuerDID.String()}}, testPresentation) assert.NoError(tt, err) // parse the VP @@ -233,13 +233,14 @@ func TestVerifiablePresentationJWT(t *testing.T) { func getTestVectorKey0Signer(t *testing.T) jwx.Signer { // https://github.com/decentralized-identity/JWS-Test-Suite/blob/main/data/keys/key-0-ed25519.json knownJWK := jwx.PrivateKeyJWK{ + KID: "key-0", KTY: "OKP", CRV: "Ed25519", X: "JYCAGl6C7gcDeKbNqtXBfpGzH0f5elifj7L6zYNj_Is", D: "pLMxJruKPovJlxF3Lu_x9Aw3qe2wcj5WhKUAXYLBjwE", } - signer, err := jwx.NewJWXSignerFromJWK("signer-id", knownJWK.KID, knownJWK) - assert.NoError(t, err) + signer, err := jwx.NewJWXSignerFromJWK("signer-id", knownJWK) + require.NoError(t, err) return *signer } diff --git a/credential/signature.go b/credential/signature.go index 0734ea48..5fa44f81 100644 --- a/credential/signature.go +++ b/credential/signature.go @@ -82,7 +82,7 @@ func VerifyJWTCredential(cred string, resolver did.Resolver) (bool, error) { } // construct a verifier - credVerifier, err := jwx.NewJWXVerifier(issuerDID.ID, issuerKey) + credVerifier, err := jwx.NewJWXVerifier(issuerDID.ID, issuerKID, issuerKey) if err != nil { return false, errors.Wrapf(err, "error constructing verifier for credential<%s>", token.JwtID()) } diff --git a/credential/util_test.go b/credential/util_test.go index c59ed6dd..59325897 100644 --- a/credential/util_test.go +++ b/credential/util_test.go @@ -37,11 +37,13 @@ func TestCredentialsFromInterface(t *testing.T) { knownJWK := cryptosuite.JSONWebKey2020{ ID: "did:example:123#key-0", PublicKeyJWK: jwx.PublicKeyJWK{ + KID: "key-0", KTY: "OKP", CRV: "Ed25519", X: "JYCAGl6C7gcDeKbNqtXBfpGzH0f5elifj7L6zYNj_Is", }, PrivateKeyJWK: jwx.PrivateKeyJWK{ + KID: "key-0", KTY: "OKP", CRV: "Ed25519", X: "JYCAGl6C7gcDeKbNqtXBfpGzH0f5elifj7L6zYNj_Is", @@ -49,7 +51,7 @@ func TestCredentialsFromInterface(t *testing.T) { }, } - signer, err := cryptosuite.NewJSONWebKeySigner("issuer-id", knownJWK.ID, knownJWK.PrivateKeyJWK, cryptosuite.AssertionMethod) + signer, err := cryptosuite.NewJSONWebKeySigner("issuer-id", knownJWK.PrivateKeyJWK, cryptosuite.AssertionMethod) assert.NoError(t, err) suite := cryptosuite.GetJSONWebSignature2020Suite() @@ -71,13 +73,14 @@ func TestCredentialsFromInterface(t *testing.T) { t.Run("JWT Cred", func(tt *testing.T) { knownJWK := jwx.PrivateKeyJWK{ + KID: "key-0", KTY: "OKP", CRV: "Ed25519", X: "JYCAGl6C7gcDeKbNqtXBfpGzH0f5elifj7L6zYNj_Is", D: "pLMxJruKPovJlxF3Lu_x9Aw3qe2wcj5WhKUAXYLBjwE", } - signer, err := jwx.NewJWXSignerFromJWK("signer-id", knownJWK.KID, knownJWK) + signer, err := jwx.NewJWXSignerFromJWK("signer-id", knownJWK) assert.NoError(tt, err) testCred := getTestCredential() diff --git a/crypto/jwx/jwk.go b/crypto/jwx/jwk.go index 40cf2756..72ec9c84 100644 --- a/crypto/jwx/jwk.go +++ b/crypto/jwx/jwk.go @@ -32,7 +32,7 @@ type PrivateKeyJWK struct { E string `json:"e,omitempty"` Use string `json:"use,omitempty"` KeyOps string `json:"key_ops,omitempty"` - Alg string `json:"alg,omitempty"` + ALG string `json:"alg,omitempty"` KID string `json:"kid,omitempty"` D string `json:"d,omitempty"` DP string `json:"dp,omitempty"` @@ -42,8 +42,21 @@ type PrivateKeyJWK struct { QI string `json:"qi,omitempty"` } +func (k *PrivateKeyJWK) IsEmpty() bool { + if k == nil { + return true + } + return reflect.DeepEqual(k, &PrivateKeyJWK{}) +} + // ToPublicKeyJWK converts a PrivateKeyJWK to a PublicKeyJWK -func (k PrivateKeyJWK) ToPublicKeyJWK() PublicKeyJWK { +func (k *PrivateKeyJWK) ToPublicKeyJWK() PublicKeyJWK { + if k.ALG == "" { + alg, err := AlgFromKeyAndCurve(k.ALG, k.CRV) + if err == nil { + k.ALG = alg + } + } return PublicKeyJWK{ KTY: k.KTY, CRV: k.CRV, @@ -53,33 +66,61 @@ func (k PrivateKeyJWK) ToPublicKeyJWK() PublicKeyJWK { E: k.E, Use: k.Use, KeyOps: k.KeyOps, - Alg: k.Alg, + ALG: k.ALG, KID: k.KID, } } -func (k PrivateKeyJWK) ToPrivateKey() (gocrypto.PrivateKey, error) { - // handle Dilithium separately since it's not supported by our jwx library - if k.KTY == DilithiumKTY { - return k.toDilithiumPrivateKey() +// ToPrivateKey converts a PrivateKeyJWK to a PrivateKeyJWK +func (k *PrivateKeyJWK) ToPrivateKey() (gocrypto.PrivateKey, error) { + if k.ALG == "" { + alg, err := AlgFromKeyAndCurve(k.KTY, k.CRV) + if err != nil { + return nil, errors.Wrap(err, "getting alg from key and curve") + } + k.ALG = alg + } + if IsSupportedJWXSigningVerificationAlgorithm(k.ALG) { + return k.toSupportedPrivateKey() + } + if IsExperimentalJWXSigningVerificationAlgorithm(k.ALG) { + return k.toExperimentalPrivateKey() } - gotJWK, err := JWKFromPrivateKeyJWK(k) + return nil, fmt.Errorf("unsupported key conversion %+v", k) +} + +func (k *PrivateKeyJWK) toSupportedPrivateKey() (gocrypto.PrivateKey, error) { + keyBytes, err := json.Marshal(k) + if err != nil { + return nil, err + } + gotJWK, err := jwk.ParseKey(keyBytes) if err != nil { return nil, errors.Wrap(err, "creating JWK from private key") } - var goKey gocrypto.PrivateKey - if err = gotJWK.Raw(&goKey); err != nil { + var key gocrypto.PrivateKey + if err = gotJWK.Raw(&key); err != nil { return nil, errors.Wrap(err, "converting JWK to go key") } + // dereference the ptr - if reflect.ValueOf(goKey).Kind() == reflect.Ptr { - goKey = reflect.ValueOf(goKey).Elem().Interface().(gocrypto.PrivateKey) + if reflect.ValueOf(key).Kind() == reflect.Ptr { + key = reflect.ValueOf(key).Elem().Interface().(gocrypto.PrivateKey) + } + return key, nil +} + +func (k *PrivateKeyJWK) toExperimentalPrivateKey() (gocrypto.PrivateKey, error) { + switch k.KTY { + case DilithiumKTY: + return k.toDilithiumPrivateKey() + default: + return nil, fmt.Errorf("unsupported key type %s", k.KTY) } - return goKey, nil } // complies with https://www.ietf.org/id/draft-ietf-cose-dilithium-00.html#name-crydi-key-representations -func (k PrivateKeyJWK) toDilithiumPrivateKey() (gocrypto.PrivateKey, error) { +func (k *PrivateKeyJWK) toDilithiumPrivateKey() (gocrypto.PrivateKey, error) { if k.D == "" { return nil, fmt.Errorf("missing private key D") } @@ -90,7 +131,7 @@ func (k PrivateKeyJWK) toDilithiumPrivateKey() (gocrypto.PrivateKey, error) { if err != nil { return nil, err } - switch k.Alg { + switch k.ALG { case DilithiumMode2Alg.String(): return dilithium.Mode2.PrivateKeyFromBytes(decodedPrivKey), nil case DilithiumMode3Alg.String(): @@ -98,7 +139,7 @@ func (k PrivateKeyJWK) toDilithiumPrivateKey() (gocrypto.PrivateKey, error) { case DilithiumMode5Alg.String(): return dilithium.Mode5.PrivateKeyFromBytes(decodedPrivKey), nil default: - return nil, fmt.Errorf("unsupported algorithm %s", k.Alg) + return nil, fmt.Errorf("unsupported algorithm %s", k.ALG) } } @@ -112,31 +153,66 @@ type PublicKeyJWK struct { E string `json:"e,omitempty"` Use string `json:"use,omitempty"` KeyOps string `json:"key_ops,omitempty"` - Alg string `json:"alg,omitempty"` + ALG string `json:"alg,omitempty"` KID string `json:"kid,omitempty"` } -func (k PublicKeyJWK) ToPublicKey() (gocrypto.PublicKey, error) { - // handle Dilithium separately since it's not supported by our jwx library - if k.KTY == DilithiumKTY { - return k.toDilithiumPublicKey() +func (k *PublicKeyJWK) IsEmpty() bool { + if k == nil { + return true } - gotJWK, err := JWKFromPublicKeyJWK(k) + return reflect.DeepEqual(k, &PublicKeyJWK{}) +} + +// ToPublicKey converts a PublicKeyJWK to a PublicKey +func (k *PublicKeyJWK) ToPublicKey() (gocrypto.PublicKey, error) { + if k.ALG == "" { + alg, err := AlgFromKeyAndCurve(k.KTY, k.CRV) + if err != nil { + return nil, errors.Wrap(err, "getting alg from key and curve") + } + k.ALG = alg + } + if IsSupportedJWXSigningVerificationAlgorithm(k.ALG) { + return k.toSupportedPublicKey() + } + if IsExperimentalJWXSigningVerificationAlgorithm(k.ALG) { + return k.toExperimentalPublicKey() + } + return nil, fmt.Errorf("unsupported key conversion %+v", k) +} + +func (k *PublicKeyJWK) toSupportedPublicKey() (gocrypto.PublicKey, error) { + keyBytes, err := json.Marshal(k) + if err != nil { + return nil, err + } + gotJWK, err := jwk.ParseKey(keyBytes) if err != nil { return nil, errors.Wrap(err, "creating JWK from public key") } - var goKey gocrypto.PublicKey - if err = gotJWK.Raw(&goKey); err != nil { + var key gocrypto.PublicKey + if err = gotJWK.Raw(&key); err != nil { return nil, errors.Wrap(err, "converting JWK to go key") } + // dereference the ptr - if reflect.ValueOf(goKey).Kind() == reflect.Ptr { - goKey = reflect.ValueOf(goKey).Elem().Interface().(gocrypto.PublicKey) + if reflect.ValueOf(key).Kind() == reflect.Ptr { + key = reflect.ValueOf(key).Elem().Interface().(gocrypto.PublicKey) } - return goKey, nil + return key, nil } -func (k PublicKeyJWK) toDilithiumPublicKey() (gocrypto.PublicKey, error) { +func (k *PublicKeyJWK) toExperimentalPublicKey() (gocrypto.PublicKey, error) { + switch k.KTY { + case DilithiumKTY: + return k.toDilithiumPublicKey() + default: + return nil, fmt.Errorf("unsupported key type %s", k.KTY) + } +} + +func (k *PublicKeyJWK) toDilithiumPublicKey() (gocrypto.PublicKey, error) { if k.X == "" { return nil, fmt.Errorf("missing public key X") } @@ -144,7 +220,7 @@ func (k PublicKeyJWK) toDilithiumPublicKey() (gocrypto.PublicKey, error) { if err != nil { return nil, errors.Wrap(err, "decoding public key") } - switch k.Alg { + switch k.ALG { case DilithiumMode2Alg.String(): return dilithium.Mode2.PublicKeyFromBytes(decodedPubKey), nil case DilithiumMode3Alg.String(): @@ -152,179 +228,104 @@ func (k PublicKeyJWK) toDilithiumPublicKey() (gocrypto.PublicKey, error) { case DilithiumMode5Alg.String(): return dilithium.Mode5.PublicKeyFromBytes(decodedPubKey), nil default: - return nil, fmt.Errorf("unsupported algorithm %s", k.Alg) - } -} - -// JWKToPrivateKeyJWK converts a JWK to a PrivateKeyJWK -func JWKToPrivateKeyJWK(key jwk.Key) (*PrivateKeyJWK, error) { - keyBytes, err := json.Marshal(key) - if err != nil { - return nil, err - } - var privateKeyJWK PrivateKeyJWK - if err = json.Unmarshal(keyBytes, &privateKeyJWK); err != nil { - return nil, err - } - return &privateKeyJWK, nil -} - -// JWKToPublicKeyJWK converts a JWK to a PublicKeyJWK -func JWKToPublicKeyJWK(key jwk.Key) (*PublicKeyJWK, error) { - keyBytes, err := json.Marshal(key) - if err != nil { - return nil, err - } - var pubKeyJWK PublicKeyJWK - if err = json.Unmarshal(keyBytes, &pubKeyJWK); err != nil { - return nil, err - } - return &pubKeyJWK, nil -} - -// JWKFromPublicKeyJWK converts a PublicKeyJWK to a JWK -func JWKFromPublicKeyJWK(key PublicKeyJWK) (jwk.Key, error) { - keyBytes, err := json.Marshal(key) - if err != nil { - return nil, err - } - return jwk.ParseKey(keyBytes) -} - -// JWKFromPrivateKeyJWK converts a PrivateKeyJWK to a JWK -func JWKFromPrivateKeyJWK(key PrivateKeyJWK) (jwk.Key, error) { - keyBytes, err := json.Marshal(key) - if err != nil { - return nil, err - } - return jwk.ParseKey(keyBytes) -} - -// PublicKeyToJWK converts a public key to a JWK -func PublicKeyToJWK(key gocrypto.PublicKey) (jwk.Key, error) { - // dereference the ptr - if reflect.ValueOf(key).Kind() == reflect.Ptr { - key = reflect.ValueOf(key).Elem().Interface().(gocrypto.PublicKey) - } - switch k := key.(type) { - case rsa.PublicKey: - return jwkKeyFromRSAPublicKey(k) - case ed25519.PublicKey: - return jwkKeyFromEd25519PublicKey(k) - case x25519.PublicKey: - return jwkKeyFromX25519PublicKey(k) - case secp256k1.PublicKey: - return jwkKeyFromSECP256k1PublicKey(k) - case ecdsa.PublicKey: - return jwkKeyFromECDSAPublicKey(k) - default: - return nil, fmt.Errorf("unsupported public key type: %T", k) + return nil, fmt.Errorf("unsupported algorithm %s", k.ALG) } } // PublicKeyToPublicKeyJWK converts a public key to a PublicKeyJWK -func PublicKeyToPublicKeyJWK(key gocrypto.PublicKey) (*PublicKeyJWK, error) { +func PublicKeyToPublicKeyJWK(kid string, key gocrypto.PublicKey) (*PublicKeyJWK, error) { // dereference the ptr, which could be a nested ptr for reflect.ValueOf(key).Kind() == reflect.Ptr { key = reflect.ValueOf(key).Elem().Interface().(gocrypto.PublicKey) } + var pubKeyJWK *PublicKeyJWK + var err error switch k := key.(type) { case rsa.PublicKey: - return jwkFromRSAPublicKey(k) + pubKeyJWK, err = jwkFromRSAPublicKey(k) case ed25519.PublicKey: - return jwkFromEd25519PublicKey(k) + pubKeyJWK, err = jwkFromEd25519PublicKey(k) case x25519.PublicKey: - return jwkFromX25519PublicKey(k) + pubKeyJWK, err = jwkFromX25519PublicKey(k) case secp256k1.PublicKey: - return jwkFromSECP256k1PublicKey(k) + pubKeyJWK, err = jwkFromSECP256k1PublicKey(k) case ecdsa.PublicKey: - return jwkFromECDSAPublicKey(k) + pubKeyJWK, err = jwkFromECDSAPublicKey(k) case mode2.PublicKey: pubKey := dilithium.Mode2.PublicKeyFromBytes(k.Bytes()) - return jwkFromDilithiumPublicKey(crypto.Dilithium2, pubKey) + pubKeyJWK, err = jwkFromDilithiumPublicKey(crypto.Dilithium2, pubKey) case mode3.PublicKey: pubKey := dilithium.Mode3.PublicKeyFromBytes(k.Bytes()) - return jwkFromDilithiumPublicKey(crypto.Dilithium3, pubKey) + pubKeyJWK, err = jwkFromDilithiumPublicKey(crypto.Dilithium3, pubKey) case mode5.PublicKey: pubKey := dilithium.Mode5.PublicKeyFromBytes(k.Bytes()) - return jwkFromDilithiumPublicKey(crypto.Dilithium5, pubKey) + pubKeyJWK, err = jwkFromDilithiumPublicKey(crypto.Dilithium5, pubKey) default: return nil, fmt.Errorf("unsupported public key type: %T", k) } -} - -// PrivateKeyToJWK converts a private key to a JWK -func PrivateKeyToJWK(key gocrypto.PrivateKey) (jwk.Key, error) { - // dereference the ptr - if reflect.ValueOf(key).Kind() == reflect.Ptr { - key = reflect.ValueOf(key).Elem().Interface().(gocrypto.PrivateKey) + if err != nil { + return nil, err } - switch k := key.(type) { - case rsa.PrivateKey: - return jwkKeyFromRSAPrivateKey(k) - case ed25519.PrivateKey: - return jwkKeyFromEd25519PrivateKey(k) - case x25519.PrivateKey: - return jwkKeyFromX25519PrivateKey(k) - case secp256k1.PrivateKey: - return jwkKeyFromSECP256k1PrivateKey(k) - case ecdsa.PrivateKey: - return jwkKeyFromECDSAPrivateKey(k) - default: - return nil, fmt.Errorf("unsupported private key type: %T", k) + pubKeyJWK.KID = kid + if pubKeyJWK.ALG == "" { + alg, err := AlgFromKeyAndCurve(pubKeyJWK.KTY, pubKeyJWK.CRV) + if err != nil { + return nil, errors.Wrap(err, "getting alg from key and curve") + } + pubKeyJWK.ALG = alg } + return pubKeyJWK, err } // PrivateKeyToPrivateKeyJWK converts a private key to a PrivateKeyJWK -func PrivateKeyToPrivateKeyJWK(key gocrypto.PrivateKey) (*PublicKeyJWK, *PrivateKeyJWK, error) { +func PrivateKeyToPrivateKeyJWK(keyID string, key gocrypto.PrivateKey) (*PublicKeyJWK, *PrivateKeyJWK, error) { // dereference the ptr, which could be nested for reflect.ValueOf(key).Kind() == reflect.Ptr { key = reflect.ValueOf(key).Elem().Interface().(gocrypto.PrivateKey) } + var pubKeyJWK *PublicKeyJWK + var privKeyJWK *PrivateKeyJWK + var err error switch k := key.(type) { case rsa.PrivateKey: - return jwkFromRSAPrivateKey(k) + pubKeyJWK, privKeyJWK, err = jwkFromRSAPrivateKey(k) + if err != nil { + return nil, nil, err + } case ed25519.PrivateKey: - return jwkFromEd25519PrivateKey(k) + pubKeyJWK, privKeyJWK, err = jwkFromEd25519PrivateKey(k) case x25519.PrivateKey: - return jwkFromX25519PrivateKey(k) + pubKeyJWK, privKeyJWK, err = jwkFromX25519PrivateKey(k) case secp256k1.PrivateKey: - return jwkFromSECP256k1PrivateKey(k) + pubKeyJWK, privKeyJWK, err = jwkFromSECP256k1PrivateKey(k) case ecdsa.PrivateKey: - return jwkFromECDSAPrivateKey(k) + pubKeyJWK, privKeyJWK, err = jwkFromECDSAPrivateKey(k) case mode2.PrivateKey: privKey := dilithium.Mode2.PrivateKeyFromBytes(k.Bytes()) - return jwkFromDilithiumPrivateKey(crypto.Dilithium2, privKey) + pubKeyJWK, privKeyJWK, err = jwkFromDilithiumPrivateKey(crypto.Dilithium2, privKey) case mode3.PrivateKey: privKey := dilithium.Mode3.PrivateKeyFromBytes(k.Bytes()) - return jwkFromDilithiumPrivateKey(crypto.Dilithium3, privKey) + pubKeyJWK, privKeyJWK, err = jwkFromDilithiumPrivateKey(crypto.Dilithium3, privKey) case mode5.PrivateKey: privKey := dilithium.Mode5.PrivateKeyFromBytes(k.Bytes()) - return jwkFromDilithiumPrivateKey(crypto.Dilithium5, privKey) + pubKeyJWK, privKeyJWK, err = jwkFromDilithiumPrivateKey(crypto.Dilithium5, privKey) default: return nil, nil, fmt.Errorf("unsupported private key type: %T", k) } -} - -func GetCRVFromJWK(key jwk.Key) (string, error) { - maybeCrv, hasCrv := key.Get("crv") - if hasCrv { - crv, crvStr := maybeCrv.(jwa.EllipticCurveAlgorithm) - if !crvStr { - return "", fmt.Errorf("could not get crv value: %+v", maybeCrv) - } - return crv.String(), nil - } - return "", nil -} - -// jwkKeyFromRSAPrivateKey converts a RSA private key to a JWK -func jwkKeyFromRSAPrivateKey(key rsa.PrivateKey) (jwk.Key, error) { - rsaJWK, err := jwk.FromRaw(key) if err != nil { - return nil, errors.Wrap(err, "generating rsa jwk") + return nil, nil, err } - return rsaJWK, nil + pubKeyJWK.KID = keyID + privKeyJWK.KID = keyID + if privKeyJWK.ALG == "" { + alg, err := AlgFromKeyAndCurve(privKeyJWK.KTY, privKeyJWK.CRV) + if err != nil { + return nil, nil, errors.Wrap(err, "getting alg from key and curve") + } + pubKeyJWK.ALG = alg + privKeyJWK.ALG = alg + } + return pubKeyJWK, privKeyJWK, nil } // jwkFromRSAPrivateKey converts a RSA private key to a JWK @@ -353,19 +354,6 @@ func jwkFromRSAPrivateKey(key rsa.PrivateKey) (*PublicKeyJWK, *PrivateKeyJWK, er return &publicKeyJWK, &privateKeyJWK, nil } -// jwkKeyFromRSAPublicKey converts an RSA public key to a JWK -func jwkKeyFromRSAPublicKey(key rsa.PublicKey) (jwk.Key, error) { - rsaJWKGeneric, err := jwk.FromRaw(key) - if err != nil { - return nil, errors.Wrap(err, "failed to generate rsa jwk") - } - rsaJWK, ok := rsaJWKGeneric.(jwk.RSAPublicKey) - if !ok { - return nil, errors.New("failed casting to rsa jwk") - } - return rsaJWK, nil -} - // jwkFromRSAPublicKey converts an RSA public key to a JWK func jwkFromRSAPublicKey(key rsa.PublicKey) (*PublicKeyJWK, error) { rsaJWKGeneric, err := jwk.FromRaw(key) @@ -388,19 +376,6 @@ func jwkFromRSAPublicKey(key rsa.PublicKey) (*PublicKeyJWK, error) { return &publicKeyJWK, nil } -// jwkKeyFromEd25519PrivateKey converts an Ed25519 private key to a JWK -func jwkKeyFromEd25519PrivateKey(key ed25519.PrivateKey) (jwk.Key, error) { - ed25519JWKGeneric, err := jwk.FromRaw(key) - if err != nil { - return nil, errors.Wrap(err, "generating ed25519 jwk") - } - ed25519JWK, ok := ed25519JWKGeneric.(jwk.OKPPrivateKey) - if !ok { - return nil, errors.New("failed casting ed25519 jwk") - } - return ed25519JWK, nil -} - // jwkFromEd25519PrivateKey converts an Ed25519 private key to a JWK func jwkFromEd25519PrivateKey(key ed25519.PrivateKey) (*PublicKeyJWK, *PrivateKeyJWK, error) { ed25519JWKGeneric, err := jwk.FromRaw(key) @@ -427,19 +402,6 @@ func jwkFromEd25519PrivateKey(key ed25519.PrivateKey) (*PublicKeyJWK, *PrivateKe return &publicKeyJWK, &privateKeyJWK, nil } -// jwkKeyFromEd25519PublicKey converts a Ed25519 public key to a JWK -func jwkKeyFromEd25519PublicKey(key ed25519.PublicKey) (jwk.Key, error) { - ed25519JWKGeneric, err := jwk.FromRaw(key) - if err != nil { - return nil, errors.Wrap(err, "generating ed25519 jwk") - } - ed25519JWK, ok := ed25519JWKGeneric.(jwk.OKPPublicKey) - if !ok { - return nil, errors.New("failed casting to ed25519 jwk") - } - return ed25519JWK, nil -} - // jwkFromEd25519PublicKey converts a Ed25519 public key to a JWK func jwkFromEd25519PublicKey(key ed25519.PublicKey) (*PublicKeyJWK, error) { ed25519JWKGeneric, err := jwk.FromRaw(key) @@ -462,40 +424,16 @@ func jwkFromEd25519PublicKey(key ed25519.PublicKey) (*PublicKeyJWK, error) { return &publicKeyJWK, nil } -// jwkFromX25519PrivateKey converts a X25519 private key to a JWK -func jwkKeyFromX25519PrivateKey(key x25519.PrivateKey) (jwk.Key, error) { - return jwkKeyFromEd25519PrivateKey(ed25519.PrivateKey(key)) -} - // jwkFromX25519PrivateKey converts a X25519 private key to a JWK func jwkFromX25519PrivateKey(key x25519.PrivateKey) (*PublicKeyJWK, *PrivateKeyJWK, error) { return jwkFromEd25519PrivateKey(ed25519.PrivateKey(key)) } -// jwkKeyFromX25519PublicKey converts a X25519 public key to a JWK -func jwkKeyFromX25519PublicKey(key x25519.PublicKey) (jwk.Key, error) { - return jwkKeyFromEd25519PublicKey(ed25519.PublicKey(key)) -} - // jwkFromX25519PublicKey converts a X25519 public key to a JWK func jwkFromX25519PublicKey(key x25519.PublicKey) (*PublicKeyJWK, error) { return jwkFromEd25519PublicKey(ed25519.PublicKey(key)) } -// jwkKeyFromSECP256k1PrivateKey converts a SECP256k1 private key to a JWK -func jwkKeyFromSECP256k1PrivateKey(key secp256k1.PrivateKey) (jwk.Key, error) { - ecdsaPrivKey := key.ToECDSA() - secp256k1JWKGeneric, err := jwk.FromRaw(ecdsaPrivKey) - if err != nil { - return nil, errors.Wrap(err, "generating secp256k1 jwk") - } - secp256k1JWK, ok := secp256k1JWKGeneric.(jwk.ECDSAPrivateKey) - if !ok { - return nil, errors.New("failed casting to secp256k1 jwk") - } - return secp256k1JWK, nil -} - // jwkFromSECP256k1PrivateKey converts a SECP256k1 private key to a JWK func jwkFromSECP256k1PrivateKey(key secp256k1.PrivateKey) (*PublicKeyJWK, *PrivateKeyJWK, error) { ecdsaPrivKey := key.ToECDSA() @@ -523,20 +461,6 @@ func jwkFromSECP256k1PrivateKey(key secp256k1.PrivateKey) (*PublicKeyJWK, *Priva return &publicKeyJWK, &privateKeyJWK, nil } -// jwkKeyFromSECP256k1PublicKey converts a SECP256k1 public key to a JWK -func jwkKeyFromSECP256k1PublicKey(key secp256k1.PublicKey) (jwk.Key, error) { - ecdsaPubKey := key.ToECDSA() - secp256k1JWKGeneric, err := jwk.FromRaw(ecdsaPubKey) - if err != nil { - return nil, errors.Wrap(err, "generating secp256k1 jwk") - } - secp256k1JWK, ok := secp256k1JWKGeneric.(jwk.ECDSAPublicKey) - if !ok { - return nil, errors.New("failed casting to secp256k1 jwk") - } - return secp256k1JWK, nil -} - // jwkFromSECP256k1PublicKey converts a SECP256k1 public key to a JWK func jwkFromSECP256k1PublicKey(key secp256k1.PublicKey) (*PublicKeyJWK, error) { ecdsaPubKey := key.ToECDSA() @@ -560,19 +484,6 @@ func jwkFromSECP256k1PublicKey(key secp256k1.PublicKey) (*PublicKeyJWK, error) { return &publicKeyJWK, nil } -// jwkKeyFromECDSAPrivateKey converts a ECDSA private key to a JWK -func jwkKeyFromECDSAPrivateKey(key ecdsa.PrivateKey) (jwk.Key, error) { - ecdsaKeyGeneric, err := jwk.FromRaw(key) - if err != nil { - return nil, errors.Wrap(err, "generating ecdsa jwk") - } - ecdsaKey, ok := ecdsaKeyGeneric.(jwk.ECDSAPrivateKey) - if !ok { - return nil, errors.New("failed casting to ecdsa jwk") - } - return ecdsaKey, nil -} - // jwkFromECDSAPrivateKey converts a ECDSA private key to a JWK func jwkFromECDSAPrivateKey(key ecdsa.PrivateKey) (*PublicKeyJWK, *PrivateKeyJWK, error) { ecdsaKeyGeneric, err := jwk.FromRaw(key) @@ -621,26 +532,13 @@ func jwkFromDilithiumPrivateKey(m crypto.DilithiumMode, k dilithium.PrivateKey) privKeyJWK := PrivateKeyJWK{ KTY: DilithiumKTY, X: x, - Alg: alg.String(), + ALG: alg.String(), D: d, } pubKeyJWK := privKeyJWK.ToPublicKeyJWK() return &pubKeyJWK, &privKeyJWK, nil } -// jwkKeyFromECDSAPublicKey converts a ECDSA public key to a JWK -func jwkKeyFromECDSAPublicKey(key ecdsa.PublicKey) (jwk.Key, error) { - ecdsaKeyGeneric, err := jwk.FromRaw(key) - if err != nil { - return nil, errors.Wrap(err, "generating ecdsa jwk") - } - ecdsaKey, ok := ecdsaKeyGeneric.(jwk.ECDSAPublicKey) - if !ok { - return nil, errors.New("failed casting to ecdsa jwk") - } - return ecdsaKey, nil -} - // jwkFromECDSAPublicKey converts a ECDSA public key to a JWK func jwkFromECDSAPublicKey(key ecdsa.PublicKey) (*PublicKeyJWK, error) { ecdsaKeyGeneric, err := jwk.FromRaw(key) @@ -680,6 +578,6 @@ func jwkFromDilithiumPublicKey(mode crypto.DilithiumMode, k dilithium.PublicKey) return &PublicKeyJWK{ KTY: DilithiumKTY, X: x, - Alg: alg.String(), + ALG: alg.String(), }, nil } diff --git a/crypto/jwx/jwk_test.go b/crypto/jwx/jwk_test.go index d60fcd15..c1f62e2b 100644 --- a/crypto/jwx/jwk_test.go +++ b/crypto/jwx/jwk_test.go @@ -4,25 +4,19 @@ import ( "testing" "github.com/TBD54566975/ssi-sdk/crypto" - "github.com/lestrrat-go/jwx/v2/jwa" - "github.com/lestrrat-go/jwx/v2/jwk" "github.com/stretchr/testify/assert" ) func TestJWKToPrivateKeyJWK(t *testing.T) { + testKID := "test-kid" t.Run("Ed25519", func(tt *testing.T) { // known private key _, privateKey, err := crypto.GenerateEd25519Key() assert.NoError(tt, err) assert.NotEmpty(tt, privateKey) - // convert to JWK - key, err := jwk.FromRaw(privateKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, key) - // to our representation of a jwk - privKeyJWK, err := JWKToPrivateKeyJWK(key) + _, privKeyJWK, err := PrivateKeyToPrivateKeyJWK(testKID, privateKey) assert.NoError(tt, err) assert.NotEmpty(tt, privKeyJWK) @@ -43,11 +37,11 @@ func TestJWKToPrivateKeyJWK(t *testing.T) { assert.NotEmpty(tt, privateKey) // to our representation of a jwk - _, privKeyJWK, err := PrivateKeyToPrivateKeyJWK(privateKey) + _, privKeyJWK, err := PrivateKeyToPrivateKeyJWK(testKID, privateKey) assert.NoError(tt, err) assert.Equal(tt, DilithiumKTY, privKeyJWK.KTY) - assert.EqualValues(tt, DilithiumMode2Alg, privKeyJWK.Alg) + assert.EqualValues(tt, DilithiumMode2Alg, privKeyJWK.ALG) // convert back gotPrivKey, err := privKeyJWK.ToPrivateKey() @@ -63,11 +57,11 @@ func TestJWKToPrivateKeyJWK(t *testing.T) { assert.NotEmpty(tt, privateKey) // to our representation of a jwk - _, privKeyJWK, err := PrivateKeyToPrivateKeyJWK(privateKey) + _, privKeyJWK, err := PrivateKeyToPrivateKeyJWK(testKID, privateKey) assert.NoError(tt, err) assert.Equal(tt, DilithiumKTY, privKeyJWK.KTY) - assert.EqualValues(tt, DilithiumMode3Alg, privKeyJWK.Alg) + assert.EqualValues(tt, DilithiumMode3Alg, privKeyJWK.ALG) // convert back gotPrivKey, err := privKeyJWK.ToPrivateKey() @@ -83,11 +77,11 @@ func TestJWKToPrivateKeyJWK(t *testing.T) { assert.NotEmpty(tt, privateKey) // to our representation of a jwk - _, privKeyJWK, err := PrivateKeyToPrivateKeyJWK(privateKey) + _, privKeyJWK, err := PrivateKeyToPrivateKeyJWK(testKID, privateKey) assert.NoError(tt, err) assert.Equal(tt, DilithiumKTY, privKeyJWK.KTY) - assert.EqualValues(tt, DilithiumMode5Alg, privKeyJWK.Alg) + assert.EqualValues(tt, DilithiumMode5Alg, privKeyJWK.ALG) // convert back gotPrivKey, err := privKeyJWK.ToPrivateKey() @@ -98,19 +92,15 @@ func TestJWKToPrivateKeyJWK(t *testing.T) { } func TestJWKToPublicKeyJWK(t *testing.T) { + testKID := "test-kid" t.Run("Ed25519", func(tt *testing.T) { // known public key publicKey, _, err := crypto.GenerateEd25519Key() assert.NoError(tt, err) assert.NotEmpty(tt, publicKey) - // convert to JWK - key, err := jwk.FromRaw(publicKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, key) - // to our representation of a jwk - pubKeyJWK, err := JWKToPublicKeyJWK(key) + pubKeyJWK, err := PublicKeyToPublicKeyJWK(testKID, publicKey) assert.NoError(tt, err) assert.NotEmpty(tt, pubKeyJWK) @@ -131,11 +121,11 @@ func TestJWKToPublicKeyJWK(t *testing.T) { assert.NotEmpty(tt, publicKey) // to our representation of a jwk - pubKeyJWK, err := PublicKeyToPublicKeyJWK(publicKey) + pubKeyJWK, err := PublicKeyToPublicKeyJWK(testKID, publicKey) assert.NoError(tt, err) assert.Equal(tt, DilithiumKTY, pubKeyJWK.KTY) - assert.EqualValues(tt, DilithiumMode2Alg, pubKeyJWK.Alg) + assert.EqualValues(tt, DilithiumMode2Alg, pubKeyJWK.ALG) // convert back gotPubKey, err := pubKeyJWK.ToPublicKey() @@ -151,11 +141,11 @@ func TestJWKToPublicKeyJWK(t *testing.T) { assert.NotEmpty(tt, publicKey) // to our representation of a jwk - pubKeyJWK, err := PublicKeyToPublicKeyJWK(publicKey) + pubKeyJWK, err := PublicKeyToPublicKeyJWK(testKID, publicKey) assert.NoError(tt, err) assert.Equal(tt, DilithiumKTY, pubKeyJWK.KTY) - assert.EqualValues(tt, DilithiumMode3Alg, pubKeyJWK.Alg) + assert.EqualValues(tt, DilithiumMode3Alg, pubKeyJWK.ALG) // convert back gotPubKey, err := pubKeyJWK.ToPublicKey() @@ -171,11 +161,11 @@ func TestJWKToPublicKeyJWK(t *testing.T) { assert.NotEmpty(tt, publicKey) // to our representation of a jwk - pubKeyJWK, err := PublicKeyToPublicKeyJWK(publicKey) + pubKeyJWK, err := PublicKeyToPublicKeyJWK(testKID, publicKey) assert.NoError(tt, err) assert.Equal(tt, DilithiumKTY, pubKeyJWK.KTY) - assert.EqualValues(tt, DilithiumMode5Alg, pubKeyJWK.Alg) + assert.EqualValues(tt, DilithiumMode5Alg, pubKeyJWK.ALG) // convert back gotPubKey, err := pubKeyJWK.ToPublicKey() @@ -185,167 +175,18 @@ func TestJWKToPublicKeyJWK(t *testing.T) { }) } -func TestJWKFromPrivateKeyJWK(t *testing.T) { - // known private key - _, privateKey, err := crypto.GenerateEd25519Key() - assert.NoError(t, err) - assert.NotEmpty(t, privateKey) - - // convert to JWK - key, err := jwk.FromRaw(privateKey) - assert.NoError(t, err) - assert.NotEmpty(t, key) - - // to our representation of a jwk - privKeyJWK, err := JWKToPrivateKeyJWK(key) - assert.NoError(t, err) - assert.NotEmpty(t, privKeyJWK) - - assert.Equal(t, "OKP", privKeyJWK.KTY) - assert.Equal(t, "Ed25519", privKeyJWK.CRV) - - // back to a jwk - gotJWK, err := JWKFromPrivateKeyJWK(*privKeyJWK) - assert.NoError(t, err) - assert.NotEmpty(t, gotJWK) - assert.Equal(t, key, gotJWK) -} - -func TestJWKFromPublicKeyJWK(t *testing.T) { - // known public key - publicKey, _, err := crypto.GenerateEd25519Key() - assert.NoError(t, err) - assert.NotEmpty(t, publicKey) - - // convert to JWK - key, err := jwk.FromRaw(publicKey) - assert.NoError(t, err) - assert.NotEmpty(t, key) - - // to our representation of a jwk - pubKeyJWK, err := JWKToPublicKeyJWK(key) - assert.NoError(t, err) - assert.NotEmpty(t, pubKeyJWK) - - assert.Equal(t, "OKP", pubKeyJWK.KTY) - assert.Equal(t, "Ed25519", pubKeyJWK.CRV) - - // back to a jwk - gotJWK, err := JWKFromPublicKeyJWK(*pubKeyJWK) - assert.NoError(t, err) - assert.NotEmpty(t, gotJWK) - assert.Equal(t, key, gotJWK) -} - -func TestPublicKeyToJWK(t *testing.T) { - t.Run("RSA", func(tt *testing.T) { - pubKey, _, err := crypto.GenerateRSA2048Key() - assert.NoError(t, err) - - jwk, err := PublicKeyToJWK(pubKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, jwk) - assert.Equal(tt, jwa.RSA, jwk.KeyType()) - - jwk2, err := PublicKeyToJWK(&pubKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, jwk2) - assert.Equal(tt, jwa.RSA, jwk2.KeyType()) - }) - - t.Run("Ed25519", func(tt *testing.T) { - pubKey, _, err := crypto.GenerateEd25519Key() - assert.NoError(t, err) - - jwk, err := PublicKeyToJWK(pubKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, jwk) - assert.Equal(tt, jwa.OKP, jwk.KeyType()) - - jwk2, err := PublicKeyToJWK(&pubKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, jwk2) - assert.Equal(tt, jwa.OKP, jwk2.KeyType()) - }) - - t.Run("X25519", func(tt *testing.T) { - pubKey, _, err := crypto.GenerateX25519Key() - assert.NoError(t, err) - - jwk, err := PublicKeyToJWK(pubKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, jwk) - assert.Equal(tt, jwa.OKP, jwk.KeyType()) - - jwk2, err := PublicKeyToJWK(&pubKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, jwk2) - assert.Equal(tt, jwa.OKP, jwk2.KeyType()) - }) - - t.Run("secp256k1", func(tt *testing.T) { - pubKey, _, err := crypto.GenerateSECP256k1Key() - assert.NoError(t, err) - - jwk, err := PublicKeyToJWK(pubKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, jwk) - assert.Equal(tt, jwa.EC, jwk.KeyType()) - - jwk2, err := PublicKeyToJWK(&pubKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, jwk2) - assert.Equal(tt, jwa.EC, jwk2.KeyType()) - }) - - t.Run("ecdsa P-256", func(tt *testing.T) { - pubKey, _, err := crypto.GenerateP256Key() - assert.NoError(t, err) - - jwk, err := PublicKeyToJWK(pubKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, jwk) - assert.Equal(tt, jwa.EC, jwk.KeyType()) - - jwk2, err := PublicKeyToJWK(&pubKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, jwk2) - assert.Equal(tt, jwa.EC, jwk.KeyType()) - }) - - t.Run("ecdsa P-384", func(tt *testing.T) { - pubKey, _, err := crypto.GenerateP384Key() - assert.NoError(t, err) - - jwk, err := PublicKeyToJWK(pubKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, jwk) - assert.Equal(tt, jwa.EC, jwk.KeyType()) - - jwk2, err := PublicKeyToJWK(&pubKey) - assert.NoError(tt, err) - assert.NotEmpty(tt, jwk2) - assert.Equal(tt, jwa.EC, jwk2.KeyType()) - }) - - t.Run("unsupported", func(tt *testing.T) { - jwk, err := PublicKeyToJWK(nil) - assert.Error(tt, err) - assert.Empty(tt, jwk) - }) -} - func TestPublicKeyToPublicKeyJWK(t *testing.T) { + testKID := "key-id" t.Run("RSA", func(tt *testing.T) { pubKey, _, err := crypto.GenerateRSA2048Key() assert.NoError(t, err) - jwk, err := PublicKeyToPublicKeyJWK(pubKey) + jwk, err := PublicKeyToPublicKeyJWK(testKID, pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, "RSA", jwk.KTY) - jwk2, err := PublicKeyToPublicKeyJWK(&pubKey) + jwk2, err := PublicKeyToPublicKeyJWK(testKID, &pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk2) assert.Equal(tt, "RSA", jwk2.KTY) @@ -355,13 +196,13 @@ func TestPublicKeyToPublicKeyJWK(t *testing.T) { pubKey, _, err := crypto.GenerateEd25519Key() assert.NoError(t, err) - jwk, err := PublicKeyToPublicKeyJWK(pubKey) + jwk, err := PublicKeyToPublicKeyJWK(testKID, pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, "OKP", jwk.KTY) assert.Equal(tt, "Ed25519", jwk.CRV) - jwk2, err := PublicKeyToPublicKeyJWK(&pubKey) + jwk2, err := PublicKeyToPublicKeyJWK(testKID, &pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk2) assert.Equal(tt, "OKP", jwk2.KTY) @@ -372,13 +213,13 @@ func TestPublicKeyToPublicKeyJWK(t *testing.T) { pubKey, _, err := crypto.GenerateX25519Key() assert.NoError(t, err) - jwk, err := PublicKeyToPublicKeyJWK(pubKey) + jwk, err := PublicKeyToPublicKeyJWK(testKID, pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, "OKP", jwk.KTY) assert.Equal(tt, "Ed25519", jwk.CRV) - jwk2, err := PublicKeyToPublicKeyJWK(&pubKey) + jwk2, err := PublicKeyToPublicKeyJWK(testKID, &pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk2) assert.Equal(tt, "OKP", jwk2.KTY) @@ -389,13 +230,13 @@ func TestPublicKeyToPublicKeyJWK(t *testing.T) { pubKey, _, err := crypto.GenerateSECP256k1Key() assert.NoError(t, err) - jwk, err := PublicKeyToPublicKeyJWK(pubKey) + jwk, err := PublicKeyToPublicKeyJWK(testKID, pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, "EC", jwk.KTY) assert.Equal(tt, "secp256k1", jwk.CRV) - jwk2, err := PublicKeyToPublicKeyJWK(&pubKey) + jwk2, err := PublicKeyToPublicKeyJWK(testKID, &pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk2) assert.Equal(tt, "EC", jwk2.KTY) @@ -406,13 +247,13 @@ func TestPublicKeyToPublicKeyJWK(t *testing.T) { pubKey, _, err := crypto.GenerateP256Key() assert.NoError(t, err) - jwk, err := PublicKeyToPublicKeyJWK(pubKey) + jwk, err := PublicKeyToPublicKeyJWK(testKID, pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, "EC", jwk.KTY) assert.Equal(tt, "P-256", jwk.CRV) - jwk2, err := PublicKeyToPublicKeyJWK(&pubKey) + jwk2, err := PublicKeyToPublicKeyJWK(testKID, &pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk2) assert.Equal(tt, "EC", jwk2.KTY) @@ -423,13 +264,13 @@ func TestPublicKeyToPublicKeyJWK(t *testing.T) { pubKey, _, err := crypto.GenerateP384Key() assert.NoError(t, err) - jwk, err := PublicKeyToPublicKeyJWK(pubKey) + jwk, err := PublicKeyToPublicKeyJWK(testKID, pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, "EC", jwk.KTY) assert.Equal(tt, "P-384", jwk.CRV) - jwk2, err := PublicKeyToPublicKeyJWK(&pubKey) + jwk2, err := PublicKeyToPublicKeyJWK(testKID, &pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk2) assert.Equal(tt, "EC", jwk2.KTY) @@ -440,66 +281,67 @@ func TestPublicKeyToPublicKeyJWK(t *testing.T) { pubKey, _, err := crypto.GenerateDilithiumKeyPair(crypto.Dilithium2) assert.NoError(t, err) - jwk, err := PublicKeyToPublicKeyJWK(pubKey) + jwk, err := PublicKeyToPublicKeyJWK(testKID, pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, DilithiumKTY, jwk.KTY) - assert.EqualValues(tt, DilithiumMode2Alg, jwk.Alg) + assert.EqualValues(tt, DilithiumMode2Alg, jwk.ALG) - jwk2, err := PublicKeyToPublicKeyJWK(&pubKey) + jwk2, err := PublicKeyToPublicKeyJWK(testKID, &pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk2) assert.Equal(tt, DilithiumKTY, jwk2.KTY) - assert.EqualValues(tt, DilithiumMode2Alg, jwk2.Alg) + assert.EqualValues(tt, DilithiumMode2Alg, jwk2.ALG) }) t.Run("Dilithium 3", func(tt *testing.T) { pubKey, _, err := crypto.GenerateDilithiumKeyPair(crypto.Dilithium3) assert.NoError(t, err) - jwk, err := PublicKeyToPublicKeyJWK(pubKey) + jwk, err := PublicKeyToPublicKeyJWK(testKID, pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, DilithiumKTY, jwk.KTY) - assert.EqualValues(tt, DilithiumMode3Alg, jwk.Alg) + assert.EqualValues(tt, DilithiumMode3Alg, jwk.ALG) - jwk2, err := PublicKeyToPublicKeyJWK(&pubKey) + jwk2, err := PublicKeyToPublicKeyJWK(testKID, &pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk2) assert.Equal(tt, DilithiumKTY, jwk2.KTY) - assert.EqualValues(tt, DilithiumMode3Alg, jwk2.Alg) + assert.EqualValues(tt, DilithiumMode3Alg, jwk2.ALG) }) t.Run("Dilithium 5", func(tt *testing.T) { pubKey, _, err := crypto.GenerateDilithiumKeyPair(crypto.Dilithium5) assert.NoError(t, err) - jwk, err := PublicKeyToPublicKeyJWK(pubKey) + jwk, err := PublicKeyToPublicKeyJWK(testKID, pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, DilithiumKTY, jwk.KTY) - assert.EqualValues(tt, DilithiumMode5Alg, jwk.Alg) + assert.EqualValues(tt, DilithiumMode5Alg, jwk.ALG) - jwk2, err := PublicKeyToPublicKeyJWK(&pubKey) + jwk2, err := PublicKeyToPublicKeyJWK(testKID, &pubKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk2) assert.Equal(tt, DilithiumKTY, jwk2.KTY) - assert.EqualValues(tt, DilithiumMode5Alg, jwk2.Alg) + assert.EqualValues(tt, DilithiumMode5Alg, jwk2.ALG) }) t.Run("unsupported", func(tt *testing.T) { - jwk, err := PublicKeyToPublicKeyJWK(nil) + jwk, err := PublicKeyToPublicKeyJWK(testKID, nil) assert.Error(tt, err) assert.Empty(tt, jwk) }) } func TestPrivateKeyToPrivateKeyJWK(t *testing.T) { + testKID := "test-kid" t.Run("RSA", func(tt *testing.T) { _, privKey, err := crypto.GenerateRSA2048Key() assert.NoError(t, err) - _, jwk, err := PrivateKeyToPrivateKeyJWK(privKey) + _, jwk, err := PrivateKeyToPrivateKeyJWK(testKID, privKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, "RSA", jwk.KTY) @@ -509,7 +351,7 @@ func TestPrivateKeyToPrivateKeyJWK(t *testing.T) { _, privKey, err := crypto.GenerateEd25519Key() assert.NoError(t, err) - _, jwk, err := PrivateKeyToPrivateKeyJWK(privKey) + _, jwk, err := PrivateKeyToPrivateKeyJWK(testKID, privKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, "OKP", jwk.KTY) @@ -520,7 +362,7 @@ func TestPrivateKeyToPrivateKeyJWK(t *testing.T) { _, privKey, err := crypto.GenerateX25519Key() assert.NoError(t, err) - _, jwk, err := PrivateKeyToPrivateKeyJWK(privKey) + _, jwk, err := PrivateKeyToPrivateKeyJWK(testKID, privKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, "OKP", jwk.KTY) @@ -531,7 +373,7 @@ func TestPrivateKeyToPrivateKeyJWK(t *testing.T) { _, privKey, err := crypto.GenerateSECP256k1Key() assert.NoError(t, err) - _, jwk, err := PrivateKeyToPrivateKeyJWK(privKey) + _, jwk, err := PrivateKeyToPrivateKeyJWK(testKID, privKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, "EC", jwk.KTY) @@ -542,7 +384,7 @@ func TestPrivateKeyToPrivateKeyJWK(t *testing.T) { _, privKey, err := crypto.GenerateP256Key() assert.NoError(t, err) - _, jwk, err := PrivateKeyToPrivateKeyJWK(privKey) + _, jwk, err := PrivateKeyToPrivateKeyJWK(testKID, privKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, "EC", jwk.KTY) @@ -553,7 +395,7 @@ func TestPrivateKeyToPrivateKeyJWK(t *testing.T) { _, privKey, err := crypto.GenerateP384Key() assert.NoError(t, err) - _, jwk, err := PrivateKeyToPrivateKeyJWK(privKey) + _, jwk, err := PrivateKeyToPrivateKeyJWK(testKID, privKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, "EC", jwk.KTY) @@ -564,55 +406,55 @@ func TestPrivateKeyToPrivateKeyJWK(t *testing.T) { _, privKey, err := crypto.GenerateDilithiumKeyPair(crypto.Dilithium2) assert.NoError(t, err) - _, jwk, err := PrivateKeyToPrivateKeyJWK(privKey) + _, jwk, err := PrivateKeyToPrivateKeyJWK(testKID, privKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, DilithiumKTY, jwk.KTY) - assert.EqualValues(tt, DilithiumMode2Alg, jwk.Alg) + assert.EqualValues(tt, DilithiumMode2Alg, jwk.ALG) - _, jwk2, err := PrivateKeyToPrivateKeyJWK(&privKey) + _, jwk2, err := PrivateKeyToPrivateKeyJWK(testKID, &privKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk2) assert.Equal(tt, DilithiumKTY, jwk2.KTY) - assert.EqualValues(tt, DilithiumMode2Alg, jwk2.Alg) + assert.EqualValues(tt, DilithiumMode2Alg, jwk2.ALG) }) t.Run("Dilithium 3", func(tt *testing.T) { _, privKey, err := crypto.GenerateDilithiumKeyPair(crypto.Dilithium3) assert.NoError(t, err) - _, jwk, err := PrivateKeyToPrivateKeyJWK(privKey) + _, jwk, err := PrivateKeyToPrivateKeyJWK(testKID, privKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, DilithiumKTY, jwk.KTY) - assert.EqualValues(tt, DilithiumMode3Alg, jwk.Alg) + assert.EqualValues(tt, DilithiumMode3Alg, jwk.ALG) - _, jwk2, err := PrivateKeyToPrivateKeyJWK(&privKey) + _, jwk2, err := PrivateKeyToPrivateKeyJWK(testKID, &privKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk2) assert.Equal(tt, DilithiumKTY, jwk2.KTY) - assert.EqualValues(tt, DilithiumMode3Alg, jwk2.Alg) + assert.EqualValues(tt, DilithiumMode3Alg, jwk2.ALG) }) t.Run("Dilithium 5", func(tt *testing.T) { _, privKey, err := crypto.GenerateDilithiumKeyPair(crypto.Dilithium5) assert.NoError(t, err) - _, jwk, err := PrivateKeyToPrivateKeyJWK(privKey) + _, jwk, err := PrivateKeyToPrivateKeyJWK(testKID, privKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk) assert.Equal(tt, DilithiumKTY, jwk.KTY) - assert.EqualValues(tt, DilithiumMode5Alg, jwk.Alg) + assert.EqualValues(tt, DilithiumMode5Alg, jwk.ALG) - _, jwk2, err := PrivateKeyToPrivateKeyJWK(&privKey) + _, jwk2, err := PrivateKeyToPrivateKeyJWK(testKID, &privKey) assert.NoError(tt, err) assert.NotEmpty(tt, jwk2) assert.Equal(tt, DilithiumKTY, jwk2.KTY) - assert.EqualValues(tt, DilithiumMode5Alg, jwk2.Alg) + assert.EqualValues(tt, DilithiumMode5Alg, jwk2.ALG) }) t.Run("unsupported", func(tt *testing.T) { - _, jwk, err := PrivateKeyToPrivateKeyJWK(nil) + _, jwk, err := PrivateKeyToPrivateKeyJWK(testKID, nil) assert.Error(tt, err) assert.Empty(tt, jwk) }) @@ -625,7 +467,7 @@ func TestDilithiumVectors(t *testing.T) { retrieveTestVectorAs(tt, dilithiumPublicJWK, &pubKeyJWK) assert.NotEmpty(tt, pubKeyJWK) assert.Equal(tt, DilithiumKTY, pubKeyJWK.KTY) - assert.EqualValues(tt, DilithiumMode5Alg, pubKeyJWK.Alg) + assert.EqualValues(tt, DilithiumMode5Alg, pubKeyJWK.ALG) gotPubKey, err := pubKeyJWK.ToPublicKey() assert.NoError(tt, err) @@ -637,7 +479,7 @@ func TestDilithiumVectors(t *testing.T) { retrieveTestVectorAs(tt, dilithiumPrivateJWK, &privKeyJWK) assert.NotEmpty(tt, privKeyJWK) assert.Equal(tt, DilithiumKTY, privKeyJWK.KTY) - assert.EqualValues(tt, DilithiumMode5Alg, privKeyJWK.Alg) + assert.EqualValues(tt, DilithiumMode5Alg, privKeyJWK.ALG) gotPrivKey, err := privKeyJWK.ToPrivateKey() assert.NoError(tt, err) diff --git a/crypto/jwx/jws.go b/crypto/jwx/jws.go index af7e8341..8f3a524a 100644 --- a/crypto/jwx/jws.go +++ b/crypto/jwx/jws.go @@ -3,6 +3,7 @@ package jwx import ( "fmt" + "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/pkg/errors" @@ -11,10 +12,10 @@ import ( // SignJWS takes a set of payload and signs it with the key defined in the signer func (s *Signer) SignJWS(payload []byte) ([]byte, error) { headers := jws.NewHeaders() - if err := headers.Set(jws.AlgorithmKey, s.SignatureAlgorithm); err != nil { + if err := headers.Set(jws.AlgorithmKey, s.ALG); err != nil { return nil, errors.Wrap(err, "setting algorithm header") } - return jws.Sign(payload, jws.WithKey(s.SignatureAlgorithm, s.Key, jws.WithProtectedHeaders(headers))) + return jws.Sign(payload, jws.WithKey(jwa.SignatureAlgorithm(s.ALG), s.PrivateKey, jws.WithProtectedHeaders(headers))) } // Parse attempts to turn a string into a jwt.Token @@ -32,7 +33,7 @@ func (*Signer) Parse(token string) (jws.Headers, jwt.Token, error) { // VerifyJWS parses a token given the verifier's known algorithm and key, and returns an error, which is nil upon success. func (v *Verifier) VerifyJWS(token string) error { - key := jws.WithKey(v.Algorithm(), v.Key) + key := jws.WithKey(jwa.SignatureAlgorithm(v.ALG), v.publicKey) if _, err := jws.Verify([]byte(token), key); err != nil { return errors.Wrap(err, "verifying JWT") } diff --git a/crypto/jwx/jws_dilithium_test.go b/crypto/jwx/jws_dilithium_test.go index 79973ba4..d6813d97 100644 --- a/crypto/jwx/jws_dilithium_test.go +++ b/crypto/jwx/jws_dilithium_test.go @@ -68,7 +68,8 @@ func TestJWSDilithiumVector(t *testing.T) { retrieveTestVectorAs(tt, dilithiumPrivateJWK, &privKeyJWK) assert.NotEmpty(tt, privKeyJWK) - pubKey, err := privKeyJWK.ToPublicKeyJWK().ToPublicKey() + jwk := privKeyJWK.ToPublicKeyJWK() + pubKey, err := jwk.ToPublicKey() assert.NoError(tt, err) assert.NotEmpty(tt, pubKey) diff --git a/crypto/jwx/jwt.go b/crypto/jwx/jwt.go index 77dd8274..0ddd59dc 100644 --- a/crypto/jwx/jwt.go +++ b/crypto/jwx/jwt.go @@ -3,12 +3,12 @@ package jwx import ( gocrypto "crypto" "fmt" + "reflect" "time" "github.com/TBD54566975/ssi-sdk/crypto" - "github.com/goccy/go-json" + "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lestrrat-go/jwx/v2/jwa" - "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/pkg/errors" @@ -17,146 +17,138 @@ import ( // Signer is a struct that contains the key and algorithm used to sign JWTs and produce JWS values type Signer struct { ID string - jwa.SignatureAlgorithm - jwk.Key + PrivateKeyJWK + gocrypto.PrivateKey } // NewJWXSigner creates a new signer from a private key to sign and produce JWS values -// TODO(gabe) support keys not in jwk.Key https://github.com/TBD54566975/ssi-sdk/issues/365 func NewJWXSigner(id, kid string, key gocrypto.PrivateKey) (*Signer, error) { - privateKeyJWK, err := PrivateKeyToJWK(key) + _, privateKeyJWK, err := PrivateKeyToPrivateKeyJWK(kid, key) if err != nil { - return nil, err + return nil, errors.Wrap(err, "converting private key to JWK") } - return NewJWXSignerFromKey(id, kid, privateKeyJWK) + return jwxSigner(id, *privateKeyJWK, key) } // NewJWXSignerFromJWK creates a new signer from a private key to sign and produce JWS values -func NewJWXSignerFromJWK(id, kid string, key PrivateKeyJWK) (*Signer, error) { - gotJWK, alg, err := jwxSigner(id, kid, key) +func NewJWXSignerFromJWK(id string, key PrivateKeyJWK) (*Signer, error) { + privateKey, err := key.ToPrivateKey() if err != nil { - return nil, err + return nil, errors.Wrap(err, "converting JWK to private key") } - if !IsSupportedJWXSigningVerificationAlgorithm(*alg) { - return nil, fmt.Errorf("unsupported signing algorithm: %s", alg) - } - return &Signer{ - ID: id, - SignatureAlgorithm: *alg, - Key: gotJWK, - }, nil + return jwxSigner(id, key, privateKey) } -// NewJWXSignerFromKey creates a new signer from a private key to sign and produce JWS values -func NewJWXSignerFromKey(id, kid string, key jwk.Key) (*Signer, error) { - gotJWK, alg, err := jwxSignerFromKey(id, kid, key) - if err != nil { - return nil, err +func jwxSigner(id string, jwk PrivateKeyJWK, key gocrypto.PrivateKey) (*Signer, error) { + if id == "" { + return nil, errors.New("id is required") + } + if jwk.IsEmpty() { + return nil, errors.New("jwk is required") + } + if key == nil { + return nil, errors.New("key is required") + } + if jwk.ALG == "" { + alg, err := AlgFromKeyAndCurve(jwk.KTY, jwk.CRV) + if err != nil { + return nil, errors.Wrap(err, "getting alg from key and curve") + } + jwk.ALG = alg } - if !IsSupportedJWXSigningVerificationAlgorithm(*alg) { - return nil, fmt.Errorf("unsupported signing algorithm: %s", alg) + if !IsSupportedJWXSigningVerificationAlgorithm(jwk.ALG) && !IsExperimentalJWXSigningVerificationAlgorithm(jwk.ALG) { + return nil, fmt.Errorf("unsupported signing algorithm: %s", jwk.ALG) + } + if convertedPrivKey, ok := privKeyForJWX(key); ok { + key = convertedPrivKey + } + return &Signer{ID: id, PrivateKeyJWK: jwk, PrivateKey: key}, nil +} + +// some key types need to be converted to work with our signing library, such as +// secp256k1 keys, which need to be converted to ecdsa keys +func privKeyForJWX(key gocrypto.PrivateKey) (gocrypto.PrivateKey, bool) { + for reflect.ValueOf(key).Kind() == reflect.Ptr { + key = reflect.ValueOf(key).Elem().Interface().(gocrypto.PrivateKey) + } + switch k := key.(type) { + case secp256k1.PrivateKey: + return *k.ToECDSA(), true + default: + return nil, false } - return &Signer{ID: id, SignatureAlgorithm: *alg, Key: gotJWK}, nil } // ToVerifier converts a signer to a verifier, where the passed in verifiedID is the intended ID of the verifier for // `aud` validation func (s *Signer) ToVerifier(verifierID string) (*Verifier, error) { - key, err := s.Key.PublicKey() - if err != nil { - return nil, err - } - return NewJWXVerifierFromKey(verifierID, key) + publicKeyJWK := s.PrivateKeyJWK.ToPublicKeyJWK() + return NewJWXVerifierFromJWK(verifierID, publicKeyJWK) } // Verifier is a struct that contains the key and algorithm used to verify JWTs and JWS signatures type Verifier struct { ID string - jwk.Key + PublicKeyJWK + publicKey gocrypto.PublicKey } // NewJWXVerifier creates a new verifier from a public key to verify JWTs and JWS signatures -// TODO(gabe) support keys not in jwk.Key https://github.com/TBD54566975/ssi-sdk/issues/365 -func NewJWXVerifier(id string, key gocrypto.PublicKey) (*Verifier, error) { - privateKeyJWK, err := PublicKeyToJWK(key) +func NewJWXVerifier(id, kid string, key gocrypto.PublicKey) (*Verifier, error) { + publicKeyJWK, err := PublicKeyToPublicKeyJWK(kid, key) if err != nil { - return nil, err + return nil, errors.Wrap(err, "converting public key to JWK") } - return NewJWXVerifierFromKey(id, privateKeyJWK) + return jwxVerifier(id, *publicKeyJWK, key) } // NewJWXVerifierFromJWK creates a new verifier from a public key to verify JWTs and JWS signatures func NewJWXVerifierFromJWK(id string, key PublicKeyJWK) (*Verifier, error) { - gotJWK, alg, err := jwxVerifier(id, key) + pubKey, err := key.ToPublicKey() if err != nil { - return nil, err + return nil, errors.Wrap(err, "converting JWK to public key") } - if !IsSupportedJWXSigningVerificationAlgorithm(*alg) { - return nil, fmt.Errorf("unsupported signing/verification algorithm: %s", alg) - } - return &Verifier{ID: id, Key: gotJWK}, nil + return jwxVerifier(id, key, pubKey) } -// NewJWXVerifierFromKey creates a new verifier from a public key to verify JWTs and JWS signatures -func NewJWXVerifierFromKey(id string, key jwk.Key) (*Verifier, error) { - gotJWK, alg, err := jwkVerifierFromKey(id, key) - if err != nil { - return nil, err +func jwxVerifier(id string, jwk PublicKeyJWK, key gocrypto.PublicKey) (*Verifier, error) { + if id == "" { + return nil, errors.New("id is required") } - if !IsSupportedJWXSigningVerificationAlgorithm(*alg) { - return nil, fmt.Errorf("unsupported signing algorithm: %s", alg) + if jwk.IsEmpty() { + return nil, errors.New("jwk is required") } - return &Verifier{ID: id, Key: gotJWK}, nil -} - -func jwxSigner(id, kid string, key PrivateKeyJWK) (jwk.Key, *jwa.SignatureAlgorithm, error) { - return jwxSignerVerifier(id, kid, key) -} - -func jwxSignerFromKey(id, kid string, key jwk.Key) (jwk.Key, *jwa.SignatureAlgorithm, error) { - return jwxSignerVerifier(id, kid, key) -} - -func jwxVerifier(id string, key PublicKeyJWK) (jwk.Key, *jwa.SignatureAlgorithm, error) { - return jwxSignerVerifier(id, "", key) -} - -func jwkVerifierFromKey(id string, key jwk.Key) (jwk.Key, *jwa.SignatureAlgorithm, error) { - return jwxSignerVerifier(id, "", key) -} - -func jwxSignerVerifier(id, kid string, key any) (jwk.Key, *jwa.SignatureAlgorithm, error) { - jwkBytes, err := json.Marshal(key) - if err != nil { - return nil, nil, err - } - parsedKey, err := jwk.ParseKey(jwkBytes) - if err != nil { - return nil, nil, err - } - crv, err := GetCRVFromJWK(parsedKey) - if err != nil { - return nil, nil, err + if key == nil { + return nil, errors.New("key is required") } - alg, err := AlgFromKeyAndCurve(parsedKey.KeyType(), jwa.EllipticCurveAlgorithm(crv)) - if err != nil { - return nil, nil, errors.Wrap(err, "could not get verification alg from jwk") - } - if err = parsedKey.Set(jwt.IssuerKey, id); err != nil { - return nil, nil, fmt.Errorf("could not set iss with provided value: %s", kid) + if jwk.ALG == "" { + alg, err := AlgFromKeyAndCurve(jwk.KTY, jwk.CRV) + if err != nil { + return nil, errors.Wrap(err, "getting alg from key and curve") + } + jwk.ALG = alg } - if err = parsedKey.Set(jwk.KeyIDKey, kid); err != nil { - return nil, nil, fmt.Errorf("could not set kid with provided value: %s", kid) + if !IsSupportedJWXSigningVerificationAlgorithm(jwk.ALG) && !IsExperimentalJWXSigningVerificationAlgorithm(jwk.ALG) { + return nil, fmt.Errorf("unsupported signing/verification algorithm: %s", jwk.ALG) } - if err = parsedKey.Set(jwk.AlgorithmKey, alg); err != nil { - return nil, nil, fmt.Errorf("could not set alg with value: %s", alg) + if convertedPubKey, ok := pubKeyForJWX(key); ok { + key = convertedPubKey } - return parsedKey, &alg, nil + return &Verifier{ID: id, PublicKeyJWK: jwk, publicKey: key}, nil } -// GetSigningAlgorithm returns the algorithm used to sign the JWT -func (s *Signer) GetSigningAlgorithm() string { - return s.Algorithm().String() +// some key types need to be converted to work with our signing library, such as +// secp256k1 keys, which need to be converted to ecdsa keys +func pubKeyForJWX(key gocrypto.PublicKey) (gocrypto.PublicKey, bool) { + for reflect.ValueOf(key).Kind() == reflect.Ptr { + key = reflect.ValueOf(key).Elem().Interface().(gocrypto.PublicKey) + } + switch k := key.(type) { + case secp256k1.PublicKey: + return *k.ToECDSA(), true + default: + return nil, false + } } // SignWithDefaults takes a set of JWT keys and values to add to a JWT before singing them with @@ -168,26 +160,32 @@ func (s *Signer) SignWithDefaults(kvs map[string]any) ([]byte, error) { iss := s.ID if iss != "" { if err := t.Set(jwt.IssuerKey, iss); err != nil { - return nil, fmt.Errorf("could not set iss with provided value: %s", iss) + return nil, errors.Wrapf(err, "setting iss with provided value: %s", iss) } } iat := time.Now().Unix() if err := t.Set(jwt.IssuedAtKey, iat); err != nil { - return nil, fmt.Errorf("could not set iat with value: %d", iat) + return nil, errors.Wrapf(err, "setting iat with value: %d", iat) } for k, v := range kvs { if err := t.Set(k, v); err != nil { - return nil, errors.Wrapf(err, "could not set %s to value: %v", k, v) + return nil, errors.Wrapf(err, "setting %s to value: %v", k, v) + } + } + hdrs := jws.NewHeaders() + if s.KID != "" { + if err := hdrs.Set(jws.KeyIDKey, s.KID); err != nil { + return nil, errors.Wrap(err, "setting KID protected header") } } - return jwt.Sign(t, jwt.WithKey(s.SignatureAlgorithm, s.Key)) + return jwt.Sign(t, jwt.WithKey(jwa.SignatureAlgorithm(s.ALG), s.PrivateKey, jws.WithProtectedHeaders(hdrs))) } // Verify parses a token given the verifier's known algorithm and key, and returns an error, which is nil upon success func (v *Verifier) Verify(token string) error { - if _, err := jwt.Parse([]byte(token), jwt.WithKey(v.Algorithm(), v.Key)); err != nil { - return errors.Wrap(err, "could not verify JWT") + if _, err := jwt.Parse([]byte(token), jwt.WithKey(jwa.SignatureAlgorithm(v.ALG), v.publicKey)); err != nil { + return errors.Wrap(err, "verifying JWT") } return nil } @@ -196,33 +194,35 @@ func (v *Verifier) Verify(token string) error { func (*Verifier) Parse(token string) (jws.Headers, jwt.Token, error) { parsed, err := jwt.Parse([]byte(token), jwt.WithValidate(false), jwt.WithVerify(false)) if err != nil { - return nil, nil, errors.Wrap(err, "could not parse JWT") + return nil, nil, errors.Wrap(err, "parsing JWT") } headers, err := GetJWSHeaders([]byte(token)) if err != nil { - return nil, nil, errors.Wrap(err, "could not get JWT headers") + return nil, nil, errors.Wrap(err, "getting JWT headers") } return headers, parsed, nil } // VerifyAndParse attempts to turn a string into a jwt.Token and verify its signature using the verifier func (v *Verifier) VerifyAndParse(token string) (jws.Headers, jwt.Token, error) { - parsed, err := jwt.Parse([]byte(token), jwt.WithKey(v.Algorithm(), v.Key)) + parsed, err := jwt.Parse([]byte(token), jwt.WithKey(jwa.SignatureAlgorithm(v.ALG), v.publicKey)) if err != nil { - return nil, nil, errors.Wrap(err, "could not parse and verify JWT") + return nil, nil, errors.Wrap(err, "parsing and verifying JWT") } headers, err := GetJWSHeaders([]byte(token)) if err != nil { - return nil, nil, errors.Wrap(err, "could not get JWT headers") + return nil, nil, errors.Wrap(err, "getting JWT headers") } return headers, parsed, nil } // AlgFromKeyAndCurve returns the supported JSON Web Algorithm for signing for a given key type and curve pair // The curve parameter is optional (e.g. "") as in the case of RSA. -func AlgFromKeyAndCurve(kty jwa.KeyType, crv jwa.EllipticCurveAlgorithm) (jwa.SignatureAlgorithm, error) { - if kty == jwa.RSA { - return jwa.PS256, nil +func AlgFromKeyAndCurve(kty, crv string) (string, error) { + if kty == jwa.RSA.String() { + return jwa.PS256.String(), nil + } else if kty == DilithiumKTY { + return "", errors.New("dilithium alg should already be set") } if crv == "" { @@ -230,25 +230,25 @@ func AlgFromKeyAndCurve(kty jwa.KeyType, crv jwa.EllipticCurveAlgorithm) (jwa.Si } curve := crv - if kty == jwa.OKP { + if kty == jwa.OKP.String() { switch curve { - case jwa.Ed25519: - return jwa.EdDSA, nil + case jwa.Ed25519.String(): + return jwa.EdDSA.String(), nil default: return "", fmt.Errorf("unsupported OKP jwt curve: %s", curve) } } - if kty == jwa.EC { + if kty == jwa.EC.String() { switch curve { - case jwa.EllipticCurveAlgorithm(crypto.SECP256k1): - return jwa.ES256K, nil - case jwa.P256: - return jwa.ES256, nil - case jwa.P384: - return jwa.ES384, nil - case jwa.P521: - return jwa.ES512, nil + case crypto.SECP256k1.String(): + return jwa.ES256K.String(), nil + case jwa.P256.String(): + return jwa.ES256.String(), nil + case jwa.P384.String(): + return jwa.ES384.String(), nil + case jwa.P521.String(): + return jwa.ES512.String(), nil default: return "", fmt.Errorf("unsupported EC curve: %s", curve) } @@ -256,9 +256,31 @@ func AlgFromKeyAndCurve(kty jwa.KeyType, crv jwa.EllipticCurveAlgorithm) (jwa.Si return "", fmt.Errorf("unsupported key type: %s", kty) } -// IsSupportedJWXSigningVerificationAlgorithm returns true if the algorithm is supported for signing or verifying JWTs -func IsSupportedJWXSigningVerificationAlgorithm(algorithm jwa.SignatureAlgorithm) bool { - for _, supported := range GetSupportedJWTSigningVerificationAlgorithms() { +// IsSupportedJWXSigningVerificationAlgorithm returns true if the algorithm is supported for signing or verifying JWXs +func IsSupportedJWXSigningVerificationAlgorithm(algorithm string) bool { + for _, supported := range GetSupportedJWXSigningVerificationAlgorithms() { + if algorithm == supported { + return true + } + } + return false +} + +// GetSupportedJWXSigningVerificationAlgorithms returns a list of supported signing and verifying algorithms for JWXs +func GetSupportedJWXSigningVerificationAlgorithms() []string { + return []string{ + jwa.PS256.String(), + jwa.ES256.String(), + jwa.ES256K.String(), + jwa.ES384.String(), + jwa.ES512.String(), + jwa.EdDSA.String(), + } +} + +// IsExperimentalJWXSigningVerificationAlgorithm returns true if the algorithm is supported for experimental signing or verifying JWXs +func IsExperimentalJWXSigningVerificationAlgorithm(algorithm string) bool { + for _, supported := range GetExperimentalJWXSigningVerificationAlgorithms() { if algorithm == supported { return true } @@ -266,14 +288,11 @@ func IsSupportedJWXSigningVerificationAlgorithm(algorithm jwa.SignatureAlgorithm return false } -// GetSupportedJWTSigningVerificationAlgorithms returns a list of supported signing and verifying algorithms for JWTs -func GetSupportedJWTSigningVerificationAlgorithms() []jwa.SignatureAlgorithm { - return []jwa.SignatureAlgorithm{ - jwa.PS256, - jwa.ES256, - jwa.ES256K, - jwa.ES384, - jwa.ES512, - jwa.EdDSA, +// GetExperimentalJWXSigningVerificationAlgorithms returns a list of experimental signing and verifying algorithms for JWXs +func GetExperimentalJWXSigningVerificationAlgorithms() []string { + return []string{ + DilithiumMode2Alg.String(), + DilithiumMode3Alg.String(), + DilithiumMode5Alg.String(), } } diff --git a/crypto/jwx/jwt_test.go b/crypto/jwx/jwt_test.go index 20c5702d..1f973dac 100644 --- a/crypto/jwx/jwt_test.go +++ b/crypto/jwx/jwt_test.go @@ -99,7 +99,7 @@ func TestSignVerifyJWTForEachSupportedKeyType(t *testing.T) { assert.NoError(t, err) assert.NotEmpty(t, verifier) - sameVerifier, err := NewJWXVerifier(testID, pubKey) + sameVerifier, err := NewJWXVerifier(testID, testKID, pubKey) assert.NoError(t, err) assert.Equal(t, verifier, sameVerifier) @@ -182,7 +182,7 @@ func getTestVectorKey0Signer(t *testing.T) Signer { D: "pLMxJruKPovJlxF3Lu_x9Aw3qe2wcj5WhKUAXYLBjwE", } - signer, err := NewJWXSignerFromJWK("signer-id", knownJWK.KID, knownJWK) + signer, err := NewJWXSignerFromJWK("signer-id", knownJWK) assert.NoError(t, err) return *signer } diff --git a/crypto/keys.go b/crypto/keys.go index 635bf319..b3e8d229 100644 --- a/crypto/keys.go +++ b/crypto/keys.go @@ -20,6 +20,7 @@ import ( ) // GenerateKeyByKeyType creates a brand-new key, returning the public and private key for the given key type +// TODO(gabe): update to support experimental key types https://github.com/TBD54566975/ssi-sdk/issues/146 func GenerateKeyByKeyType(kt KeyType) (crypto.PublicKey, crypto.PrivateKey, error) { switch kt { case Ed25519: @@ -51,6 +52,7 @@ func GenerateKeyByKeyType(kt KeyType) (crypto.PublicKey, crypto.PrivateKey, erro } // PubKeyToBytes constructs a byte representation of a public key, for a set number of supported key types +// TODO(gabe): update to support experimental key types https://github.com/TBD54566975/ssi-sdk/issues/146 func PubKeyToBytes(key crypto.PublicKey) ([]byte, error) { // dereference the ptr if reflect.ValueOf(key).Kind() == reflect.Ptr { @@ -87,6 +89,7 @@ func PubKeyToBytes(key crypto.PublicKey) ([]byte, error) { // BytesToPubKey reconstructs a public key given some bytes and a target key type // It is assumed the key was turned into byte form using the sibling method `PubKeyToBytes` +// TODO(gabe): update to support experimental key types https://github.com/TBD54566975/ssi-sdk/issues/146 func BytesToPubKey(keyBytes []byte, kt KeyType) (crypto.PublicKey, error) { switch kt { case Ed25519, X25519: @@ -143,7 +146,8 @@ func BytesToPubKey(keyBytes []byte, kt KeyType) (crypto.PublicKey, error) { } } -// GetKeyTypeFromPrivateKey returns the key type of a private key for known key types +// GetKeyTypeFromPrivateKey returns the key type for a private key for known key types +// TODO(gabe): update to support experimental key types https://github.com/TBD54566975/ssi-sdk/issues/146 func GetKeyTypeFromPrivateKey(key crypto.PrivateKey) (KeyType, error) { // dereference the ptr if reflect.ValueOf(key).Kind() == reflect.Ptr { @@ -181,6 +185,7 @@ func GetKeyTypeFromPrivateKey(key crypto.PrivateKey) (KeyType, error) { } // PrivKeyToBytes constructs a byte representation of a private key, for a set number of supported key types +// TODO(gabe): update to support experimental key types https://github.com/TBD54566975/ssi-sdk/issues/146 func PrivKeyToBytes(key crypto.PrivateKey) ([]byte, error) { // dereference the ptr if reflect.ValueOf(key).Kind() == reflect.Ptr { @@ -222,6 +227,7 @@ func PrivKeyToBytes(key crypto.PrivateKey) ([]byte, error) { // BytesToPrivKey reconstructs a private key given some bytes and a target key type // It is assumed the key was turned into byte form using the sibling method `PrivKeyToBytes` +// TODO(gabe): update to support experimental key types https://github.com/TBD54566975/ssi-sdk/issues/146 func BytesToPrivKey(keyBytes []byte, kt KeyType) (crypto.PrivateKey, error) { switch kt { case Ed25519: diff --git a/cryptosuite/jsonwebkey2020.go b/cryptosuite/jsonwebkey2020.go index 82d87216..b5d0a1af 100644 --- a/cryptosuite/jsonwebkey2020.go +++ b/cryptosuite/jsonwebkey2020.go @@ -7,6 +7,7 @@ import ( "github.com/TBD54566975/ssi-sdk/crypto" "github.com/TBD54566975/ssi-sdk/crypto/jwx" "github.com/TBD54566975/ssi-sdk/util" + "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jws" "github.com/pkg/errors" ) @@ -94,7 +95,7 @@ func GenerateJSONWebKey2020(kty KTY, crv CRV) (*JSONWebKey2020, error) { // JSONWebKey2020FromPrivateKey returns a JsonWebKey2020 value from a given private key, containing both JWK // public and private key representations of the key. func JSONWebKey2020FromPrivateKey(key gocrypto.PrivateKey) (*JSONWebKey2020, error) { - pubKeyJWK, privKeyJWK, err := jwx.PrivateKeyToPrivateKeyJWK(key) + pubKeyJWK, privKeyJWK, err := jwx.PrivateKeyToPrivateKeyJWK("", key) if err != nil { return nil, err } @@ -187,11 +188,11 @@ func (s *JSONWebKeySigner) Sign(tbs []byte) ([]byte, error) { if err := headers.Set(jws.CriticalKey, []string{b64}); err != nil { return nil, err } - return jws.Sign(nil, jws.WithKey(s.SignatureAlgorithm, s.Key), jws.WithHeaders(headers), jws.WithDetachedPayload(tbs)) + return jws.Sign(nil, jws.WithKey(jwa.SignatureAlgorithm(s.ALG), s.PrivateKey), jws.WithHeaders(headers), jws.WithDetachedPayload(tbs)) } func (s *JSONWebKeySigner) GetKeyID() string { - return s.Key.KeyID() + return s.KID } func (*JSONWebKeySigner) GetSignatureType() SignatureType { @@ -199,7 +200,7 @@ func (*JSONWebKeySigner) GetSignatureType() SignatureType { } func (s *JSONWebKeySigner) GetSigningAlgorithm() string { - return s.Algorithm().String() + return s.ALG } func (s *JSONWebKeySigner) SetProofPurpose(purpose ProofPurpose) { @@ -218,8 +219,8 @@ func (s *JSONWebKeySigner) GetPayloadFormat() PayloadFormat { return s.format } -func NewJSONWebKeySigner(id, kid string, key jwx.PrivateKeyJWK, purpose ProofPurpose) (*JSONWebKeySigner, error) { - signer, err := jwx.NewJWXSignerFromJWK(id, kid, key) +func NewJSONWebKeySigner(id string, key jwx.PrivateKeyJWK, purpose ProofPurpose) (*JSONWebKeySigner, error) { + signer, err := jwx.NewJWXSignerFromJWK(id, key) if err != nil { return nil, err } @@ -239,12 +240,16 @@ type JSONWebKeyVerifier struct { // Verify attempts to verify a `signature` against a given `message`, returning nil if the verification is successful // and an error should it fail. func (v JSONWebKeyVerifier) Verify(message, signature []byte) error { - _, err := jws.Verify(signature, jws.WithKey(v.Algorithm(), v.Key), jws.WithDetachedPayload(message)) + pubKey, err := v.PublicKeyJWK.ToPublicKey() + if err != nil { + return errors.Wrap(err, "getting public key") + } + _, err = jws.Verify(signature, jws.WithKey(jwa.SignatureAlgorithm(v.ALG), pubKey), jws.WithDetachedPayload(message)) return err } func (v JSONWebKeyVerifier) GetKeyID() string { - return v.Key.KeyID() + return v.KID } func NewJSONWebKeyVerifier(id string, key jwx.PublicKeyJWK) (*JSONWebKeyVerifier, error) { @@ -252,9 +257,7 @@ func NewJSONWebKeyVerifier(id string, key jwx.PublicKeyJWK) (*JSONWebKeyVerifier if err != nil { return nil, err } - return &JSONWebKeyVerifier{ - Verifier: *verifier, - }, nil + return &JSONWebKeyVerifier{Verifier: *verifier}, nil } // PubKeyBytesToTypedKey converts a public key byte slice to a crypto.PublicKey based on a given key type, merging diff --git a/cryptosuite/jsonwebkey2020_test.go b/cryptosuite/jsonwebkey2020_test.go index 6bb4b7be..e3dd13f7 100644 --- a/cryptosuite/jsonwebkey2020_test.go +++ b/cryptosuite/jsonwebkey2020_test.go @@ -46,7 +46,7 @@ func TestJSONWebKey2020SignerVerifier(t *testing.T) { assert.NoError(t, err) assert.NotEmpty(t, jwk) - signer, err := NewJSONWebKeySigner(signerID, jwk.ID, jwk.PrivateKeyJWK, AssertionMethod) + signer, err := NewJSONWebKeySigner(signerID, jwk.PrivateKeyJWK, AssertionMethod) assert.NoError(t, err) testMessage := []byte("my name is satoshi") diff --git a/cryptosuite/jwssignaturesuite_test.go b/cryptosuite/jwssignaturesuite_test.go index d50967ab..4cf46455 100644 --- a/cryptosuite/jwssignaturesuite_test.go +++ b/cryptosuite/jwssignaturesuite_test.go @@ -106,7 +106,7 @@ func TestJsonWebSignature2020AllKeyTypes(t *testing.T) { jwk, err := GenerateJSONWebKey2020(test.kty, test.crv) if !test.expectErr { - signer, err := NewJSONWebKeySigner(issuerID, jwk.ID, jwk.PrivateKeyJWK, AssertionMethod) + signer, err := NewJSONWebKeySigner(issuerID, jwk.PrivateKeyJWK, AssertionMethod) assert.NoError(tt, err) // pin to avoid ptr shadowing @@ -162,7 +162,8 @@ func TestCredentialLDProof(t *testing.T) { assert.NotEmpty(t, jwk) jwk.ID = issuer - signer, err := NewJSONWebKeySigner(issuer, jwk.ID, jwk.PrivateKeyJWK, AssertionMethod) + jwk.PrivateKeyJWK.KID = issuer + signer, err := NewJSONWebKeySigner(issuer, jwk.PrivateKeyJWK, AssertionMethod) assert.NoError(t, err) assert.NotEmpty(t, signer) @@ -240,7 +241,7 @@ func TestJSONWebSignature2020TestVectorCredential0(t *testing.T) { assert.NoError(t, err) } -func TestJsonWebSignature2020TestVectorsCredential1(t *testing.T) { +func TestJSONWebSignature2020TestVectorsCredential1(t *testing.T) { // https://github.com/decentralized-identity/JWS-Test-Suite/blob/main/data/keys/key-0-ed25519.json signer, jwk := getTestVectorKey0Signer(t, AssertionMethod) @@ -289,7 +290,7 @@ func (t *TestVerifiablePresentation) SetProof(p *crypto.Proof) { t.Proof = p } -func TestJsonWebSignature2020TestVectorPresentation0(t *testing.T) { +func TestJSONWebSignature2020TestVectorPresentation0(t *testing.T) { // https://github.com/decentralized-identity/JWS-Test-Suite/blob/main/data/keys/key-0-ed25519.json signer, jwk := getTestVectorKey0Signer(t, Authentication) @@ -332,7 +333,7 @@ func TestJsonWebSignature2020TestVectorPresentation0(t *testing.T) { } // https://github.com/decentralized-identity/JWS-Test-Suite/blob/main/data/keys/key-0-ed25519.json -func TestJsonWebSignature2020TestVectorPresentation1(t *testing.T) { +func TestJSONWebSignature2020TestVectorPresentation1(t *testing.T) { signer, jwk := getTestVectorKey0Signer(t, Authentication) // https://github.com/decentralized-identity/JWS-Test-Suite/blob/main/data/presentations/presentation-1.json @@ -420,11 +421,13 @@ func getTestVectorKey0Signer(t *testing.T, purpose ProofPurpose) (JSONWebKeySign knownJWK := JSONWebKey2020{ ID: "did:example:123#key-0", PublicKeyJWK: jwx.PublicKeyJWK{ + KID: "key-0", KTY: "OKP", CRV: "Ed25519", X: "JYCAGl6C7gcDeKbNqtXBfpGzH0f5elifj7L6zYNj_Is", }, PrivateKeyJWK: jwx.PrivateKeyJWK{ + KID: "key-0", KTY: "OKP", CRV: "Ed25519", X: "JYCAGl6C7gcDeKbNqtXBfpGzH0f5elifj7L6zYNj_Is", @@ -432,7 +435,7 @@ func getTestVectorKey0Signer(t *testing.T, purpose ProofPurpose) (JSONWebKeySign }, } - signer, err := NewJSONWebKeySigner("verifier-id", knownJWK.ID, knownJWK.PrivateKeyJWK, purpose) + signer, err := NewJSONWebKeySigner("verifier-id", knownJWK.PrivateKeyJWK, purpose) assert.NoError(t, err) return *signer, knownJWK } diff --git a/did/ion/crypto.go b/did/ion/crypto.go index b8fa2223..b6b55c2a 100644 --- a/did/ion/crypto.go +++ b/did/ion/crypto.go @@ -92,13 +92,8 @@ func CanonicalizeAny(data any) ([]byte, error) { // https://identity.foundation/sidetree/spec/#public-key-commitment-scheme func Commit(key sdkcrypto.PublicKeyJWK) (reveal, commitment string, err error) { // 1. Encode the public key into the form of a valid JWK. - gotJWK, err := sdkcrypto.JWKFromPublicKeyJWK(key) - if err != nil { - return "", "", err - } - // 2. Canonicalize the JWK encoded public key using the implementation’s JSON_CANONICALIZATION_SCHEME. - canonicalKey, err := CanonicalizeAny(gotJWK) + canonicalKey, err := CanonicalizeAny(key) if err != nil { logrus.WithError(err).Error("could not canonicalize JWK") return "", "", err diff --git a/did/ion/operations.go b/did/ion/operations.go index 068f855d..332fe2cf 100644 --- a/did/ion/operations.go +++ b/did/ion/operations.go @@ -48,6 +48,7 @@ import ( "github.com/TBD54566975/ssi-sdk/did" "github.com/TBD54566975/ssi-sdk/util" "github.com/goccy/go-json" + "github.com/google/uuid" "github.com/pkg/errors" ) @@ -228,7 +229,7 @@ func NewIONDID(doc Document) (*DID, *CreateRequest, error) { if err != nil { return nil, nil, errors.Wrap(err, "generating update keypair") } - updatePubKeyJWK, updatePrivKeyJWK, err := jwx.PrivateKeyToPrivateKeyJWK(updatePrivateKey) + updatePubKeyJWK, updatePrivKeyJWK, err := jwx.PrivateKeyToPrivateKeyJWK(uuid.NewString(), updatePrivateKey) if err != nil { return nil, nil, errors.Wrap(err, "converting update key pair to JWK") } @@ -238,7 +239,7 @@ func NewIONDID(doc Document) (*DID, *CreateRequest, error) { if err != nil { return nil, nil, errors.Wrap(err, "generating recovery keypair") } - recoveryPubKeyJWK, recoveryPrivKeyJWK, err := jwx.PrivateKeyToPrivateKeyJWK(recoveryPrivateKey) + recoveryPubKeyJWK, recoveryPrivKeyJWK, err := jwx.PrivateKeyToPrivateKeyJWK(uuid.NewString(), recoveryPrivateKey) if err != nil { return nil, nil, errors.Wrap(err, "converting recovery keypair to JWK") } @@ -284,7 +285,7 @@ func (d DID) Update(stateChange StateChange) (*DID, *UpdateRequest, error) { if err != nil { return nil, nil, errors.Wrap(err, "generating next update keypair") } - nextUpdatePubKeyJWK, nextUpdatePrivKeyJWK, err := jwx.PrivateKeyToPrivateKeyJWK(nextUpdatePrivateKey) + nextUpdatePubKeyJWK, nextUpdatePrivKeyJWK, err := jwx.PrivateKeyToPrivateKeyJWK(uuid.NewString(), nextUpdatePrivateKey) if err != nil { return nil, nil, errors.Wrap(err, "converting next update key pair to JWK") } @@ -327,7 +328,7 @@ func (d DID) Recover(doc Document) (*DID, *RecoverRequest, error) { if err != nil { return nil, nil, errors.Wrap(err, "generating nest recovery keypair") } - nextRecoveryPubKeyJWK, nextRecoveryPrivKeyJWK, err := jwx.PrivateKeyToPrivateKeyJWK(nextRecoveryPrivateKey) + nextRecoveryPubKeyJWK, nextRecoveryPrivKeyJWK, err := jwx.PrivateKeyToPrivateKeyJWK(uuid.NewString(), nextRecoveryPrivateKey) if err != nil { return nil, nil, errors.Wrap(err, "converting next recovery key pair to JWK") } @@ -337,7 +338,7 @@ func (d DID) Recover(doc Document) (*DID, *RecoverRequest, error) { if err != nil { return nil, nil, errors.Wrap(err, "generating next update keypair") } - nextUpdatePubKeyJWK, nextUpdatePrivKeyJWK, err := jwx.PrivateKeyToPrivateKeyJWK(nextUpdatePrivateKey) + nextUpdatePubKeyJWK, nextUpdatePrivKeyJWK, err := jwx.PrivateKeyToPrivateKeyJWK(uuid.NewString(), nextUpdatePrivateKey) if err != nil { return nil, nil, errors.Wrap(err, "converting next update key pair to JWK") } diff --git a/did/jwk.go b/did/jwk.go index 150166bb..d342428f 100644 --- a/did/jwk.go +++ b/did/jwk.go @@ -11,7 +11,6 @@ import ( "github.com/TBD54566975/ssi-sdk/crypto/jwx" "github.com/TBD54566975/ssi-sdk/cryptosuite" "github.com/goccy/go-json" - "github.com/lestrrat-go/jwx/v2/jwk" "github.com/pkg/errors" ) @@ -57,7 +56,8 @@ func GenerateDIDJWK(kt crypto.KeyType) (gocrypto.PrivateKey, *DIDJWK, error) { if err != nil { return nil, nil, errors.Wrap(err, "generating key for did:jwk") } - pubKeyJWK, err := jwx.PublicKeyToJWK(pubKey) + // kid not needed since it will be set on expansion + pubKeyJWK, err := jwx.PublicKeyToPublicKeyJWK("", pubKey) if err != nil { return nil, nil, errors.Wrap(err, "converting public key to JWK") } @@ -65,7 +65,7 @@ func GenerateDIDJWK(kt crypto.KeyType) (gocrypto.PrivateKey, *DIDJWK, error) { // 2. Serialize it into a UTF-8 string // 3. Encode string using base64url // 4. Prepend the string with the did:jwk prefix - didJWK, err := CreateDIDJWK(pubKeyJWK) + didJWK, err := CreateDIDJWK(*pubKeyJWK) if err != nil { return nil, nil, errors.Wrap(err, "creating did:jwk") } @@ -74,7 +74,7 @@ func GenerateDIDJWK(kt crypto.KeyType) (gocrypto.PrivateKey, *DIDJWK, error) { // CreateDIDJWK creates a did:jwk from a JWK public key by following the steps in the spec: // https://github.com/quartzjer/did-jwk/blob/main/spec.md -func CreateDIDJWK(publicKeyJWK jwk.Key) (*DIDJWK, error) { +func CreateDIDJWK(publicKeyJWK jwx.PublicKeyJWK) (*DIDJWK, error) { // 2. Serialize it into a UTF-8 string pubKeyJWKBytes, err := json.Marshal(publicKeyJWK) if err != nil { diff --git a/did/jwk_test.go b/did/jwk_test.go index 8543236b..f17d50bd 100644 --- a/did/jwk_test.go +++ b/did/jwk_test.go @@ -7,9 +7,9 @@ import ( "testing" "github.com/TBD54566975/ssi-sdk/crypto" + "github.com/TBD54566975/ssi-sdk/crypto/jwx" "github.com/TBD54566975/ssi-sdk/cryptosuite" "github.com/goccy/go-json" - "github.com/lestrrat-go/jwx/v2/jwk" "github.com/stretchr/testify/assert" ) @@ -151,10 +151,10 @@ func TestExpandDIDJWK(t *testing.T) { assert.NotEmpty(t, pk) assert.NotEmpty(t, sk) - gotJWK, err := jwk.FromRaw(pk) + gotJWK, err := jwx.PublicKeyToPublicKeyJWK("test-kid", pk) assert.NoError(t, err) - didJWK, err := CreateDIDJWK(gotJWK) + didJWK, err := CreateDIDJWK(*gotJWK) assert.NoError(t, err) assert.NotEmpty(t, didJWK) diff --git a/did/key.go b/did/key.go index 8e4bcb3b..bd0087a6 100644 --- a/did/key.go +++ b/did/key.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/TBD54566975/ssi-sdk/crypto/jwx" - "github.com/lestrrat-go/jwx/v2/jwk" "github.com/mr-tron/base58" "github.com/TBD54566975/ssi-sdk/cryptosuite" @@ -234,12 +233,7 @@ func constructVerificationMethod(id, keyReference string, pubKey []byte, keyType return nil, errors.Wrap(err, "converting bytes to public key") } - standardJWK, err := jwk.FromRaw(cryptoPubKey) - if err != nil { - return nil, errors.Wrap(err, "could not expand key of type JsonWebKey2020") - } - - pubKeyJWK, err := jwx.JWKToPublicKeyJWK(standardJWK) + pubKeyJWK, err := jwx.PublicKeyToPublicKeyJWK(keyReference, cryptoPubKey) if err != nil { return nil, errors.Wrap(err, "could convert did:key to PublicKeyJWK") } diff --git a/example/presentation/presentation.go b/example/presentation/presentation.go index 85d15b67..91d27f47 100644 --- a/example/presentation/presentation.go +++ b/example/presentation/presentation.go @@ -63,23 +63,22 @@ func makePresentationRequest(requesterID string, presentationData exchange.Prese // Signer: // https://github.com/TBD54566975/ssi-sdk/blob/main/cryptosuite/jsonwebkey2020.go#L350 // Implements: https://github.com/TBD54566975/ssi-sdk/blob/main/cryptosuite/jwt.go#L12 - signer, err := jwx.NewJWXSignerFromJWK(requesterID, jwk.ID, jwk.PrivateKeyJWK) + signer, err := jwx.NewJWXSignerFromJWK(requesterID, jwk.PrivateKeyJWK) if err != nil { return nil, err } // Builds a presentation request - // Requires a signer, the presentation data, and a target - // Target is the Audience Key - target := "did:test" - requestJWTBytes, err := exchange.BuildJWTPresentationRequest(*signer, presentationData, target) + // Requires a signer, the presentation data, and an optional audience + audience := "did:test" + requestJWTBytes, err := exchange.BuildJWTPresentationRequest(*signer, presentationData, []string{audience}) if err != nil { return nil, err } // TODO: Add better documentation on the verification process // Seems like needed to know more of: https://github.com/lestrrat-go/jwx/tree/develop/v2/jwt - verifier, err := cryptosuite.NewJSONWebKeyVerifier(target, jwk.PublicKeyJWK) + verifier, err := cryptosuite.NewJSONWebKeyVerifier(audience, jwk.PublicKeyJWK) if err != nil { return nil, err } diff --git a/example/usecase/employer_university_flow/pkg/util.go b/example/usecase/employer_university_flow/pkg/util.go index e0168ceb..796d85e3 100644 --- a/example/usecase/employer_university_flow/pkg/util.go +++ b/example/usecase/employer_university_flow/pkg/util.go @@ -36,7 +36,7 @@ func NewEntity(name string, didMethod did.Method) (*Entity, error) { // over multiple mechanisms. For more information, please go to here: // https://identity.foundation/presentation-exchange/#presentation-request and for the source code with the sdk, // https://github.com/TBD54566975/ssi-sdk/blob/main/credential/exchange/request.go is appropriate to start off with. -func MakePresentationRequest(key gocrypto.PrivateKey, keyID string, presentationData exchange.PresentationDefinition, requesterID, targetID string) (pr []byte, signer *jwx.Signer, err error) { +func MakePresentationRequest(key gocrypto.PrivateKey, keyID string, presentationData exchange.PresentationDefinition, requesterID, audienceID string) (pr []byte, signer *jwx.Signer, err error) { example.WriteNote("Presentation Request (JWT) is created") // Signer uses a private key @@ -47,7 +47,7 @@ func MakePresentationRequest(key gocrypto.PrivateKey, keyID string, presentation // Builds a presentation request // Requires a signer, the presentation data, and a target which is the Audience Key - requestJWTBytes, err := exchange.BuildJWTPresentationRequest(*signer, presentationData, targetID) + requestJWTBytes, err := exchange.BuildJWTPresentationRequest(*signer, presentationData, []string{audienceID}) if err != nil { return nil, nil, err }