Skip to content

Commit

Permalink
Refactor verification, integrity, parsing (#404)
Browse files Browse the repository at this point in the history
* rename verfiers to validators

* split out parsing

* rename

* lint

* Fix test

* bad renames

* renames

* make id optional

* Missing updates

---------

Co-authored-by: Andres Uribe Gonzalez <[email protected]>
  • Loading branch information
decentralgabe and andresuribe87 authored Jun 1, 2023
1 parent 651d0e6 commit effaa78
Show file tree
Hide file tree
Showing 27 changed files with 280 additions and 245 deletions.
4 changes: 0 additions & 4 deletions credential/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,6 @@ func (vcb *VerifiableCredentialBuilder) SetCredentialSubject(subject CredentialS
return errors.New(BuilderEmptyError)
}

if subject.GetID() == "" {
return errors.New("credential subject must have an ID property")
}

vcb.CredentialSubject = subject
return nil
}
Expand Down
9 changes: 4 additions & 5 deletions credential/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,12 @@ func TestCredentialBuilder(t *testing.T) {
err = builder.SetCredentialStatus(status)
assert.NoError(t, err)

// bad cred subject - no id
badSubject := CredentialSubject{
// cred subject - no id
subjectWithMissingID := CredentialSubject{
"name": "Satoshi",
}
err = builder.SetCredentialSubject(badSubject)
assert.Error(t, err)
assert.Contains(t, err.Error(), "credential subject must have an ID property")
err = builder.SetCredentialSubject(subjectWithMissingID)
assert.NoError(t, err)

// good subject
subject := CredentialSubject{
Expand Down
3 changes: 2 additions & 1 deletion credential/exchange/submission.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

"github.com/TBD54566975/ssi-sdk/credential"
"github.com/TBD54566975/ssi-sdk/credential/integrity"
"github.com/TBD54566975/ssi-sdk/crypto/jwx"
"github.com/TBD54566975/ssi-sdk/util"
"github.com/goccy/go-json"
Expand Down Expand Up @@ -165,7 +166,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: []string{requester}}, *vpSubmission)
return integrity.SignVerifiablePresentationJWT(jwtSigner, integrity.JWTVVPParameters{Audience: []string{requester}}, *vpSubmission)
default:
return nil, fmt.Errorf("presentation submission embed target <%s> is not implemented", et)
}
Expand Down
9 changes: 5 additions & 4 deletions credential/exchange/submission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"testing"

"github.com/TBD54566975/ssi-sdk/credential/integrity"
"github.com/TBD54566975/ssi-sdk/cryptosuite/jws2020"
"github.com/goccy/go-json"
"github.com/oliveagle/jsonpath"
Expand Down Expand Up @@ -64,7 +65,7 @@ func TestBuildPresentationSubmission(t *testing.T) {

resolver, err := resolution.NewResolver([]resolution.Resolver{key.Resolver{}}...)
assert.NoError(tt, err)
_, _, _, err = credential.VerifyVerifiablePresentationJWT(context.Background(), *verifier, resolver, string(submissionBytes))
_, _, _, err = integrity.VerifyVerifiablePresentationJWT(context.Background(), *verifier, resolver, string(submissionBytes))
assert.Error(tt, err)
assert.Contains(tt, err.Error(), "credential must have a proof")
})
Expand Down Expand Up @@ -92,7 +93,7 @@ func TestBuildPresentationSubmission(t *testing.T) {
signer, verifier := getJWKSignerVerifier(tt)
testVC := getTestVerifiableCredential(signer.ID, signer.ID)

credJWT, err := credential.SignVerifiableCredentialJWT(*signer, testVC)
credJWT, err := integrity.SignVerifiableCredentialJWT(*signer, testVC)
assert.NoError(tt, err)
assert.NotEmpty(tt, credJWT)

Expand All @@ -107,7 +108,7 @@ func TestBuildPresentationSubmission(t *testing.T) {

resolver, err := resolution.NewResolver([]resolution.Resolver{key.Resolver{}}...)
assert.NoError(tt, err)
_, _, vp, err := credential.VerifyVerifiablePresentationJWT(context.Background(), *verifier, resolver, string(submissionBytes))
_, _, vp, err := integrity.VerifyVerifiablePresentationJWT(context.Background(), *verifier, resolver, string(submissionBytes))
assert.NoError(tt, err)

assert.NoError(tt, vp.IsValid())
Expand Down Expand Up @@ -358,7 +359,7 @@ func TestBuildPresentationSubmissionVP(t *testing.T) {
assert.Equal(tt, "test-verifiable-credential", asVC.ID)
assert.Equal(tt, "Block", asVC.CredentialSubject["company"])

_, vcJWTToken, asVCJWT, err := credential.ParseVerifiableCredentialFromJWT(*(vp.VerifiableCredential[1].(*string)))
_, vcJWTToken, asVCJWT, err := integrity.ParseVerifiableCredentialFromJWT(*(vp.VerifiableCredential[1].(*string)))
assert.NoError(tt, err)
assert.NotEmpty(tt, vcJWTToken)
assert.NotEmpty(tt, asVCJWT)
Expand Down
10 changes: 6 additions & 4 deletions credential/exchange/verification.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"strings"

"github.com/TBD54566975/ssi-sdk/credential/integrity"
"github.com/TBD54566975/ssi-sdk/credential/parsing"
"github.com/TBD54566975/ssi-sdk/crypto/jwx"
"github.com/TBD54566975/ssi-sdk/did/resolution"
"github.com/TBD54566975/ssi-sdk/schema"
Expand Down Expand Up @@ -56,7 +58,7 @@ func VerifyPresentationSubmission(ctx context.Context, verifier any, resolver re
return nil, fmt.Errorf("verifier<%T> is not a JWT verifier", verifier)
}
// verify the VP, which in turn verifies all credentials in it
_, _, vp, err := credential.VerifyVerifiablePresentationJWT(ctx, jwtVerifier, resolver, string(submission))
_, _, vp, err := integrity.VerifyVerifiablePresentationJWT(ctx, jwtVerifier, resolver, string(submission))
if err != nil {
return nil, errors.Wrap(err, "verification of the presentation submission failed")
}
Expand Down Expand Up @@ -134,8 +136,8 @@ func VerifyPresentationSubmissionVP(def PresentationDefinition, vp credential.Ve
submissionDescriptor.ID, submissionDescriptor.Path)
}

// TODO(gabe) add in signature verification of claims here https://github.com/TBD54566975/ssi-sdk/issues/71
_, _, cred, err := credential.ToCredential(claim)
// get the credential from the claim
_, _, cred, err := parsing.ToCredential(claim)
if err != nil {
return nil, errors.Wrapf(err, "getting claim as json: <%s>", claim)
}
Expand All @@ -150,7 +152,7 @@ func VerifyPresentationSubmissionVP(def PresentationDefinition, vp credential.Ve

// TODO(gabe) consider enforcing limited disclosure if present
// for each field we need to verify at least one path matches
credJSON, err := credential.ToCredentialJSONMap(claim)
credJSON, err := parsing.ToCredentialJSONMap(claim)
if err != nil {
return nil, errors.Wrapf(err, "getting credential as json: %v", cred)
}
Expand Down
7 changes: 4 additions & 3 deletions credential/exchange/verification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"testing"

"github.com/TBD54566975/ssi-sdk/credential/integrity"
"github.com/TBD54566975/ssi-sdk/cryptosuite/jws2020"
"github.com/stretchr/testify/assert"

Expand Down Expand Up @@ -138,7 +139,7 @@ func TestVerifyPresentationSubmission(t *testing.T) {

signer, verifier := getJWKSignerVerifier(tt)
testVC := getTestVerifiableCredential(signer.ID, signer.ID)
credJWT, err := credential.SignVerifiableCredentialJWT(*signer, testVC)
credJWT, err := integrity.SignVerifiableCredentialJWT(*signer, testVC)
assert.NoError(tt, err)
presentationClaim := PresentationClaim{
Token: util.StringPtr(string(credJWT)),
Expand Down Expand Up @@ -186,7 +187,7 @@ func TestVerifyPresentationSubmissionVP(t *testing.T) {
assert.NoError(tt, err)
assert.NotEmpty(tt, submissionBytes)

_, _, verifiablePresentation, err := credential.ParseVerifiablePresentationFromJWT(string(submissionBytes))
_, _, verifiablePresentation, err := integrity.ParseVerifiablePresentationFromJWT(string(submissionBytes))
assert.NoError(tt, err)

_, err = VerifyPresentationSubmissionVP(def, *verifiablePresentation)
Expand Down Expand Up @@ -523,7 +524,7 @@ func TestVerifyPresentationSubmissionVP(t *testing.T) {
}
signer, _ := getJWKSignerVerifier(tt)
testVC := getTestVerifiableCredential("test-issuer", "test-subject")
vcData, err := credential.SignVerifiableCredentialJWT(*signer, testVC)
vcData, err := integrity.SignVerifiableCredentialJWT(*signer, testVC)
assert.NoError(tt, err)
b := NewPresentationSubmissionBuilder(def.ID)
assert.NoError(tt, b.SetDescriptorMap([]SubmissionDescriptor{
Expand Down
11 changes: 6 additions & 5 deletions credential/jws.go → credential/integrity/jws.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package credential
package integrity

import (
"github.com/TBD54566975/ssi-sdk/credential"
"github.com/TBD54566975/ssi-sdk/crypto/jwx"
"github.com/goccy/go-json"
"github.com/lestrrat-go/jwx/v2/jwa"
Expand All @@ -14,7 +15,7 @@ const (

// SignVerifiableCredentialJWS is prepared according to https://transmute-industries.github.io/vc-jws/.
// This is currently an experimental. It's unstable and subject to change. Use at your own peril.
func SignVerifiableCredentialJWS(signer jwx.Signer, cred VerifiableCredential) ([]byte, error) {
func SignVerifiableCredentialJWS(signer jwx.Signer, cred credential.VerifiableCredential) ([]byte, error) {
payload, err := json.Marshal(cred)
if err != nil {
return nil, errors.Wrap(err, "marshalling credential")
Expand All @@ -38,7 +39,7 @@ func SignVerifiableCredentialJWS(signer jwx.Signer, cred VerifiableCredential) (
// ParseVerifiableCredentialFromJWS parses a JWS. Depending on the `cty` header value, it parses as a JWT or simply
// decodes the payload.
// This is currently an experimental. It's unstable and subject to change. Use at your own peril.
func ParseVerifiableCredentialFromJWS(token string) (*jws.Message, *VerifiableCredential, error) {
func ParseVerifiableCredentialFromJWS(token string) (*jws.Message, *credential.VerifiableCredential, error) {
parsed, err := jws.Parse([]byte(token))
if err != nil {
return nil, nil, errors.Wrap(err, "parsing JWS")
Expand All @@ -55,7 +56,7 @@ func ParseVerifiableCredentialFromJWS(token string) (*jws.Message, *VerifiableCr
return parsed, cred, err
}

var cred VerifiableCredential
var cred credential.VerifiableCredential
if err = json.Unmarshal(parsed.Payload(), &cred); err != nil {
return nil, nil, errors.Wrap(err, "reconstructing Verifiable Credential")
}
Expand All @@ -66,7 +67,7 @@ func ParseVerifiableCredentialFromJWS(token string) (*jws.Message, *VerifiableCr
// VerifyVerifiableCredentialJWS verifies the signature validity on the token and parses
// the token in a verifiable credential.
// This is currently an experimental. It's unstable and subject to change. Use at your own peril.
func VerifyVerifiableCredentialJWS(verifier jwx.Verifier, token string) (*jws.Message, *VerifiableCredential, error) {
func VerifyVerifiableCredentialJWS(verifier jwx.Verifier, token string) (*jws.Message, *credential.VerifiableCredential, error) {
if err := verifier.VerifyJWS(token); err != nil {
return nil, nil, errors.Wrap(err, "verifying JWS")
}
Expand Down
5 changes: 3 additions & 2 deletions credential/jws_test.go → credential/integrity/jws_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package credential
package integrity

import (
"context"
"testing"

"github.com/TBD54566975/ssi-sdk/credential"
"github.com/lestrrat-go/jwx/v2/jws"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/stretchr/testify/assert"
)

func TestVerifiableCredentialJWS(t *testing.T) {
testCredential := VerifiableCredential{
testCredential := credential.VerifiableCredential{
Context: []any{"https://www.w3.org/2018/credentials/v1", "https://w3id.org/security/suites/jws-2020/v1"},
Type: []any{"VerifiableCredential"},
Issuer: "did:example:123",
Expand Down
27 changes: 14 additions & 13 deletions credential/jwt.go → credential/integrity/jwt.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package credential
package integrity

import (
"context"
"fmt"
"time"

"github.com/TBD54566975/ssi-sdk/credential"
"github.com/TBD54566975/ssi-sdk/crypto/jwx"
"github.com/TBD54566975/ssi-sdk/did/resolution"

Expand All @@ -24,7 +25,7 @@ const (

// SignVerifiableCredentialJWT is prepared according to https://w3c.github.io/vc-jwt/#version-1.1
// which will soon be deprecated by https://w3c.github.io/vc-jwt/ see: https://github.com/TBD54566975/ssi-sdk/issues/191
func SignVerifiableCredentialJWT(signer jwx.Signer, cred VerifiableCredential) ([]byte, error) {
func SignVerifiableCredentialJWT(signer jwx.Signer, cred credential.VerifiableCredential) ([]byte, error) {
if cred.IsEmpty() {
return nil, errors.New("credential cannot be empty")
}
Expand Down Expand Up @@ -98,9 +99,9 @@ func SignVerifiableCredentialJWT(signer jwx.Signer, cred VerifiableCredential) (

// VerifyVerifiableCredentialJWT verifies the signature validity on the token and parses
// the token in a verifiable credential.
// TODO(gabe) modify this to add additional verification steps such as credential status, expiration, etc.
// TODO(gabe) modify this to add additional validation steps such as credential status, expiration, etc.
// related to https://github.com/TBD54566975/ssi-service/issues/122
func VerifyVerifiableCredentialJWT(verifier jwx.Verifier, token string) (jws.Headers, jwt.Token, *VerifiableCredential, error) {
func VerifyVerifiableCredentialJWT(verifier jwx.Verifier, token string) (jws.Headers, jwt.Token, *credential.VerifiableCredential, error) {
if err := verifier.Verify(token); err != nil {
return nil, nil, nil, errors.Wrap(err, "verifying JWT")
}
Expand All @@ -111,7 +112,7 @@ func VerifyVerifiableCredentialJWT(verifier jwx.Verifier, token string) (jws.Hea
// https://www.w3.org/TR/vc-data-model/#jwt-decoding
// If there are any issues during decoding, an error is returned. As a result, a successfully
// decoded VerifiableCredential object is returned.
func ParseVerifiableCredentialFromJWT(token string) (jws.Headers, jwt.Token, *VerifiableCredential, error) {
func ParseVerifiableCredentialFromJWT(token string) (jws.Headers, jwt.Token, *credential.VerifiableCredential, error) {
parsed, err := jwt.Parse([]byte(token), jwt.WithValidate(false), jwt.WithVerify(false))
if err != nil {
return nil, nil, nil, errors.Wrap(err, "parsing credential token")
Expand All @@ -133,7 +134,7 @@ func ParseVerifiableCredentialFromJWT(token string) (jws.Headers, jwt.Token, *Ve
}

// ParseVerifiableCredentialFromToken takes a JWT object and parses it into a VerifiableCredential
func ParseVerifiableCredentialFromToken(token jwt.Token) (*VerifiableCredential, error) {
func ParseVerifiableCredentialFromToken(token jwt.Token) (*credential.VerifiableCredential, error) {
// parse remaining JWT properties and set in the credential
vcClaim, ok := token.Get(VCJWTProperty)
if !ok {
Expand All @@ -143,7 +144,7 @@ func ParseVerifiableCredentialFromToken(token jwt.Token) (*VerifiableCredential,
if err != nil {
return nil, errors.Wrap(err, "marshalling credential claim")
}
var cred VerifiableCredential
var cred credential.VerifiableCredential
if err = json.Unmarshal(vcBytes, &cred); err != nil {
return nil, errors.Wrap(err, "reconstructing Verifiable Credential")
}
Expand Down Expand Up @@ -179,7 +180,7 @@ func ParseVerifiableCredentialFromToken(token jwt.Token) (*VerifiableCredential,
if cred.CredentialSubject == nil {
cred.CredentialSubject = make(map[string]any)
}
cred.CredentialSubject[VerifiableCredentialIDProperty] = subStr
cred.CredentialSubject[credential.VerifiableCredentialIDProperty] = subStr
}

return &cred, nil
Expand All @@ -195,7 +196,7 @@ 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) {
func SignVerifiablePresentationJWT(signer jwx.Signer, parameters JWTVVPParameters, presentation credential.VerifiablePresentation) ([]byte, error) {
if presentation.IsEmpty() {
return nil, errors.New("presentation cannot be empty")
}
Expand Down Expand Up @@ -270,7 +271,7 @@ func SignVerifiablePresentationJWT(signer jwx.Signer, parameters JWTVVPParameter
// After decoding the signature of each credential in the presentation is verified. If there are any issues during
// decoding or signature validation, an error is returned. As a result, a successfully decoded VerifiablePresentation
// object is returned.
func VerifyVerifiablePresentationJWT(ctx context.Context, verifier jwx.Verifier, r resolution.Resolver, token string) (jws.Headers, jwt.Token, *VerifiablePresentation, error) {
func VerifyVerifiablePresentationJWT(ctx context.Context, verifier jwx.Verifier, r resolution.Resolver, token string) (jws.Headers, jwt.Token, *credential.VerifiablePresentation, error) {
if r == nil {
return nil, nil, nil, errors.New("r cannot be empty")
}
Expand Down Expand Up @@ -306,7 +307,7 @@ func VerifyVerifiablePresentationJWT(ctx context.Context, verifier jwx.Verifier,
return nil, nil, nil, errors.Wrapf(err, "verifying credential %d", i)
}
if !verified {
return nil, nil, nil, errors.Errorf("credential %d failed signature verification", i)
return nil, nil, nil, errors.Errorf("credential %d failed signature validation", i)
}
}

Expand All @@ -318,7 +319,7 @@ func VerifyVerifiablePresentationJWT(ctx context.Context, verifier jwx.Verifier,
// https://www.w3.org/TR/vc-data-model/#jwt-decoding
// If there are any issues during decoding, an error is returned. As a result, a successfully
// decoded VerifiablePresentation object is returned.
func ParseVerifiablePresentationFromJWT(token string) (jws.Headers, jwt.Token, *VerifiablePresentation, error) {
func ParseVerifiablePresentationFromJWT(token string) (jws.Headers, jwt.Token, *credential.VerifiablePresentation, error) {
parsed, err := jwt.Parse([]byte(token), jwt.WithValidate(false), jwt.WithVerify(false))
if err != nil {
return nil, nil, nil, errors.Wrap(err, "parsing vp token")
Expand All @@ -331,7 +332,7 @@ func ParseVerifiablePresentationFromJWT(token string) (jws.Headers, jwt.Token, *
if err != nil {
return nil, nil, nil, errors.Wrap(err, "could not marshalling vp claim")
}
var pres VerifiablePresentation
var pres credential.VerifiablePresentation
if err = json.Unmarshal(vpBytes, &pres); err != nil {
return nil, nil, nil, errors.Wrap(err, "reconstructing Verifiable Presentation")
}
Expand Down
Loading

0 comments on commit effaa78

Please sign in to comment.