Skip to content
This repository was archived by the owner on Dec 12, 2024. It is now read-only.

Commit 9a70a02

Browse files
Refactor JWX logic to reduce dep on JWX lib (#368)
* remove some jwk methods * simplify jwt/jws/jwk logic * clean up dilithium stuff * todo tags * simplify jwx logic * make aud optional
1 parent da47a51 commit 9a70a02

File tree

27 files changed

+495
-710
lines changed

27 files changed

+495
-710
lines changed

credential/exchange/request.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,21 @@ func BuildPresentationRequest(signer any, pt PresentationRequestType, def Presen
4646
if len(opts) > 1 {
4747
return nil, fmt.Errorf("only one option supported")
4848
}
49-
var audience string
49+
var audience []string
5050
if len(opts) == 1 {
5151
opt := opts[0]
5252
if opt.Type != AudienceOption {
5353
return nil, fmt.Errorf("unsupported option type: %s", opt.Type)
5454
}
5555
var ok bool
56-
audience, ok = opt.Value.(string)
57-
if !ok {
58-
return nil, fmt.Errorf("audience option value must be a string")
56+
audStr, ok := opt.Value.(string)
57+
if ok {
58+
audience = []string{audStr}
59+
} else {
60+
audience, ok = opt.Value.([]string)
61+
if !ok {
62+
return nil, fmt.Errorf("audience option value must be a string or array of strings")
63+
}
5964
}
6065
}
6166

@@ -75,15 +80,15 @@ func BuildPresentationRequest(signer any, pt PresentationRequestType, def Presen
7580
}
7681

7782
// BuildJWTPresentationRequest builds a JWT representation of a presentation request
78-
func BuildJWTPresentationRequest(signer jwx.Signer, def PresentationDefinition, target string) ([]byte, error) {
83+
func BuildJWTPresentationRequest(signer jwx.Signer, def PresentationDefinition, audience []string) ([]byte, error) {
7984
jwtValues := map[string]any{
8085
jwt.JwtIDKey: uuid.NewString(),
8186
jwt.IssuerKey: signer.ID,
82-
jwt.AudienceKey: target,
87+
jwt.AudienceKey: audience,
8388
PresentationDefinitionKey: def,
8489
}
85-
if target != "" {
86-
jwtValues[jwt.AudienceKey] = target
90+
if len(audience) != 0 {
91+
jwtValues[jwt.AudienceKey] = audience
8792
}
8893
return signer.SignWithDefaults(jwtValues)
8994
}

credential/exchange/request_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func TestBuildPresentationRequest(t *testing.T) {
1818
assert.NoError(t, err)
1919

2020
testDef := getDummyPresentationDefinition()
21-
requestJWTBytes, err := BuildJWTPresentationRequest(*signer, testDef, "did:test")
21+
requestJWTBytes, err := BuildJWTPresentationRequest(*signer, testDef, []string{"did:test"})
2222
assert.NoError(t, err)
2323
assert.NotEmpty(t, requestJWTBytes)
2424

credential/exchange/submission.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func BuildPresentationSubmission(signer any, requester string, def PresentationD
165165
if err != nil {
166166
return nil, errors.Wrap(err, "unable to fulfill presentation definition with given credentials")
167167
}
168-
return credential.SignVerifiablePresentationJWT(jwtSigner, credential.JWTVVPParameters{Audience: requester}, *vpSubmission)
168+
return credential.SignVerifiablePresentationJWT(jwtSigner, credential.JWTVVPParameters{Audience: []string{requester}}, *vpSubmission)
169169
default:
170170
return nil, fmt.Errorf("presentation submission embed target <%s> is not implemented", et)
171171
}

credential/exchange/submission_test.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"github.com/TBD54566975/ssi-sdk/did"
99
"github.com/TBD54566975/ssi-sdk/util"
1010
"github.com/goccy/go-json"
11-
"github.com/lestrrat-go/jwx/v2/jwk"
1211
"github.com/oliveagle/jsonpath"
1312
"github.com/stretchr/testify/assert"
1413
"github.com/stretchr/testify/require"
@@ -92,7 +91,7 @@ func TestBuildPresentationSubmission(t *testing.T) {
9291
presentationClaim := PresentationClaim{
9392
Token: util.StringPtr(string(credJWT)),
9493
JWTFormat: JWTVC.Ptr(),
95-
SignatureAlgorithmOrProofType: signer.GetSigningAlgorithm(),
94+
SignatureAlgorithmOrProofType: signer.ALG,
9695
}
9796
submissionBytes, err := BuildPresentationSubmission(*signer, signer.ID, def, []PresentationClaim{presentationClaim}, JWTVPTarget)
9897
assert.NoError(tt, err)
@@ -850,13 +849,10 @@ func getJWKSignerVerifier(t *testing.T) (*jwx.Signer, *jwx.Verifier) {
850849
privKey, didKey, err := did.GenerateDIDKey(crypto.Ed25519)
851850
require.NoError(t, err)
852851

853-
key, err := jwk.FromRaw(privKey)
854-
require.NoError(t, err)
855-
856852
expanded, err := didKey.Expand()
857853
require.NoError(t, err)
858854
kid := expanded.VerificationMethod[0].ID
859-
signer, err := jwx.NewJWXSignerFromKey(didKey.String(), kid, key)
855+
signer, err := jwx.NewJWXSigner(didKey.String(), kid, privKey)
860856
require.NoError(t, err)
861857

862858
verifier, err := signer.ToVerifier(didKey.String())

credential/exchange/verification_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func TestVerifyPresentationSubmission(t *testing.T) {
137137
presentationClaim := PresentationClaim{
138138
Token: util.StringPtr(string(credJWT)),
139139
JWTFormat: JWTVC.Ptr(),
140-
SignatureAlgorithmOrProofType: signer.GetSigningAlgorithm(),
140+
SignatureAlgorithmOrProofType: signer.ALG,
141141
}
142142
submissionBytes, err := BuildPresentationSubmission(*signer, verifier.ID, def, []PresentationClaim{presentationClaim}, JWTVPTarget)
143143
assert.NoError(tt, err)

credential/jws.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ func SignVerifiableCredentialJWS(signer jwx.Signer, cred VerifiableCredential) (
2121
}
2222

2323
headers := jws.NewHeaders()
24+
if err = headers.Set(jws.KeyIDKey, signer.KID); err != nil {
25+
return nil, errors.Wrap(err, "setting key ID JOSE header")
26+
}
2427
if err = headers.Set(jws.ContentTypeKey, VCMediaType); err != nil {
2528
return nil, errors.Wrap(err, "setting content type JOSE header")
2629
}
27-
28-
signed, err := jws.Sign(payload, jws.WithKey(jwa.SignatureAlgorithm(signer.GetSigningAlgorithm()), signer.Key, jws.WithProtectedHeaders(headers)))
30+
signed, err := jws.Sign(payload, jws.WithKey(jwa.SignatureAlgorithm(signer.ALG), signer.PrivateKey, jws.WithProtectedHeaders(headers)))
2931
if err != nil {
3032
return nil, errors.Wrap(err, "signing JWT credential")
3133
}

credential/jwt.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/TBD54566975/ssi-sdk/did"
1010
"github.com/goccy/go-json"
1111
"github.com/google/uuid"
12+
"github.com/lestrrat-go/jwx/v2/jwa"
1213
"github.com/lestrrat-go/jwx/v2/jws"
1314
"github.com/lestrrat-go/jwx/v2/jwt"
1415
"github.com/pkg/errors"
@@ -81,7 +82,13 @@ func SignVerifiableCredentialJWT(signer jwx.Signer, cred VerifiableCredential) (
8182
return nil, errors.New("setting credential value")
8283
}
8384

84-
signed, err := jwt.Sign(t, jwt.WithKey(signer.SignatureAlgorithm, signer.Key))
85+
hdrs := jws.NewHeaders()
86+
if signer.KID != "" {
87+
if err := hdrs.Set(jws.KeyIDKey, signer.KID); err != nil {
88+
return nil, errors.Wrap(err, "setting KID protected header")
89+
}
90+
}
91+
signed, err := jwt.Sign(t, jwt.WithKey(jwa.SignatureAlgorithm(signer.ALG), signer.PrivateKey, jws.WithProtectedHeaders(hdrs)))
8592
if err != nil {
8693
return nil, errors.Wrap(err, "signing JWT credential")
8794
}
@@ -179,18 +186,15 @@ func ParseVerifiableCredentialFromToken(token jwt.Token) (*VerifiableCredential,
179186

180187
// JWTVVPParameters represents additional parameters needed when constructing a JWT VP as opposed to a VP
181188
type JWTVVPParameters struct {
182-
// Audience is a required intended audience of the JWT.
183-
Audience string `validate:"required"`
189+
// Audience is an optional audience of the JWT.
190+
Audience []string
184191
// Expiration is an optional expiration time of the JWT using the `exp` property.
185192
Expiration int
186193
}
187194

188195
// SignVerifiablePresentationJWT transforms a VP into a VP JWT and signs it
189196
// According to https://w3c.github.io/vc-jwt/#version-1.1
190197
func SignVerifiablePresentationJWT(signer jwx.Signer, parameters JWTVVPParameters, presentation VerifiablePresentation) ([]byte, error) {
191-
if parameters.Audience == "" {
192-
return nil, errors.New("audience cannot be empty")
193-
}
194198
if presentation.IsEmpty() {
195199
return nil, errors.New("presentation cannot be empty")
196200
}
@@ -200,8 +204,14 @@ func SignVerifiablePresentationJWT(signer jwx.Signer, parameters JWTVVPParameter
200204

201205
t := jwt.New()
202206
// set JWT-VP specific parameters
203-
if err := t.Set(jwt.AudienceKey, parameters.Audience); err != nil {
204-
return nil, errors.Wrap(err, "setting audience value")
207+
208+
// NOTE: according to the JWT encoding rules (https://www.w3.org/TR/vc-data-model/#jwt-encoding) aud is a required
209+
// property; however, aud is not required according to the JWT spec. Requiring audience limits a number of cases
210+
// where JWT-VPs can be used, so we do not enforce this requirement.
211+
if parameters.Audience != nil {
212+
if err := t.Set(jwt.AudienceKey, parameters.Audience); err != nil {
213+
return nil, errors.Wrap(err, "setting audience value")
214+
}
205215
}
206216
iatAndNBF := time.Now().Unix()
207217
if err := t.Set(jwt.IssuedAtKey, iatAndNBF); err != nil {
@@ -241,7 +251,13 @@ func SignVerifiablePresentationJWT(signer jwx.Signer, parameters JWTVVPParameter
241251
return nil, errors.Wrap(err, "setting vp value")
242252
}
243253

244-
signed, err := jwt.Sign(t, jwt.WithKey(signer.SignatureAlgorithm, signer.Key))
254+
hdrs := jws.NewHeaders()
255+
if signer.KID != "" {
256+
if err := hdrs.Set(jws.KeyIDKey, signer.KID); err != nil {
257+
return nil, errors.Wrap(err, "setting KID protected header")
258+
}
259+
}
260+
signed, err := jwt.Sign(t, jwt.WithKey(jwa.SignatureAlgorithm(signer.ALG), signer.PrivateKey, jws.WithProtectedHeaders(hdrs)))
245261
if err != nil {
246262
return nil, errors.Wrap(err, "signing JWT presentation")
247263
}
@@ -272,13 +288,13 @@ func VerifyVerifiablePresentationJWT(ctx context.Context, verifier jwx.Verifier,
272288
// make sure the audience matches the verifier
273289
audMatch := false
274290
for _, aud := range vpToken.Audience() {
275-
if aud == verifier.ID || aud == verifier.KeyID() {
291+
if aud == verifier.ID || aud == verifier.KID {
276292
audMatch = true
277293
break
278294
}
279295
}
280296
if !audMatch {
281-
return nil, nil, nil, errors.Errorf("audience mismatch: expected [%s] or [%s], got %s", verifier.ID, verifier.KeyID(), vpToken.Audience())
297+
return nil, nil, nil, errors.Errorf("audience mismatch: expected [%s] or [%s], got %s", verifier.ID, verifier.KID, vpToken.Audience())
282298
}
283299

284300
// verify signature for each credential in the vp

credential/jwt_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func TestVerifiablePresentationJWT(t *testing.T) {
9898
}
9999

100100
signer := getTestVectorKey0Signer(tt)
101-
signed, err := SignVerifiablePresentationJWT(signer, JWTVVPParameters{Audience: "bad-audience"}, testPresentation)
101+
signed, err := SignVerifiablePresentationJWT(signer, JWTVVPParameters{Audience: []string{"bad-audience"}}, testPresentation)
102102
assert.NoError(tt, err)
103103

104104
verifier, err := signer.ToVerifier(signer.ID)
@@ -126,7 +126,7 @@ func TestVerifiablePresentationJWT(t *testing.T) {
126126
}
127127

128128
signer := getTestVectorKey0Signer(tt)
129-
signed, err := SignVerifiablePresentationJWT(signer, JWTVVPParameters{Audience: signer.ID}, testPresentation)
129+
signed, err := SignVerifiablePresentationJWT(signer, JWTVVPParameters{Audience: []string{signer.ID}}, testPresentation)
130130
assert.NoError(tt, err)
131131

132132
verifier, err := signer.ToVerifier(signer.ID)
@@ -204,7 +204,7 @@ func TestVerifiablePresentationJWT(t *testing.T) {
204204
// sign the presentation from the subject to the issuer
205205
subjectSigner, err := jwx.NewJWXSigner(subjectDID.String(), subjectKID, subjectPrivKey)
206206
assert.NoError(tt, err)
207-
signed, err := SignVerifiablePresentationJWT(*subjectSigner, JWTVVPParameters{Audience: issuerDID.String()}, testPresentation)
207+
signed, err := SignVerifiablePresentationJWT(*subjectSigner, JWTVVPParameters{Audience: []string{issuerDID.String()}}, testPresentation)
208208
assert.NoError(tt, err)
209209

210210
// parse the VP
@@ -233,13 +233,14 @@ func TestVerifiablePresentationJWT(t *testing.T) {
233233
func getTestVectorKey0Signer(t *testing.T) jwx.Signer {
234234
// https://github.com/decentralized-identity/JWS-Test-Suite/blob/main/data/keys/key-0-ed25519.json
235235
knownJWK := jwx.PrivateKeyJWK{
236+
KID: "key-0",
236237
KTY: "OKP",
237238
CRV: "Ed25519",
238239
X: "JYCAGl6C7gcDeKbNqtXBfpGzH0f5elifj7L6zYNj_Is",
239240
D: "pLMxJruKPovJlxF3Lu_x9Aw3qe2wcj5WhKUAXYLBjwE",
240241
}
241242

242-
signer, err := jwx.NewJWXSignerFromJWK("signer-id", knownJWK.KID, knownJWK)
243-
assert.NoError(t, err)
243+
signer, err := jwx.NewJWXSignerFromJWK("signer-id", knownJWK)
244+
require.NoError(t, err)
244245
return *signer
245246
}

credential/signature.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func VerifyJWTCredential(cred string, resolver did.Resolver) (bool, error) {
8282
}
8383

8484
// construct a verifier
85-
credVerifier, err := jwx.NewJWXVerifier(issuerDID.ID, issuerKey)
85+
credVerifier, err := jwx.NewJWXVerifier(issuerDID.ID, issuerKID, issuerKey)
8686
if err != nil {
8787
return false, errors.Wrapf(err, "error constructing verifier for credential<%s>", token.JwtID())
8888
}

credential/util_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,21 @@ func TestCredentialsFromInterface(t *testing.T) {
3737
knownJWK := cryptosuite.JSONWebKey2020{
3838
ID: "did:example:123#key-0",
3939
PublicKeyJWK: jwx.PublicKeyJWK{
40+
KID: "key-0",
4041
KTY: "OKP",
4142
CRV: "Ed25519",
4243
X: "JYCAGl6C7gcDeKbNqtXBfpGzH0f5elifj7L6zYNj_Is",
4344
},
4445
PrivateKeyJWK: jwx.PrivateKeyJWK{
46+
KID: "key-0",
4547
KTY: "OKP",
4648
CRV: "Ed25519",
4749
X: "JYCAGl6C7gcDeKbNqtXBfpGzH0f5elifj7L6zYNj_Is",
4850
D: "pLMxJruKPovJlxF3Lu_x9Aw3qe2wcj5WhKUAXYLBjwE",
4951
},
5052
}
5153

52-
signer, err := cryptosuite.NewJSONWebKeySigner("issuer-id", knownJWK.ID, knownJWK.PrivateKeyJWK, cryptosuite.AssertionMethod)
54+
signer, err := cryptosuite.NewJSONWebKeySigner("issuer-id", knownJWK.PrivateKeyJWK, cryptosuite.AssertionMethod)
5355
assert.NoError(t, err)
5456

5557
suite := cryptosuite.GetJSONWebSignature2020Suite()
@@ -71,13 +73,14 @@ func TestCredentialsFromInterface(t *testing.T) {
7173

7274
t.Run("JWT Cred", func(tt *testing.T) {
7375
knownJWK := jwx.PrivateKeyJWK{
76+
KID: "key-0",
7477
KTY: "OKP",
7578
CRV: "Ed25519",
7679
X: "JYCAGl6C7gcDeKbNqtXBfpGzH0f5elifj7L6zYNj_Is",
7780
D: "pLMxJruKPovJlxF3Lu_x9Aw3qe2wcj5WhKUAXYLBjwE",
7881
}
7982

80-
signer, err := jwx.NewJWXSignerFromJWK("signer-id", knownJWK.KID, knownJWK)
83+
signer, err := jwx.NewJWXSignerFromJWK("signer-id", knownJWK)
8184
assert.NoError(tt, err)
8285

8386
testCred := getTestCredential()

0 commit comments

Comments
 (0)