Skip to content

Commit

Permalink
feat: Unit test for SD-JWT Flow (Issuer, Holder, Verifier)
Browse files Browse the repository at this point in the history
Unit test for SD-JWT Flow

Closes hyperledger-archives#3461

Signed-off-by: Sandra Vrtikapa <[email protected]>
  • Loading branch information
sandrask committed Jan 5, 2023
1 parent aae6bb8 commit 0406dc2
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 17 deletions.
28 changes: 24 additions & 4 deletions pkg/doc/sdjwt/holder/holder.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import (

const notFound = -1

// Claim defines claim.
type Claim struct {
Name string
Value interface{}
}

// jwtParseOpts holds options for the SD-JWT parsing.
type parseOpts struct {
detachedPayload []byte
Expand All @@ -39,8 +45,8 @@ func WithSignatureVerifier(signatureVerifier jose.SignatureVerifier) ParseOpt {
}
}

// Parse parses input JWT in serialized form into JSON Web Token.
func Parse(sdJWTSerialized string, opts ...ParseOpt) (*common.SDJWT, error) {
// Parse parses issuer SD-JWT and returns claims that can be selected.
func Parse(sdJWTSerialized string, opts ...ParseOpt) ([]*Claim, error) {
pOpts := &parseOpts{
sigVerifier: &NoopSignatureVerifier{},
}
Expand All @@ -66,10 +72,24 @@ func Parse(sdJWTSerialized string, opts ...ParseOpt) (*common.SDJWT, error) {
return nil, err
}

return sdJWT, nil
disclosures, err := common.GetDisclosureClaims(sdJWT.Disclosures)
if err != nil {
return nil, err
}

var claims []*Claim
for _, disclosure := range disclosures {
claims = append(claims,
&Claim{
Name: disclosure.Name,
Value: disclosure.Value,
})
}

return claims, nil
}

// DiscloseClaims discloses selected claims with specified claim names.
// DiscloseClaims discloses claims with specified claim names.
func DiscloseClaims(sdJWTSerialized string, claimNames []string) (string, error) {
sdJWT := common.ParseSDJWT(sdJWTSerialized)

Expand Down
29 changes: 16 additions & 13 deletions pkg/doc/sdjwt/holder/holder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,30 +45,33 @@ func TestParse(t *testing.T) {
r.NoError(e)

t.Run("success", func(t *testing.T) {
sdJWT, err := Parse(sdJWTSerialized, WithSignatureVerifier(verifier))
claims, err := Parse(sdJWTSerialized, WithSignatureVerifier(verifier))
r.NoError(err)
require.NotNil(t, sdJWT)
require.Equal(t, 1, len(sdJWT.Disclosures))
r.NotNil(claims)
r.Equal(1, len(claims))
r.Equal("given_name", claims[0].Name)
r.Equal("Albert", claims[0].Value)
})

t.Run("success - default is no signature verifier", func(t *testing.T) {
sdJWT, err := Parse(sdJWTSerialized)
claims, err := Parse(sdJWTSerialized)
r.NoError(err)
require.NotNil(t, sdJWT)
require.Equal(t, 1, len(sdJWT.Disclosures))
r.Equal(1, len(claims))
r.Equal("given_name", claims[0].Name)
r.Equal("Albert", claims[0].Value)
})

t.Run("success - spec SD-JWT", func(t *testing.T) {
sdJWT, err := Parse(specSDJWT, WithSignatureVerifier(&NoopSignatureVerifier{}))
claims, err := Parse(specSDJWT, WithSignatureVerifier(&NoopSignatureVerifier{}))
r.NoError(err)
require.NotNil(t, sdJWT)
require.Equal(t, 7, len(sdJWT.Disclosures))
require.NotNil(t, claims)
require.Equal(t, 7, len(claims))
})

t.Run("error - additional disclosure", func(t *testing.T) {
sdJWT, err := Parse(fmt.Sprintf("%s~%s", sdJWTSerialized, additionalDisclosure), WithSignatureVerifier(verifier))
claims, err := Parse(fmt.Sprintf("%s~%s", sdJWTSerialized, additionalDisclosure), WithSignatureVerifier(verifier))
r.Error(err)
r.Nil(sdJWT)
r.Nil(claims)
r.Contains(err.Error(),
"disclosure digest 'qqvcqnczAMgYx7EykI6wwtspyvyvK790ge7MBbQ-Nus' not found in SD-JWT disclosure digests")
})
Expand All @@ -91,10 +94,10 @@ func TestParse(t *testing.T) {
sdJWTSerialized, err := buildJWS(signer, "not JSON")
r.NoError(err)

sdJWT, err := Parse(sdJWTSerialized, WithSignatureVerifier(verifier))
claims, err := Parse(sdJWTSerialized, WithSignatureVerifier(verifier))
r.Error(err)
r.Nil(claims)
r.Contains(err.Error(), "read JWT claims from JWS payload")
r.Nil(sdJWT)
})
}

Expand Down
75 changes: 75 additions & 0 deletions pkg/doc/sdjwt/integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package holder

import (
"crypto/ed25519"
"crypto/rand"
"fmt"
"testing"

"github.com/stretchr/testify/require"

afjwt "github.com/hyperledger/aries-framework-go/pkg/doc/jwt"
"github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/holder"
"github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/issuer"
"github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/verifier"
)

const (
testIssuer = "https://example.com/issuer"
)

func TestSDJWTFlow(t *testing.T) {
r := require.New(t)

pubKey, privKey, e := ed25519.GenerateKey(rand.Reader)
r.NoError(e)

signer := afjwt.NewEd25519Signer(privKey)
claims := map[string]interface{}{
"given_name": "Albert",
"last_name": "Smith",
}

signatureVerifier, e := afjwt.NewEd25519Verifier(pubKey)
r.NoError(e)

t.Run("success", func(t *testing.T) {
// Issuer will issue SD-JWT for specified claims.
token, e := issuer.New(testIssuer, claims, nil, signer)
r.NoError(e)

// TODO: Should we have one call instead of two (designed based on JWT)
sdJWTSerialized, e := token.Serialize(false)
r.NoError(e)

fmt.Println(fmt.Sprintf("issuer SD-JWT: %s", sdJWTSerialized))

// Holder will parse issuer SD-JWT and hold on to that SD-JWT and the claims that can be selected.
claims, err := holder.Parse(sdJWTSerialized, holder.WithSignatureVerifier(signatureVerifier))
r.NoError(err)

// expected disclosures given_name and last_name
r.Equal(2, len(claims))

// Holder will disclose only sub-set of claims to verifier.
sdJWTDisclosed, err := holder.DiscloseClaims(sdJWTSerialized, []string{"given_name"})
r.NoError(err)

fmt.Println(fmt.Sprintf("holder SD-JWT: %s", sdJWTDisclosed))

// Verifier will validate holder SD-JWT and create verified claims.
verifiedClaims, err := verifier.Parse(sdJWTDisclosed, verifier.WithSignatureVerifier(signatureVerifier))
r.NoError(err)

// expected claims iss, exp, iat, nbf, given_name; last_name was not disclosed
r.Equal(5, len(verifiedClaims))

fmt.Println(fmt.Sprintf("verified claims: %+v", verifiedClaims))
})
}

0 comments on commit 0406dc2

Please sign in to comment.