From 0c7b70220f7aa901ae68457881843bf68988bb93 Mon Sep 17 00:00:00 2001 From: Sandra Vrtikapa Date: Thu, 5 Jan 2023 16:19:17 -0500 Subject: [PATCH] feat: Unit test for SD-JWT Flow (Issuer, Holder, Verifier) Unit test for SD-JWT Flow Closes #3461 Signed-off-by: Sandra Vrtikapa --- pkg/doc/sdjwt/holder/holder.go | 26 ++++++++-- pkg/doc/sdjwt/holder/holder_test.go | 29 ++++++----- pkg/doc/sdjwt/integration_test.go | 75 +++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 16 deletions(-) create mode 100644 pkg/doc/sdjwt/integration_test.go diff --git a/pkg/doc/sdjwt/holder/holder.go b/pkg/doc/sdjwt/holder/holder.go index f812b42450..3f0627cbbc 100644 --- a/pkg/doc/sdjwt/holder/holder.go +++ b/pkg/doc/sdjwt/holder/holder.go @@ -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 @@ -40,7 +46,7 @@ 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) { +func Parse(sdJWTSerialized string, opts ...ParseOpt) ([]*Claim, error) { pOpts := &parseOpts{ sigVerifier: &NoopSignatureVerifier{}, } @@ -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) diff --git a/pkg/doc/sdjwt/holder/holder_test.go b/pkg/doc/sdjwt/holder/holder_test.go index 3f1f46f683..df1f236002 100644 --- a/pkg/doc/sdjwt/holder/holder_test.go +++ b/pkg/doc/sdjwt/holder/holder_test.go @@ -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") }) @@ -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) }) } diff --git a/pkg/doc/sdjwt/integration_test.go b/pkg/doc/sdjwt/integration_test.go new file mode 100644 index 0000000000..40c8e88698 --- /dev/null +++ b/pkg/doc/sdjwt/integration_test.go @@ -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)) + }) +}