Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

Commit

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

Closes #3461

Signed-off-by: Sandra Vrtikapa <[email protected]>

Signed-off-by: Sandra Vrtikapa <[email protected]>
  • Loading branch information
sandrask authored Jan 5, 2023
1 parent aae6bb8 commit 305117c
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 17 deletions.
32 changes: 28 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,28 @@ func Parse(sdJWTSerialized string, opts ...ParseOpt) (*common.SDJWT, error) {
return nil, err
}

return sdJWT, nil
return getClaims(sdJWT.Disclosures)
}

func getClaims(disclosures []string) ([]*Claim, error) {
disclosureClaims, err := common.GetDisclosureClaims(disclosures)
if err != nil {
return nil, fmt.Errorf("failed to get claims from disclosures: %w", err)
}

var claims []*Claim
for _, disclosure := range disclosureClaims {
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
46 changes: 33 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 Expand Up @@ -136,6 +139,23 @@ func TestDiscloseClaims(t *testing.T) {
})
}

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

t.Run("success", func(t *testing.T) {
claims, err := getClaims([]string{additionalDisclosure})
r.NoError(err)
r.Len(claims, 1)
})

t.Run("error - not base64 encoded ", func(t *testing.T) {
claims, err := getClaims([]string{"!!!"})
r.Error(err)
r.Nil(claims)
r.Contains(err.Error(), "failed to decode disclosure")
})
}

func TestWithJWTDetachedPayload(t *testing.T) {
detachedPayloadOpt := WithJWTDetachedPayload([]byte("payload"))
require.NotNil(t, detachedPayloadOpt)
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 sdjwt

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 305117c

Please sign in to comment.