Skip to content

Commit

Permalink
Refactor JWX logic to reduce dep on JWX lib (#368)
Browse files Browse the repository at this point in the history
* remove some jwk methods

* simplify jwt/jws/jwk logic

* clean up dilithium stuff

* todo tags

* simplify jwx logic

* make aud optional
  • Loading branch information
decentralgabe authored May 5, 2023
1 parent da47a51 commit 9a70a02
Show file tree
Hide file tree
Showing 27 changed files with 495 additions and 710 deletions.
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

0 comments on commit 9a70a02

Please sign in to comment.