Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor JWX logic to reduce dep on JWX lib #368

Merged
merged 6 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions credential/exchange/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
}

Expand All @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion credential/exchange/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion credential/exchange/submission.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
8 changes: 2 additions & 6 deletions credential/exchange/submission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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())
Expand Down
2 changes: 1 addition & 1 deletion credential/exchange/verification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions credential/jws.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
38 changes: 27 additions & 11 deletions credential/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -179,18 +186,15 @@ 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
}

// 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")
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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
Expand Down
11 changes: 6 additions & 5 deletions credential/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion credential/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down
7 changes: 5 additions & 2 deletions credential/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,21 @@ 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",
D: "pLMxJruKPovJlxF3Lu_x9Aw3qe2wcj5WhKUAXYLBjwE",
},
}

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()
Expand All @@ -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()
Expand Down
Loading