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

Decouple JWT and LD signing/verifying paths #195

Merged
merged 3 commits into from
Sep 16, 2022
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
31 changes: 9 additions & 22 deletions credential/exchange/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ package exchange
import (
"fmt"

"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/goccy/go-json"
"github.com/google/uuid"
"github.com/lestrrat-go/jwx/jwt"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"

"github.com/TBD54566975/ssi-sdk/cryptosuite"
)

// PresentationRequestType represents wrappers for Presentation Definitions submitted as requests
Expand All @@ -28,19 +27,13 @@ const (
// BuildPresentationRequest https://identity.foundation/presentation-exchange/#presentation-request
// used for transmitting a Presentation Definition from a holder to a verifier. Target is who the request is intended for.
// TODO(gabe) expand to other presentation types and signers https://github.com/TBD54566975/ssi-sdk/issues/57
func BuildPresentationRequest(signer cryptosuite.Signer, pt PresentationRequestType, def PresentationDefinition, target string) ([]byte, error) {
func BuildPresentationRequest(signer crypto.JWTSigner, pt PresentationRequestType, def PresentationDefinition, target string) ([]byte, error) {
if !IsSupportedPresentationRequestType(pt) {
return nil, fmt.Errorf("unsupported presentation request type: %s", pt)
}
switch pt {
case JWTRequest:
jwkSigner, ok := signer.(*cryptosuite.JSONWebKeySigner)
if !ok {
err := fmt.Errorf("signer not valid for request type: %s", pt)
logrus.WithError(err).Error()
return nil, err
}
return BuildJWTPresentationRequest(*jwkSigner, def, target)
return BuildJWTPresentationRequest(signer, def, target)
default:
err := fmt.Errorf("presentation request type <%s> is not implemented", pt)
logrus.WithError(err).Error()
Expand All @@ -49,41 +42,35 @@ func BuildPresentationRequest(signer cryptosuite.Signer, pt PresentationRequestT
}

// BuildJWTPresentationRequest builds a JWT representation of a presentation request
func BuildJWTPresentationRequest(signer cryptosuite.JSONWebKeySigner, def PresentationDefinition, target string) ([]byte, error) {
func BuildJWTPresentationRequest(signer crypto.JWTSigner, def PresentationDefinition, target string) ([]byte, error) {
jwtValues := map[string]interface{}{
jwt.JwtIDKey: uuid.NewString(),
jwt.IssuerKey: signer.GetKeyID(),
jwt.IssuerKey: signer.KeyID(),
jwt.AudienceKey: target,
PresentationDefinitionKey: def,
}
return signer.SignGenericJWT(jwtValues)
return signer.SignJWT(jwtValues)
}

// VerifyPresentationRequest finds the correct verifier and parser for a given presentation request type,
// verifying the signature on the request, and returning the parsed Presentation Definition object.
func VerifyPresentationRequest(verifier cryptosuite.Verifier, pt PresentationRequestType, request []byte) (*PresentationDefinition, error) {
func VerifyPresentationRequest(verifier crypto.JWTVerifier, pt PresentationRequestType, request []byte) (*PresentationDefinition, error) {
err := fmt.Errorf("cannot verify unsupported presentation request type: %s", pt)
if !IsSupportedPresentationRequestType(pt) {
logrus.WithError(err).Error()
return nil, err
}
switch pt {
case JWTRequest:
jwkVerifier, ok := verifier.(*cryptosuite.JSONWebKeyVerifier)
if !ok {
err := fmt.Errorf("verifier not valid for request type: %s", pt)
logrus.WithError(err).Error()
return nil, err
}
return VerifyJWTPresentationRequest(*jwkVerifier, request)
return VerifyJWTPresentationRequest(verifier, request)
default:
return nil, err
}
}

// VerifyJWTPresentationRequest verifies the signature on a JWT-based presentation request for a given verifier
// and then returns the parsed Presentation Definition object as a result.
func VerifyJWTPresentationRequest(verifier cryptosuite.JSONWebKeyVerifier, request []byte) (*PresentationDefinition, error) {
func VerifyJWTPresentationRequest(verifier crypto.JWTVerifier, request []byte) (*PresentationDefinition, error) {
parsed, err := verifier.VerifyAndParseJWT(string(request))
if err != nil {
err := errors.Wrap(err, "could not verify and parse jwt presentation request")
Expand Down
35 changes: 24 additions & 11 deletions credential/exchange/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,31 @@ import (
"testing"

"github.com/goccy/go-json"
"github.com/lestrrat-go/jwx/jwk"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/TBD54566975/ssi-sdk/cryptosuite"
)

func TestBuildPresentationRequest(t *testing.T) {

t.Run("JWT Request", func(t *testing.T) {
jwk, err := cryptosuite.GenerateJSONWebKey2020(cryptosuite.OKP, cryptosuite.Ed25519)
_, privKey, err := crypto.GenerateEd25519Key()
assert.NoError(t, err)
signer, err := cryptosuite.NewJSONWebKeySigner(jwk.ID, jwk.PrivateKeyJWK, cryptosuite.Authentication)

key, err := jwk.New(privKey)
require.NoError(t, err)

signer, err := crypto.NewJWTSigner("test-id", key)
assert.NoError(t, err)

testDef := getDummyPresentationDefinition()
requestJWTBytes, err := BuildJWTPresentationRequest(*signer, testDef, "did:test")
assert.NoError(t, err)
assert.NotEmpty(t, requestJWTBytes)

verifier, err := cryptosuite.NewJSONWebKeyVerifier(jwk.ID, jwk.PublicKeyJWK)
verifier, err := signer.ToVerifier()
assert.NoError(t, err)

parsed, err := verifier.VerifyAndParseJWT(string(requestJWTBytes))
Expand All @@ -35,17 +40,21 @@ func TestBuildPresentationRequest(t *testing.T) {
})

t.Run("Happy Path", func(t *testing.T) {
jwk, err := cryptosuite.GenerateJSONWebKey2020(cryptosuite.OKP, cryptosuite.Ed25519)
_, privKey, err := crypto.GenerateEd25519Key()
assert.NoError(t, err)
signer, err := cryptosuite.NewJSONWebKeySigner(jwk.ID, jwk.PrivateKeyJWK, cryptosuite.Authentication)

key, err := jwk.New(privKey)
require.NoError(t, err)

signer, err := crypto.NewJWTSigner("test-id", key)
assert.NoError(t, err)

testDef := getDummyPresentationDefinition()
requestJWTBytes, err := BuildPresentationRequest(signer, JWTRequest, testDef, "did:test")
requestJWTBytes, err := BuildPresentationRequest(*signer, JWTRequest, testDef, "did:test")
assert.NoError(t, err)
assert.NotEmpty(t, requestJWTBytes)

verifier, err := cryptosuite.NewJSONWebKeyVerifier(jwk.ID, jwk.PublicKeyJWK)
verifier, err := signer.ToVerifier()
assert.NoError(t, err)

parsed, err := verifier.VerifyAndParseJWT(string(requestJWTBytes))
Expand All @@ -57,13 +66,17 @@ func TestBuildPresentationRequest(t *testing.T) {
})

t.Run("Unsupported Request Method", func(t *testing.T) {
jwk, err := cryptosuite.GenerateJSONWebKey2020(cryptosuite.OKP, cryptosuite.Ed25519)
_, privKey, err := crypto.GenerateEd25519Key()
assert.NoError(t, err)
signer, err := cryptosuite.NewJSONWebKeySigner(jwk.ID, jwk.PrivateKeyJWK, cryptosuite.Authentication)

key, err := jwk.New(privKey)
require.NoError(t, err)

signer, err := crypto.NewJWTSigner("test-id", key)
assert.NoError(t, err)

testDef := getDummyPresentationDefinition()
_, err = BuildPresentationRequest(signer, "bad", testDef, "did:test")
_, err = BuildPresentationRequest(*signer, "bad", testDef, "did:test")
assert.Error(t, err)
assert.Contains(t, err.Error(), "unsupported presentation request type")
})
Expand Down
14 changes: 5 additions & 9 deletions credential/exchange/submission.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"regexp"
"strings"

"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/goccy/go-json"
"github.com/google/uuid"
"github.com/oliveagle/jsonpath"
Expand All @@ -14,7 +15,6 @@ import (

"github.com/TBD54566975/ssi-sdk/credential"
"github.com/TBD54566975/ssi-sdk/credential/signing"
"github.com/TBD54566975/ssi-sdk/cryptosuite"
"github.com/TBD54566975/ssi-sdk/util"
)

Expand Down Expand Up @@ -124,7 +124,9 @@ func (pc *PresentationClaim) GetClaimJSON() (map[string]interface{}, error) {
// BuildPresentationSubmission constructs a submission given a presentation definition, set of claims, and an
// embed target format.
// https://identity.foundation/presentation-exchange/#presentation-submission
func BuildPresentationSubmission(signer cryptosuite.Signer, def PresentationDefinition, claims []PresentationClaim, et EmbedTarget) ([]byte, error) {
// Note: this method does not support LD cryptosuites, and prefers JWT representations. Future refactors
// may include an analog method for LD suites.
func BuildPresentationSubmission(signer crypto.JWTSigner, def PresentationDefinition, claims []PresentationClaim, et EmbedTarget) ([]byte, error) {
if !IsSupportedEmbedTarget(et) {
err := fmt.Errorf("unsupported presentation submission embed target type: %s", et)
logrus.WithError(err).Error()
Expand All @@ -138,19 +140,13 @@ func BuildPresentationSubmission(signer cryptosuite.Signer, def PresentationDefi
}
switch et {
case JWTVPTarget:
jwkSigner, ok := signer.(*cryptosuite.JSONWebKeySigner)
if !ok {
err := fmt.Errorf("signer not valid for request type: %s", et)
logrus.WithError(err).Error()
return nil, err
}
vpSubmission, err := BuildPresentationSubmissionVP(def, normalizedClaims)
if err != nil {
err := errors.Wrap(err, "unable to fulfill presentation definition with given credentials")
logrus.WithError(err).Error()
return nil, err
}
return signing.SignVerifiablePresentationJWT(*jwkSigner, *vpSubmission)
return signing.SignVerifiablePresentationJWT(signer, *vpSubmission)
default:
err := fmt.Errorf("presentation submission embed target <%s> is not implemented", et)
logrus.WithError(err).Error()
Expand Down
25 changes: 15 additions & 10 deletions credential/exchange/submission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"testing"

"github.com/goccy/go-json"
"github.com/lestrrat-go/jwx/jwk"
"github.com/oliveagle/jsonpath"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/TBD54566975/ssi-sdk/credential"
"github.com/TBD54566975/ssi-sdk/credential/signing"
Expand All @@ -16,7 +18,7 @@ import (

func TestBuildPresentationSubmission(t *testing.T) {
t.Run("Unsupported embed target", func(tt *testing.T) {
_, err := BuildPresentationSubmission(&cryptosuite.JSONWebKeySigner{}, PresentationDefinition{}, nil, "badEmbedTarget")
_, err := BuildPresentationSubmission(crypto.JWTSigner{}, PresentationDefinition{}, nil, "badEmbedTarget")
assert.Error(tt, err)
assert.Contains(tt, err.Error(), "unsupported presentation submission embed target type")
})
Expand Down Expand Up @@ -48,7 +50,7 @@ func TestBuildPresentationSubmission(t *testing.T) {
LDPFormat: LDPVC.Ptr(),
SignatureAlgorithmOrProofType: string(cryptosuite.JSONWebSignature2020),
}
submissionBytes, err := BuildPresentationSubmission(signer, def, []PresentationClaim{presentationClaim}, JWTVPTarget)
submissionBytes, err := BuildPresentationSubmission(*signer, def, []PresentationClaim{presentationClaim}, JWTVPTarget)
assert.NoError(tt, err)
assert.NotEmpty(tt, submissionBytes)

Expand Down Expand Up @@ -779,16 +781,19 @@ func getGenericTestClaim() map[string]interface{} {
}
}

func getJWKSignerVerifier(t *testing.T) (*cryptosuite.JSONWebKeySigner, *cryptosuite.JSONWebKeyVerifier) {
jwk, err := cryptosuite.GenerateJSONWebKey2020(cryptosuite.OKP, cryptosuite.Ed25519)
assert.NoError(t, err)
assert.NotEmpty(t, jwk)
func getJWKSignerVerifier(t *testing.T) (*crypto.JWTSigner, *crypto.JWTVerifier) {
_, privKey, err := crypto.GenerateEd25519Key()
require.NoError(t, err)

signer, err := cryptosuite.NewJSONWebKeySigner(jwk.ID, jwk.PrivateKeyJWK, cryptosuite.AssertionMethod)
assert.NoError(t, err)
key, err := jwk.New(privKey)
require.NoError(t, err)

verifier, err := cryptosuite.NewJSONWebKeyVerifier(jwk.ID, jwk.PublicKeyJWK)
assert.NoError(t, err)
kid := "test-key"
signer, err := crypto.NewJWTSigner(kid, key)
require.NoError(t, err)

verifier, err := signer.ToVerifier()
require.NoError(t, err)

return signer, verifier
}
14 changes: 5 additions & 9 deletions credential/exchange/verification.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ import (
"fmt"
"strings"

"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/goccy/go-json"
"github.com/oliveagle/jsonpath"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"

"github.com/TBD54566975/ssi-sdk/credential"
"github.com/TBD54566975/ssi-sdk/credential/signing"
"github.com/TBD54566975/ssi-sdk/cryptosuite"
"github.com/TBD54566975/ssi-sdk/util"
)

// VerifyPresentationSubmission verifies a presentation submission for both signature validity and correctness
// with the specification. It is assumed that the caller knows the submission embed target, and the corresponding
// presentation definition, and has access to the public key of the signer.
func VerifyPresentationSubmission(verifier cryptosuite.Verifier, et EmbedTarget, def PresentationDefinition, submission []byte) error {
// Note: this method does not support LD cryptosuites, and prefers JWT representations. Future refactors
// may include an analog method for LD suites.
func VerifyPresentationSubmission(verifier crypto.JWTVerifier, et EmbedTarget, def PresentationDefinition, submission []byte) error {
if err := canProcessDefinition(def); err != nil {
err := errors.Wrap(err, "feature not supported in processing given presentation definition")
logrus.WithError(err).Error("not able to verify presentation submission")
Expand All @@ -31,13 +33,7 @@ func VerifyPresentationSubmission(verifier cryptosuite.Verifier, et EmbedTarget,
}
switch et {
case JWTVPTarget:
jwkVerifier, ok := verifier.(*cryptosuite.JSONWebKeyVerifier)
if !ok {
err := fmt.Errorf("verifier not valid for request type: %s", et)
logrus.WithError(err).Error()
return err
}
vp, err := signing.VerifyVerifiablePresentationJWT(*jwkVerifier, string(submission))
vp, err := signing.VerifyVerifiablePresentationJWT(verifier, string(submission))
if err != nil {
err := errors.Wrap(err, "verification of the presentation submission failed")
logrus.WithError(err).Error()
Expand Down
13 changes: 7 additions & 6 deletions credential/exchange/verification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package exchange
import (
"testing"

"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/stretchr/testify/assert"

"github.com/TBD54566975/ssi-sdk/credential"
Expand All @@ -12,8 +13,8 @@ import (

func TestVerifyPresentationSubmission(t *testing.T) {
t.Run("Unsupported embed target", func(tt *testing.T) {
verifier := cryptosuite.JSONWebKeyVerifier{}
err := VerifyPresentationSubmission(&verifier, "badEmbedTarget", PresentationDefinition{}, nil)
verifier := crypto.JWTVerifier{}
err := VerifyPresentationSubmission(verifier, "badEmbedTarget", PresentationDefinition{}, nil)
assert.Error(tt, err)
assert.Contains(tt, err.Error(), "unsupported presentation submission embed target type")
})
Expand All @@ -37,7 +38,7 @@ func TestVerifyPresentationSubmission(t *testing.T) {
},
}
_, verifier := getJWKSignerVerifier(tt)
err := VerifyPresentationSubmission(verifier, JWTVPTarget, def, nil)
err := VerifyPresentationSubmission(*verifier, JWTVPTarget, def, nil)
assert.Error(tt, err)
assert.Contains(tt, err.Error(), "verification of the presentation submission failed")
})
Expand Down Expand Up @@ -69,11 +70,11 @@ func TestVerifyPresentationSubmission(t *testing.T) {
LDPFormat: LDPVC.Ptr(),
SignatureAlgorithmOrProofType: string(cryptosuite.JSONWebSignature2020),
}
submissionBytes, err := BuildPresentationSubmission(signer, def, []PresentationClaim{presentationClaim}, JWTVPTarget)
submissionBytes, err := BuildPresentationSubmission(*signer, def, []PresentationClaim{presentationClaim}, JWTVPTarget)
assert.NoError(tt, err)
assert.NotEmpty(tt, submissionBytes)

err = VerifyPresentationSubmission(verifier, JWTVPTarget, def, submissionBytes)
err = VerifyPresentationSubmission(*verifier, JWTVPTarget, def, submissionBytes)
assert.NoError(tt, err)
})
}
Expand Down Expand Up @@ -106,7 +107,7 @@ func TestVerifyPresentationSubmissionVP(t *testing.T) {
LDPFormat: LDPVC.Ptr(),
SignatureAlgorithmOrProofType: string(cryptosuite.JSONWebSignature2020),
}
submissionBytes, err := BuildPresentationSubmission(signer, def, []PresentationClaim{presentationClaim}, JWTVPTarget)
submissionBytes, err := BuildPresentationSubmission(*signer, def, []PresentationClaim{presentationClaim}, JWTVPTarget)
assert.NoError(tt, err)
assert.NotEmpty(tt, submissionBytes)

Expand Down
Loading