github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/sdjwt/holder/holder_test.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package holder
     8  
     9  import (
    10  	"crypto/ed25519"
    11  	"crypto/rand"
    12  	"encoding/base64"
    13  	"encoding/json"
    14  	"fmt"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/go-jose/go-jose/v3/jwt"
    20  	"github.com/stretchr/testify/require"
    21  
    22  	"github.com/hyperledger/aries-framework-go/pkg/doc/jose"
    23  	afjwt "github.com/hyperledger/aries-framework-go/pkg/doc/jwt"
    24  	"github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/common"
    25  	"github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/issuer"
    26  )
    27  
    28  const (
    29  	testIssuer = "https://example.com/issuer"
    30  )
    31  
    32  func TestParse(t *testing.T) {
    33  	r := require.New(t)
    34  
    35  	pubKey, privKey, e := ed25519.GenerateKey(rand.Reader)
    36  	r.NoError(e)
    37  
    38  	signer := afjwt.NewEd25519Signer(privKey)
    39  	claims := map[string]interface{}{"given_name": "Albert"}
    40  
    41  	token, e := issuer.New(testIssuer, claims, nil, signer)
    42  	r.NoError(e)
    43  	combinedFormatForIssuance, e := token.Serialize(false)
    44  	r.NoError(e)
    45  
    46  	verifier, e := afjwt.NewEd25519Verifier(pubKey)
    47  	r.NoError(e)
    48  
    49  	t.Run("success", func(t *testing.T) {
    50  		claims, err := Parse(combinedFormatForIssuance, WithSignatureVerifier(verifier))
    51  		r.NoError(err)
    52  		r.NotNil(claims)
    53  		r.Equal(1, len(claims))
    54  		r.Equal("given_name", claims[0].Name)
    55  		r.Equal("Albert", claims[0].Value)
    56  	})
    57  
    58  	t.Run("success - default is no signature verifier", func(t *testing.T) {
    59  		claims, err := Parse(combinedFormatForIssuance)
    60  		r.NoError(err)
    61  		r.Equal(1, len(claims))
    62  		r.Equal("given_name", claims[0].Name)
    63  		r.Equal("Albert", claims[0].Value)
    64  	})
    65  
    66  	t.Run("success - spec SD-JWT", func(t *testing.T) {
    67  		claims, err := Parse(specSDJWT, WithSignatureVerifier(&NoopSignatureVerifier{}))
    68  		r.NoError(err)
    69  		require.NotNil(t, claims)
    70  		require.Equal(t, 7, len(claims))
    71  	})
    72  
    73  	t.Run("success - VC example", func(t *testing.T) {
    74  		claims, err := Parse(vcCombinedFormatForIssuance, WithSignatureVerifier(&NoopSignatureVerifier{}))
    75  		r.NoError(err)
    76  		require.NotNil(t, claims)
    77  		require.Equal(t, 4, len(claims))
    78  	})
    79  
    80  	t.Run("success - complex claims", func(t *testing.T) {
    81  		complexClaims := createComplexClaims()
    82  
    83  		token, e := issuer.New(testIssuer, complexClaims, nil, signer,
    84  			issuer.WithStructuredClaims(true))
    85  		r.NoError(e)
    86  		cfi, e := token.Serialize(false)
    87  		r.NoError(e)
    88  
    89  		claims, err := Parse(cfi, WithSignatureVerifier(verifier))
    90  		r.NoError(err)
    91  		r.NotNil(claims)
    92  		r.Equal(10, len(claims))
    93  	})
    94  
    95  	t.Run("error - additional disclosure", func(t *testing.T) {
    96  		claims, err := Parse(fmt.Sprintf("%s~%s", combinedFormatForIssuance, additionalDisclosure),
    97  			WithSignatureVerifier(verifier))
    98  		r.Error(err)
    99  		r.Nil(claims)
   100  		r.Contains(err.Error(),
   101  			"disclosure digest 'qqvcqnczAMgYx7EykI6wwtspyvyvK790ge7MBbQ-Nus' not found in SD-JWT disclosure digests")
   102  	})
   103  
   104  	t.Run("success - with detached payload", func(t *testing.T) {
   105  		jwsParts := strings.Split(combinedFormatForIssuance, ".")
   106  		jwsDetached := fmt.Sprintf("%s..%s", jwsParts[0], jwsParts[2])
   107  
   108  		jwsPayload, err := base64.RawURLEncoding.DecodeString(jwsParts[1])
   109  		require.NoError(t, err)
   110  
   111  		sdJWT, err := Parse(jwsDetached,
   112  			WithSignatureVerifier(verifier), WithJWTDetachedPayload(jwsPayload))
   113  		r.NoError(err)
   114  		r.NotNil(r, sdJWT)
   115  	})
   116  
   117  	t.Run("error - invalid claims", func(t *testing.T) {
   118  		// claims is not JSON
   119  		sdJWTSerialized, err := buildJWS(signer, "not JSON")
   120  		r.NoError(err)
   121  
   122  		claims, err := Parse(sdJWTSerialized, WithSignatureVerifier(verifier))
   123  		r.Error(err)
   124  		r.Nil(claims)
   125  		r.Contains(err.Error(), "read JWT claims from JWS payload")
   126  	})
   127  }
   128  
   129  func TestDiscloseClaims(t *testing.T) {
   130  	r := require.New(t)
   131  
   132  	_, privKey, e := ed25519.GenerateKey(rand.Reader)
   133  	r.NoError(e)
   134  
   135  	signer := afjwt.NewEd25519Signer(privKey)
   136  	claims := map[string]interface{}{"given_name": "Albert"}
   137  
   138  	token, e := issuer.New(testIssuer, claims, nil, signer)
   139  	r.NoError(e)
   140  	combinedFormatForIssuance, e := token.Serialize(false)
   141  	r.NoError(e)
   142  
   143  	cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance)
   144  
   145  	claimsToDisclose := []string{cfi.Disclosures[0]}
   146  
   147  	t.Run("success", func(t *testing.T) {
   148  		combinedFormatForPresentation, err := CreatePresentation(combinedFormatForIssuance, claimsToDisclose)
   149  		r.NoError(err)
   150  		require.NotNil(t, combinedFormatForPresentation)
   151  		require.Equal(t, combinedFormatForIssuance+common.CombinedFormatSeparator, combinedFormatForPresentation)
   152  	})
   153  
   154  	t.Run("success - with holder binding", func(t *testing.T) {
   155  		_, holderPrivKey, e := ed25519.GenerateKey(rand.Reader)
   156  		r.NoError(e)
   157  
   158  		holderSigner := afjwt.NewEd25519Signer(holderPrivKey)
   159  
   160  		combinedFormatForPresentation, err := CreatePresentation(combinedFormatForIssuance, claimsToDisclose,
   161  			WithHolderBinding(&BindingInfo{
   162  				Payload: BindingPayload{
   163  					Audience: "https://example.com/verifier",
   164  					Nonce:    "nonce",
   165  					IssuedAt: jwt.NewNumericDate(time.Now()),
   166  				},
   167  				Signer: holderSigner,
   168  			}))
   169  		r.NoError(err)
   170  		r.NotEmpty(combinedFormatForPresentation)
   171  		r.Contains(combinedFormatForPresentation, combinedFormatForIssuance+common.CombinedFormatSeparator)
   172  	})
   173  
   174  	t.Run("error - failed to create holder binding due to signing error", func(t *testing.T) {
   175  		combinedFormatForPresentation, err := CreatePresentation(combinedFormatForIssuance, claimsToDisclose,
   176  			WithHolderBinding(&BindingInfo{
   177  				Payload: BindingPayload{},
   178  				Signer:  &mockSigner{Err: fmt.Errorf("signing error")},
   179  			}))
   180  
   181  		r.Error(err)
   182  		r.Empty(combinedFormatForPresentation)
   183  
   184  		r.Contains(err.Error(),
   185  			"failed to create holder binding: create JWS: sign JWS: sign JWS verification data: signing error")
   186  	})
   187  
   188  	t.Run("error - no disclosure(s)", func(t *testing.T) {
   189  		cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance)
   190  
   191  		combinedFormatForPresentation, err := CreatePresentation(cfi.SDJWT, claimsToDisclose)
   192  		r.Error(err)
   193  		r.Empty(combinedFormatForPresentation)
   194  		r.Contains(err.Error(), "no disclosures found in SD-JWT")
   195  	})
   196  
   197  	t.Run("error - disclosure not found", func(t *testing.T) {
   198  		combinedFormatForPresentation, err := CreatePresentation(combinedFormatForIssuance,
   199  			[]string{"non_existent"})
   200  		r.Error(err)
   201  		r.Empty(combinedFormatForPresentation)
   202  		r.Contains(err.Error(), "disclosure 'non_existent' not found")
   203  	})
   204  }
   205  
   206  func TestGetClaims(t *testing.T) {
   207  	r := require.New(t)
   208  
   209  	t.Run("success", func(t *testing.T) {
   210  		claims, err := getClaims([]string{additionalDisclosure})
   211  		r.NoError(err)
   212  		r.Len(claims, 1)
   213  	})
   214  
   215  	t.Run("error - not base64 encoded ", func(t *testing.T) {
   216  		claims, err := getClaims([]string{"!!!"})
   217  		r.Error(err)
   218  		r.Nil(claims)
   219  		r.Contains(err.Error(), "failed to decode disclosure")
   220  	})
   221  }
   222  
   223  func TestWithJWTDetachedPayload(t *testing.T) {
   224  	detachedPayloadOpt := WithJWTDetachedPayload([]byte("payload"))
   225  	require.NotNil(t, detachedPayloadOpt)
   226  
   227  	opts := &parseOpts{}
   228  	detachedPayloadOpt(opts)
   229  	require.Equal(t, []byte("payload"), opts.detachedPayload)
   230  }
   231  
   232  func buildJWS(signer jose.Signer, claims interface{}) (string, error) {
   233  	claimsBytes, err := json.Marshal(claims)
   234  	if err != nil {
   235  		return "", err
   236  	}
   237  
   238  	jws, err := jose.NewJWS(nil, nil, claimsBytes, signer)
   239  	if err != nil {
   240  		return "", err
   241  	}
   242  
   243  	return jws.SerializeCompact(false)
   244  }
   245  
   246  // Signer defines JWS Signer interface. It makes signing of data and provides custom JWS headers relevant to the signer.
   247  type mockSigner struct {
   248  	Err error
   249  }
   250  
   251  // Sign signs.
   252  func (m *mockSigner) Sign(_ []byte) ([]byte, error) {
   253  	if m.Err != nil {
   254  		return nil, m.Err
   255  	}
   256  
   257  	return nil, nil
   258  }
   259  
   260  // Headers provides JWS headers.
   261  func (m *mockSigner) Headers() jose.Headers {
   262  	headers := make(jose.Headers)
   263  	headers["alg"] = "EdDSA"
   264  
   265  	return headers
   266  }
   267  
   268  func createComplexClaims() map[string]interface{} {
   269  	claims := map[string]interface{}{
   270  		"sub":          "john_doe_42",
   271  		"given_name":   "John",
   272  		"family_name":  "Doe",
   273  		"email":        "johndoe@example.com",
   274  		"phone_number": "+1-202-555-0101",
   275  		"birthdate":    "1940-01-01",
   276  		"address": map[string]interface{}{
   277  			"street_address": "123 Main St",
   278  			"locality":       "Anytown",
   279  			"region":         "Anystate",
   280  			"country":        "US",
   281  		},
   282  	}
   283  
   284  	return claims
   285  }
   286  
   287  // nolint: lll
   288  const additionalDisclosure = `WyIzanFjYjY3ejl3a3MwOHp3aUs3RXlRIiwgImdpdmVuX25hbWUiLCAiSm9obiJd`
   289  
   290  // nolint: lll
   291  const specSDJWT = `eyJhbGciOiAiUlMyNTYiLCAia2lkIjogImNBRUlVcUowY21MekQxa3pHemhlaUJhZzBZUkF6VmRsZnhOMjgwTmdIYUEifQ.eyJfc2QiOiBbIk5ZQ29TUktFWXdYZHBlNXlkdUpYQ3h4aHluRVU4ei1iNFR5TmlhcDc3VVkiLCAiU1k4bjJCYmtYOWxyWTNleEhsU3dQUkZYb0QwOUdGOGE5Q1BPLUc4ajIwOCIsICJUUHNHTlBZQTQ2d21CeGZ2MnpuT0poZmRvTjVZMUdrZXpicGFHWkNUMWFjIiwgIlprU0p4eGVHbHVJZFlCYjdDcWtaYkpWbTB3MlY1VXJSZU5UekFRQ1lCanciLCAibDlxSUo5SlRRd0xHN09MRUlDVEZCVnhtQXJ3OFBqeTY1ZEQ2bXRRVkc1YyIsICJvMVNBc0ozM1lNaW9POXBYNVZlQU0xbHh1SEY2aFpXMmtHZGtLS0JuVmxvIiwgInFxdmNxbmN6QU1nWXg3RXlrSTZ3d3RzcHl2eXZLNzkwZ2U3TUJiUS1OdXMiXSwgImlzcyI6ICJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsICJpYXQiOiAxNTE2MjM5MDIyLCAiZXhwIjogMTUxNjI0NzAyMiwgIl9zZF9hbGciOiAic2hhLTI1NiIsICJjbmYiOiB7Imp3ayI6IHsia3R5IjogIlJTQSIsICJuIjogInBtNGJPSEJnLW9ZaEF5UFd6UjU2QVdYM3JVSVhwMTFfSUNEa0dnUzZXM1pXTHRzLWh6d0kzeDY1NjU5a2c0aFZvOWRiR29DSkUzWkdGX2VhZXRFMzBVaEJVRWdwR3dyRHJRaUo5enFwcm1jRmZyM3F2dmtHanR0aDhaZ2wxZU0yYkpjT3dFN1BDQkhXVEtXWXMxNTJSN2c2SmcyT1ZwaC1hOHJxLXE3OU1oS0c1UW9XX21UejEwUVRfNkg0YzdQaldHMWZqaDhocFdObmJQX3B2NmQxelN3WmZjNWZsNnlWUkwwRFYwVjNsR0hLZTJXcWZfZU5HakJyQkxWa2xEVGs4LXN0WF9NV0xjUi1FR21YQU92MFVCV2l0U19kWEpLSnUtdlhKeXcxNG5IU0d1eFRJSzJoeDFwdHRNZnQ5Q3N2cWltWEtlRFRVMTRxUUwxZUU3aWhjdyIsICJlIjogIkFRQUIifX19.xqgKrDO6dK_oBL3fiqdcq_elaIGxM6Z-RyuysglGyddR1O1IiE3mIk8kCpoqcRLR88opkVWN2392K_XYfAuAmeT9kJVisD8ZcgNcv-MQlWW9s8WaViXxBRe7EZWkWRQcQVR6jf95XZ5H2-_KA54POq3L42xjk0y5vDr8yc08Reak6vvJVvjXpp-Wk6uxsdEEAKFspt_EYIvISFJhfTuQqyhCjnaW13X312MSQBPwjbHn74ylUqVLljDvqcemxeqjh42KWJq4C3RqNJ7anA2i3FU1kB4-KNZWsijY7-op49iL7BrnIBxdlAMrbHEkoGTbFWdl7Ki17GHtDxxa1jaxQg~WyJkcVR2WE14UzBHYTNEb2FHbmU5eDBRIiwgInN1YiIsICJqb2huX2RvZV80MiJd~WyIzanFjYjY3ejl3a3MwOHp3aUs3RXlRIiwgImdpdmVuX25hbWUiLCAiSm9obiJd~WyJxUVdtakpsMXMxUjRscWhFTkxScnJ3IiwgImZhbWlseV9uYW1lIiwgIkRvZSJd~WyJLVXhTNWhFX1hiVmFjckdBYzdFRnd3IiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ~WyIzcXZWSjFCQURwSERTUzkzOVEtUml3IiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ~WyIweEd6bjNNaXFzY3RaSV9PcERsQWJRIiwgImFkZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0~WyJFUktNMENOZUZKa2FENW1UWFZfWDh3IiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0`
   292  
   293  // nolint: lll
   294  const vcCombinedFormatForIssuance = `eyJhbGciOiJFZERTQSJ9.eyJpYXQiOjEuNjczOTg3NTQ3ZSswOSwiaXNzIjoiZGlkOmV4YW1wbGU6NzZlMTJlYzcxMmViYzZmMWMyMjFlYmZlYjFmIiwianRpIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzE4NzIiLCJuYmYiOjEuNjczOTg3NTQ3ZSswOSwic3ViIjoiZGlkOmV4YW1wbGU6ZWJmZWIxZjcxMmViYzZmMWMyNzZlMTJlYzIxIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiX3NkX2FsZyI6InNoYS0yNTYiLCJjbmYiOnsiandrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoiZDlYemtRbVJMQncxSXpfeHVGUmVLMUItRmpCdTdjT0N3RTlOR2F1d251SSJ9fSwiY3JlZGVudGlhbFN1YmplY3QiOnsiX3NkIjpbInBBdjJUMU10YmRXNGttUUdxT1VVRUpjQmdTZi1mSFRHV2xQVUV4aWlIbVEiLCI2dDlBRUJCQnEzalZwckJ3bGljOGhFWnNNSmxXSXhRdUw5c3ExMzJZTnYwIl0sImRlZ3JlZSI6eyJfc2QiOlsibzZzV2h4RjcxWHBvZ1cxVUxCbU90bjR1SXFGdjJ3ODF6emRuelJXdlpqYyIsIi1yRklXbU1YR3ZXX0FIYVEtODhpMy11ZzRUVjhLUTg5TjdmZmtneFc2X2MiXX0sImlkIjoiZGlkOmV4YW1wbGU6ZWJmZWIxZjcxMmViYzZmMWMyNzZlMTJlYzIxIn0sImZpcnN0X25hbWUiOiJGaXJzdCBuYW1lIiwiaWQiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMTg3MiIsImluZm8iOiJJbmZvIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0wMS0xN1QyMjozMjoyNy40NjgxMDk4MTcrMDI6MDAiLCJpc3N1ZXIiOiJkaWQ6ZXhhbXBsZTo3NmUxMmVjNzEyZWJjNmYxYzIyMWViZmViMWYiLCJsYXN0X25hbWUiOiJMYXN0IG5hbWUiLCJ0eXBlIjoiVmVyaWZpYWJsZUNyZWRlbnRpYWwifX0.GcfSA6NkONxdsm5Lxj9-988eWx1ZvMz5vJ1uh2x8UK1iKIeQLmhsWpA_34RbtAm2HnuoxW4_ZGeiHBzQ1GLTDQ~WyJFWkVDRVZ1YWVJOXhZWmlWb3VMQldBIiwidHlwZSIsIkJhY2hlbG9yRGVncmVlIl0~WyJyMno1UzZMa25FRTR3TWwteFB0VEx3IiwiZGVncmVlIiwiTUlUIl0~WyJ2VkhfaGhNQy1aSUt5WFdtdDUyOWpnIiwic3BvdXNlIiwiZGlkOmV4YW1wbGU6YzI3NmUxMmVjMjFlYmZlYjFmNzEyZWJjNmYxIl0~WyJrVzh0WVVwbVl1VmRoZktFT050TnFnIiwibmFtZSIsIkpheWRlbiBEb2UiXQ`