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

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package common
     8  
     9  import (
    10  	"bytes"
    11  	"crypto"
    12  	"crypto/ed25519"
    13  	"crypto/rand"
    14  	"encoding/base64"
    15  	"encoding/json"
    16  	"fmt"
    17  	"testing"
    18  
    19  	"github.com/stretchr/testify/require"
    20  
    21  	"github.com/hyperledger/aries-framework-go/pkg/common/utils"
    22  	"github.com/hyperledger/aries-framework-go/pkg/doc/jose"
    23  	afjwt "github.com/hyperledger/aries-framework-go/pkg/doc/jwt"
    24  )
    25  
    26  const (
    27  	defaultHash = crypto.SHA256
    28  
    29  	testAlg = "sha-256"
    30  )
    31  
    32  func TestGetHash(t *testing.T) {
    33  	t.Run("success", func(t *testing.T) {
    34  		digest, err := GetHash(defaultHash, "WyI2cU1RdlJMNWhhaiIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0")
    35  		require.NoError(t, err)
    36  		require.Equal(t, "uutlBuYeMDyjLLTpf6Jxi7yNkEF35jdyWMn9U7b_RYY", digest)
    37  	})
    38  
    39  	t.Run("error - hash not available", func(t *testing.T) {
    40  		digest, err := GetHash(0, "test")
    41  		require.Error(t, err)
    42  		require.Empty(t, digest)
    43  		require.Contains(t, err.Error(), "hash function not available for: 0")
    44  	})
    45  }
    46  
    47  func TestParseCombinedFormatForIssuance(t *testing.T) {
    48  	t.Run("success - SD-JWT only", func(t *testing.T) {
    49  		cfi := ParseCombinedFormatForIssuance(testCombinedFormatForIssuance)
    50  		require.Equal(t, testSDJWT, cfi.SDJWT)
    51  		require.Equal(t, 1, len(cfi.Disclosures))
    52  
    53  		require.Equal(t, testCombinedFormatForIssuance, cfi.Serialize())
    54  	})
    55  	t.Run("success - spec example", func(t *testing.T) {
    56  		cfi := ParseCombinedFormatForIssuance(specCombinedFormatForIssuance)
    57  		require.Equal(t, 7, len(cfi.Disclosures))
    58  
    59  		require.Equal(t, specCombinedFormatForIssuance, cfi.Serialize())
    60  	})
    61  	t.Run("success - AFG generated", func(t *testing.T) {
    62  		cfi := ParseCombinedFormatForIssuance(testSDJWT)
    63  		require.Equal(t, testSDJWT, cfi.SDJWT)
    64  		require.Equal(t, 0, len(cfi.Disclosures))
    65  
    66  		require.Equal(t, testSDJWT, cfi.Serialize())
    67  	})
    68  }
    69  
    70  func TestParseCombinedFormatForPresentation(t *testing.T) {
    71  	const testHolderBinding = "holder.binding.jwt"
    72  
    73  	testCombinedFormatForPresentation := testCombinedFormatForIssuance + CombinedFormatSeparator
    74  
    75  	t.Run("success - AFG example", func(t *testing.T) {
    76  		cfp := ParseCombinedFormatForPresentation(testCombinedFormatForPresentation)
    77  		require.Equal(t, testSDJWT, cfp.SDJWT)
    78  		require.Equal(t, 1, len(cfp.Disclosures))
    79  		require.Empty(t, cfp.HolderBinding)
    80  
    81  		require.Equal(t, testCombinedFormatForPresentation, cfp.Serialize())
    82  	})
    83  
    84  	t.Run("success - spec example", func(t *testing.T) {
    85  		cfp := ParseCombinedFormatForPresentation(specCombinedFormatForIssuance + CombinedFormatSeparator)
    86  		require.Equal(t, 7, len(cfp.Disclosures))
    87  		require.Empty(t, cfp.HolderBinding)
    88  
    89  		require.Equal(t, specCombinedFormatForIssuance+CombinedFormatSeparator, cfp.Serialize())
    90  	})
    91  
    92  	t.Run("success - AFG test with holder binding", func(t *testing.T) {
    93  		testCFI := testCombinedFormatForPresentation + testHolderBinding
    94  		cfp := ParseCombinedFormatForPresentation(testCFI)
    95  		require.Equal(t, testSDJWT, cfp.SDJWT)
    96  		require.Equal(t, 1, len(cfp.Disclosures))
    97  		require.Equal(t, testHolderBinding, cfp.HolderBinding)
    98  
    99  		require.Equal(t, testCFI, cfp.Serialize())
   100  	})
   101  
   102  	t.Run("success - SD-JWT only", func(t *testing.T) {
   103  		cfp := ParseCombinedFormatForPresentation(testSDJWT)
   104  		require.Equal(t, testSDJWT, cfp.SDJWT)
   105  		require.Equal(t, 0, len(cfp.Disclosures))
   106  		require.Empty(t, cfp.HolderBinding)
   107  
   108  		require.Equal(t, testSDJWT, cfp.Serialize())
   109  	})
   110  
   111  	t.Run("success - SD-JWT + holder binding", func(t *testing.T) {
   112  		testCFI := testSDJWT + CombinedFormatSeparator + testHolderBinding
   113  
   114  		cfp := ParseCombinedFormatForPresentation(testCFI)
   115  		require.Equal(t, testSDJWT, cfp.SDJWT)
   116  		require.Equal(t, 0, len(cfp.Disclosures))
   117  		require.Equal(t, testHolderBinding, cfp.HolderBinding)
   118  
   119  		require.Equal(t, testCFI, cfp.Serialize())
   120  	})
   121  
   122  	t.Run("success - SD-JWT + multiple disclosures only", func(t *testing.T) {
   123  		specExample2bPresentation := fmt.Sprintf("%s%s", specExample2bJWT, specExample2bDisclosures)
   124  
   125  		cfp := ParseCombinedFormatForPresentation(specExample2bPresentation)
   126  		require.Equal(t, specExample2bJWT, cfp.SDJWT)
   127  		require.Equal(t, 6, len(cfp.Disclosures))
   128  		require.Empty(t, cfp.HolderBinding)
   129  
   130  		require.Equal(t, specExample2bPresentation, cfp.Serialize())
   131  	})
   132  }
   133  
   134  func TestVerifyDisclosuresInSDJWT(t *testing.T) {
   135  	r := require.New(t)
   136  
   137  	_, privKey, err := ed25519.GenerateKey(rand.Reader)
   138  	r.NoError(err)
   139  
   140  	signer := afjwt.NewEd25519Signer(privKey)
   141  
   142  	t.Run("success", func(t *testing.T) {
   143  		sdJWT := ParseCombinedFormatForIssuance(testCombinedFormatForIssuance)
   144  		require.Equal(t, 1, len(sdJWT.Disclosures))
   145  
   146  		signedJWT, _, err := afjwt.Parse(sdJWT.SDJWT, afjwt.WithSignatureVerifier(&NoopSignatureVerifier{}))
   147  		require.NoError(t, err)
   148  
   149  		err = VerifyDisclosuresInSDJWT(sdJWT.Disclosures, signedJWT)
   150  		r.NoError(err)
   151  	})
   152  
   153  	t.Run("success - complex struct(spec example 2b)", func(t *testing.T) {
   154  		specExample2bPresentation := fmt.Sprintf("%s%s", specExample2bJWT, specExample2bDisclosures)
   155  
   156  		sdJWT := ParseCombinedFormatForPresentation(specExample2bPresentation)
   157  
   158  		signedJWT, _, err := afjwt.Parse(sdJWT.SDJWT, afjwt.WithSignatureVerifier(&NoopSignatureVerifier{}))
   159  		require.NoError(t, err)
   160  
   161  		err = VerifyDisclosuresInSDJWT(sdJWT.Disclosures, signedJWT)
   162  		r.NoError(err)
   163  	})
   164  
   165  	t.Run("success - no selective disclosures(valid case)", func(t *testing.T) {
   166  		jwtPayload := &payload{
   167  			Issuer: "issuer",
   168  			SDAlg:  "sha-256",
   169  		}
   170  
   171  		signedJWT, err := afjwt.NewSigned(jwtPayload, nil, signer)
   172  		r.NoError(err)
   173  
   174  		err = VerifyDisclosuresInSDJWT(nil, signedJWT)
   175  		r.NoError(err)
   176  	})
   177  
   178  	t.Run("success - selective disclosures nil", func(t *testing.T) {
   179  		payload := make(map[string]interface{})
   180  		payload[SDAlgorithmKey] = testAlg
   181  		payload[SDKey] = nil
   182  
   183  		signedJWT, err := afjwt.NewSigned(payload, nil, signer)
   184  		r.NoError(err)
   185  
   186  		err = VerifyDisclosuresInSDJWT(nil, signedJWT)
   187  		r.NoError(err)
   188  	})
   189  
   190  	t.Run("error - disclosure not present in SD-JWT", func(t *testing.T) {
   191  		sdJWT := ParseCombinedFormatForIssuance(testCombinedFormatForIssuance)
   192  		require.Equal(t, 1, len(sdJWT.Disclosures))
   193  
   194  		signedJWT, _, err := afjwt.Parse(sdJWT.SDJWT, afjwt.WithSignatureVerifier(&NoopSignatureVerifier{}))
   195  		require.NoError(t, err)
   196  
   197  		err = VerifyDisclosuresInSDJWT(append(sdJWT.Disclosures, additionalDisclosure), signedJWT)
   198  		r.Error(err)
   199  		r.Contains(err.Error(),
   200  			"disclosure digest 'X9yH0Ajrdm1Oij4tWso9UzzKJvPoDxwmuEcO3XAdRC0' not found in SD-JWT disclosure digests")
   201  	})
   202  
   203  	t.Run("error - disclosure not present in SD-JWT without selective disclosures", func(t *testing.T) {
   204  		jwtPayload := &payload{
   205  			Issuer: "issuer",
   206  			SDAlg:  testAlg,
   207  		}
   208  
   209  		signedJWT, err := afjwt.NewSigned(jwtPayload, nil, signer)
   210  		r.NoError(err)
   211  
   212  		err = VerifyDisclosuresInSDJWT([]string{additionalDisclosure}, signedJWT)
   213  		r.Error(err)
   214  		r.Contains(err.Error(),
   215  			"disclosure digest 'X9yH0Ajrdm1Oij4tWso9UzzKJvPoDxwmuEcO3XAdRC0' not found in SD-JWT disclosure digests")
   216  	})
   217  
   218  	t.Run("error - missing algorithm", func(t *testing.T) {
   219  		jwtPayload := &payload{
   220  			Issuer: "issuer",
   221  		}
   222  
   223  		signedJWT, err := afjwt.NewSigned(jwtPayload, nil, signer)
   224  		r.NoError(err)
   225  
   226  		err = VerifyDisclosuresInSDJWT(nil, signedJWT)
   227  		r.Error(err)
   228  		r.Contains(err.Error(), "_sd_alg must be present in SD-JWT", SDAlgorithmKey)
   229  	})
   230  
   231  	t.Run("error - invalid algorithm", func(t *testing.T) {
   232  		jwtPayload := payload{
   233  			Issuer: "issuer",
   234  			SDAlg:  "SHA-XXX",
   235  		}
   236  
   237  		signedJWT, err := afjwt.NewSigned(jwtPayload, nil, signer)
   238  		r.NoError(err)
   239  
   240  		err = VerifyDisclosuresInSDJWT(nil, signedJWT)
   241  		r.Error(err)
   242  		r.Contains(err.Error(), "_sd_alg 'SHA-XXX' not supported")
   243  	})
   244  
   245  	t.Run("error - algorithm is not a string", func(t *testing.T) {
   246  		payload := make(map[string]interface{})
   247  		payload[SDAlgorithmKey] = 18
   248  
   249  		signedJWT, err := afjwt.NewSigned(payload, nil, signer)
   250  		r.NoError(err)
   251  
   252  		err = VerifyDisclosuresInSDJWT(nil, signedJWT)
   253  		r.Error(err)
   254  		r.Contains(err.Error(), "_sd_alg must be a string")
   255  	})
   256  
   257  	t.Run("error - selective disclosures must be an array", func(t *testing.T) {
   258  		payload := make(map[string]interface{})
   259  		payload[SDAlgorithmKey] = testAlg
   260  		payload[SDKey] = "test"
   261  
   262  		signedJWT, err := afjwt.NewSigned(payload, nil, signer)
   263  		r.NoError(err)
   264  
   265  		err = VerifyDisclosuresInSDJWT([]string{additionalDisclosure}, signedJWT)
   266  		r.Error(err)
   267  		r.Contains(err.Error(), "get disclosure digests: entry type[string] is not an array")
   268  	})
   269  
   270  	t.Run("error - selective disclosures must be a string", func(t *testing.T) {
   271  		payload := make(map[string]interface{})
   272  		payload[SDAlgorithmKey] = testAlg
   273  		payload[SDKey] = []float64{123}
   274  
   275  		signedJWT, err := afjwt.NewSigned(payload, nil, signer)
   276  		r.NoError(err)
   277  
   278  		err = VerifyDisclosuresInSDJWT([]string{additionalDisclosure}, signedJWT)
   279  		r.Error(err)
   280  		r.Contains(err.Error(), "get disclosure digests: entry item type[float64] is not a string")
   281  	})
   282  }
   283  
   284  func TestGetDisclosureClaims(t *testing.T) {
   285  	r := require.New(t)
   286  
   287  	t.Run("success", func(t *testing.T) {
   288  		sdJWT := ParseCombinedFormatForIssuance(testCombinedFormatForIssuance)
   289  		require.Equal(t, 1, len(sdJWT.Disclosures))
   290  
   291  		disclosureClaims, err := GetDisclosureClaims(sdJWT.Disclosures)
   292  		r.NoError(err)
   293  		r.Len(disclosureClaims, 1)
   294  
   295  		r.Equal("given_name", disclosureClaims[0].Name)
   296  		r.Equal("John", disclosureClaims[0].Value)
   297  	})
   298  
   299  	t.Run("error - invalid disclosure format (not encoded)", func(t *testing.T) {
   300  		sdJWT := ParseCombinedFormatForIssuance("jws~xyz")
   301  		require.Equal(t, 1, len(sdJWT.Disclosures))
   302  
   303  		disclosureClaims, err := GetDisclosureClaims(sdJWT.Disclosures)
   304  		r.Error(err)
   305  		r.Nil(disclosureClaims)
   306  		r.Contains(err.Error(), "failed to unmarshal disclosure array")
   307  	})
   308  
   309  	t.Run("error - invalid disclosure array (not three parts)", func(t *testing.T) {
   310  		disclosureArr := []interface{}{"name", "value"}
   311  		disclosureJSON, err := json.Marshal(disclosureArr)
   312  		require.NoError(t, err)
   313  
   314  		sdJWT := ParseCombinedFormatForIssuance(fmt.Sprintf("jws~%s", base64.RawURLEncoding.EncodeToString(disclosureJSON)))
   315  		require.Equal(t, 1, len(sdJWT.Disclosures))
   316  
   317  		disclosureClaims, err := GetDisclosureClaims(sdJWT.Disclosures)
   318  		r.Error(err)
   319  		r.Nil(disclosureClaims)
   320  		r.Contains(err.Error(), "disclosure array size[2] must be 3")
   321  	})
   322  
   323  	t.Run("error - invalid disclosure array (name is not a string)", func(t *testing.T) {
   324  		disclosureArr := []interface{}{"salt", 123, "value"}
   325  		disclosureJSON, err := json.Marshal(disclosureArr)
   326  		require.NoError(t, err)
   327  
   328  		sdJWT := ParseCombinedFormatForIssuance(fmt.Sprintf("jws~%s", base64.RawURLEncoding.EncodeToString(disclosureJSON)))
   329  		require.Equal(t, 1, len(sdJWT.Disclosures))
   330  
   331  		disclosureClaims, err := GetDisclosureClaims(sdJWT.Disclosures)
   332  		r.Error(err)
   333  		r.Nil(disclosureClaims)
   334  		r.Contains(err.Error(), "disclosure name type[float64] must be string")
   335  	})
   336  }
   337  
   338  func TestGetDisclosedClaims(t *testing.T) {
   339  	r := require.New(t)
   340  
   341  	cfi := ParseCombinedFormatForIssuance(testCombinedFormatForIssuance)
   342  	r.Equal(testSDJWT, cfi.SDJWT)
   343  	r.Equal(1, len(cfi.Disclosures))
   344  
   345  	disclosureClaims, err := GetDisclosureClaims(cfi.Disclosures)
   346  	r.NoError(err)
   347  
   348  	token, _, err := afjwt.Parse(cfi.SDJWT, afjwt.WithSignatureVerifier(&NoopSignatureVerifier{}))
   349  	r.NoError(err)
   350  
   351  	var claims map[string]interface{}
   352  	err = token.DecodeClaims(&claims)
   353  	r.NoError(err)
   354  
   355  	t.Run("success", func(t *testing.T) {
   356  		disclosedClaims, err := GetDisclosedClaims(disclosureClaims, claims)
   357  		r.NoError(err)
   358  		r.NotNil(disclosedClaims)
   359  
   360  		r.Equal(5, len(disclosedClaims))
   361  		r.NotEmpty(disclosedClaims["iat"])
   362  		r.NotEmpty(disclosedClaims["nbf"])
   363  		r.NotEmpty(disclosedClaims["iss"])
   364  		r.Equal("https://example.com/issuer", disclosedClaims["iss"])
   365  		r.Equal("John", disclosedClaims["given_name"])
   366  	})
   367  
   368  	t.Run("success - with complex object", func(t *testing.T) {
   369  		testClaims := utils.CopyMap(claims)
   370  
   371  		additionalDigest, err := GetHash(crypto.SHA256, additionalDisclosure)
   372  		r.NoError(err)
   373  
   374  		parentObj := make(map[string]interface{})
   375  		parentObj["last_name"] = "Brown"
   376  		parentObj[SDKey] = []interface{}{additionalDigest}
   377  
   378  		testClaims["father"] = parentObj
   379  
   380  		printObject(t, "Complex Claims", testClaims)
   381  
   382  		disclosedClaims, err := GetDisclosedClaims(append(disclosureClaims,
   383  			&DisclosureClaim{
   384  				Disclosure: additionalDisclosure,
   385  				Name:       "key-x",
   386  				Value:      "value-y"}),
   387  			testClaims)
   388  		r.NoError(err)
   389  		r.NotNil(disclosedClaims)
   390  
   391  		printObject(t, "Disclosed Claims", disclosedClaims)
   392  
   393  		r.Equal(6, len(disclosedClaims))
   394  		r.Equal("John", disclosedClaims["given_name"])
   395  		r.Equal("value-y", disclosedClaims["father"].(map[string]interface{})["key-x"])
   396  	})
   397  
   398  	t.Run("error - claim value contains _sd", func(t *testing.T) {
   399  		testClaims := utils.CopyMap(claims)
   400  
   401  		additionalDigest, err := GetHash(crypto.SHA256, additionalDisclosure)
   402  		r.NoError(err)
   403  
   404  		parentObj := make(map[string]interface{})
   405  		parentObj["last_name"] = "Smith"
   406  		parentObj[SDKey] = []interface{}{additionalDigest}
   407  
   408  		testClaims["father"] = parentObj
   409  
   410  		disclosedClaims, err := GetDisclosedClaims(append(disclosureClaims,
   411  			&DisclosureClaim{
   412  				Disclosure: additionalDisclosure,
   413  				Name:       "key-x",
   414  				Value: map[string]interface{}{
   415  					"_sd": []interface{}{"test-digest"},
   416  				},
   417  			}),
   418  			testClaims)
   419  		r.Error(err)
   420  		r.Nil(disclosedClaims)
   421  		r.Contains(err.Error(), "failed to process disclosed claims: claim value contains an object with an '_sd' key")
   422  	})
   423  
   424  	t.Run("error - same claim key at the same level ", func(t *testing.T) {
   425  		testClaims := utils.CopyMap(claims)
   426  
   427  		parentObj := make(map[string]interface{})
   428  		parentObj["given_name"] = "Albert"
   429  		parentObj[SDKey] = claims[SDKey]
   430  
   431  		testClaims["father"] = parentObj
   432  
   433  		printObject(t, "Complex Claims", testClaims)
   434  
   435  		disclosedClaims, err := GetDisclosedClaims(disclosureClaims, testClaims)
   436  		r.Error(err)
   437  		r.Nil(disclosedClaims)
   438  		r.Contains(err.Error(),
   439  			"failed to process disclosed claims: claim name 'given_name' already exists at the same level")
   440  	})
   441  
   442  	t.Run("error - digest included in more than one spot ", func(t *testing.T) {
   443  		testClaims := utils.CopyMap(claims)
   444  
   445  		parentObj := make(map[string]interface{})
   446  		parentObj["last_name"] = "Smith"
   447  		parentObj[SDKey] = claims[SDKey]
   448  
   449  		testClaims["father"] = parentObj
   450  
   451  		printObject(t, "Complex Claims", testClaims)
   452  
   453  		disclosedClaims, err := GetDisclosedClaims(disclosureClaims, testClaims)
   454  		r.Error(err)
   455  		r.Nil(disclosedClaims)
   456  		r.Contains(err.Error(),
   457  			"failed to process disclosed claims: digest 'qqvcqnczAMgYx7EykI6wwtspyvyvK790ge7MBbQ-Nus' has been included in more than one place") //nolint:lll
   458  	})
   459  
   460  	t.Run("error - with complex object", func(t *testing.T) {
   461  		testClaims := utils.CopyMap(claims)
   462  
   463  		parentObj := make(map[string]interface{})
   464  		parentObj["given_name"] = "Albert"
   465  		parentObj[SDKey] = []interface{}{0}
   466  
   467  		testClaims["father"] = parentObj
   468  
   469  		disclosedClaims, err := GetDisclosedClaims(disclosureClaims, testClaims)
   470  		r.Error(err)
   471  		r.Nil(disclosedClaims)
   472  
   473  		r.Contains(err.Error(),
   474  			"failed to process disclosed claims: get disclosure digests: entry item type[int] is not a string")
   475  	})
   476  
   477  	t.Run("error - no _sd_alg", func(t *testing.T) {
   478  		disclosedClaims, err := GetDisclosedClaims(disclosureClaims, make(map[string]interface{}))
   479  		r.Error(err)
   480  		r.Nil(disclosedClaims)
   481  
   482  		r.Contains(err.Error(),
   483  			"failed to get crypto hash from claims: _sd_alg must be present in SD-JWT")
   484  	})
   485  
   486  	t.Run("error - invalid _sd item", func(t *testing.T) {
   487  		testClaims := make(map[string]interface{})
   488  		testClaims[SDAlgorithmKey] = testAlg
   489  		testClaims[SDKey] = []interface{}{0}
   490  
   491  		disclosedClaims, err := GetDisclosedClaims(disclosureClaims, testClaims)
   492  		r.Error(err)
   493  		r.Nil(disclosedClaims)
   494  
   495  		r.Contains(err.Error(),
   496  			"failed to process disclosed claims: get disclosure digests: entry item type[int] is not a string")
   497  	})
   498  
   499  	t.Run("error - invalid _sd type", func(t *testing.T) {
   500  		testClaims := make(map[string]interface{})
   501  		testClaims[SDAlgorithmKey] = "sha-256"
   502  		testClaims[SDKey] = "not-array"
   503  
   504  		disclosedClaims, err := GetDisclosedClaims(disclosureClaims, testClaims)
   505  		r.Error(err)
   506  		r.Nil(disclosedClaims)
   507  
   508  		r.Contains(err.Error(),
   509  			"failed to process disclosed claims: get disclosure digests: entry type[string] is not an array")
   510  	})
   511  
   512  	t.Run("error - get hash fails", func(t *testing.T) {
   513  		testClaims := make(map[string]interface{})
   514  		testClaims[SDAlgorithmKey] = "sha-256"
   515  		testClaims[SDKey] = []interface{}{"abc"}
   516  
   517  		err := processDisclosedClaims(disclosureClaims, testClaims, make(map[string]bool), 0)
   518  		r.Error(err)
   519  
   520  		r.Contains(err.Error(),
   521  			"hash function not available for: 0")
   522  	})
   523  }
   524  
   525  func TestGetCryptoHash(t *testing.T) {
   526  	r := require.New(t)
   527  
   528  	t.Run("success", func(t *testing.T) {
   529  		hash, err := GetCryptoHash("sha-256")
   530  		r.NoError(err)
   531  		r.Equal(crypto.SHA256, hash)
   532  
   533  		hash, err = GetCryptoHash("sha-384")
   534  		r.NoError(err)
   535  		r.Equal(crypto.SHA384, hash)
   536  
   537  		hash, err = GetCryptoHash("sha-512")
   538  		r.NoError(err)
   539  		r.Equal(crypto.SHA512, hash)
   540  	})
   541  
   542  	t.Run("error - not supported", func(t *testing.T) {
   543  		hash, err := GetCryptoHash("invalid")
   544  		r.Error(err)
   545  		r.Equal(crypto.Hash(0), hash)
   546  		r.Contains(err.Error(), "_sd_alg 'invalid' not supported")
   547  	})
   548  }
   549  
   550  func TestGetSDAlg(t *testing.T) {
   551  	r := require.New(t)
   552  
   553  	t.Run("success", func(t *testing.T) {
   554  		claims := map[string]interface{}{
   555  			"_sd_alg": "sha-256",
   556  		}
   557  
   558  		alg, err := GetSDAlg(claims)
   559  		r.NoError(err)
   560  		r.Equal("sha-256", alg)
   561  	})
   562  
   563  	t.Run("success - algorithm is in VC credential subject", func(t *testing.T) {
   564  		claims := map[string]interface{}{
   565  			"given_name": "John",
   566  			"vc": map[string]interface{}{
   567  				"_sd_alg": "sha-256",
   568  			},
   569  		}
   570  
   571  		alg, err := GetSDAlg(claims)
   572  		r.NoError(err)
   573  		r.Equal("sha-256", alg)
   574  	})
   575  
   576  	t.Run("error - algorithm not found (no vc)", func(t *testing.T) {
   577  		alg, err := GetSDAlg(make(map[string]interface{}))
   578  		r.Error(err)
   579  		r.Empty(alg)
   580  
   581  		r.Contains(err.Error(), "_sd_alg must be present in SD-JWT")
   582  	})
   583  
   584  	t.Run("error - algorithm not found (vc is empty)", func(t *testing.T) {
   585  		claims := map[string]interface{}{
   586  			"vc": map[string]interface{}{},
   587  		}
   588  
   589  		alg, err := GetSDAlg(claims)
   590  		r.Error(err)
   591  		r.Empty(alg)
   592  
   593  		r.Contains(err.Error(), "_sd_alg must be present in SD-JWT")
   594  	})
   595  
   596  	t.Run("error - algorithm not found (vc is not a map)", func(t *testing.T) {
   597  		claims := map[string]interface{}{
   598  			"vc": "invalid",
   599  		}
   600  
   601  		alg, err := GetSDAlg(claims)
   602  		r.Error(err)
   603  		r.Empty(alg)
   604  
   605  		r.Contains(err.Error(), "_sd_alg must be present in SD-JWT")
   606  	})
   607  
   608  	t.Run("error - algorithm must be a string", func(t *testing.T) {
   609  		claims := map[string]interface{}{
   610  			"vc": map[string]interface{}{
   611  				"_sd_alg": 123,
   612  			},
   613  		}
   614  
   615  		alg, err := GetSDAlg(claims)
   616  		r.Error(err)
   617  		r.Empty(alg)
   618  
   619  		r.Contains(err.Error(), "_sd_alg must be a string")
   620  	})
   621  
   622  	t.Run("error - algorithm must be a string", func(t *testing.T) {
   623  		claims := map[string]interface{}{
   624  			"_sd_alg": 123,
   625  		}
   626  
   627  		alg, err := GetSDAlg(claims)
   628  		r.Error(err)
   629  		r.Empty(alg)
   630  
   631  		r.Contains(err.Error(), "_sd_alg must be a string")
   632  	})
   633  }
   634  
   635  func TestGetCNF(t *testing.T) {
   636  	r := require.New(t)
   637  
   638  	t.Run("success - cnf is at the top level", func(t *testing.T) {
   639  		claims := make(map[string]interface{})
   640  		claims["cnf"] = map[string]interface{}{
   641  			"jwk": map[string]interface{}{
   642  				"kty": "RSA",
   643  				"e":   "AQAB",
   644  				"n":   "pm4bOHBg-oYhAyPWzR56AWX3rUIXp11",
   645  			},
   646  		}
   647  
   648  		cnf, err := GetCNF(claims)
   649  		r.NoError(err)
   650  		r.NotEmpty(cnf["jwk"])
   651  	})
   652  
   653  	t.Run("success - cnf is in VC", func(t *testing.T) {
   654  		var payload map[string]interface{}
   655  
   656  		err := json.Unmarshal([]byte(vcSample), &payload)
   657  		require.NoError(t, err)
   658  
   659  		cnf, err := GetCNF(payload)
   660  		r.NoError(err)
   661  		r.NotEmpty(cnf["jwk"])
   662  	})
   663  
   664  	t.Run("error - cnf not found (empty claims)", func(t *testing.T) {
   665  		cnf, err := GetCNF(make(map[string]interface{}))
   666  		r.Error(err)
   667  		r.Empty(cnf)
   668  
   669  		r.Contains(err.Error(), "cnf must be present in SD-JWT")
   670  	})
   671  
   672  	t.Run("error - cnf is not an object", func(t *testing.T) {
   673  		claims := make(map[string]interface{})
   674  		claims["cnf"] = "abc"
   675  
   676  		cnf, err := GetCNF(claims)
   677  		r.Error(err)
   678  		r.Empty(cnf)
   679  
   680  		r.Contains(err.Error(), "cnf must be an object")
   681  	})
   682  }
   683  
   684  func TestKeyExistInMap(t *testing.T) {
   685  	r := require.New(t)
   686  
   687  	key := "_sd"
   688  
   689  	t.Run("true - claims contain _sd key (top level object)", func(t *testing.T) {
   690  		claims := map[string]interface{}{
   691  			key: "whatever",
   692  		}
   693  
   694  		exists := KeyExistsInMap(key, claims)
   695  		r.True(exists)
   696  	})
   697  
   698  	t.Run("true - claims contain _sd key (inner object)", func(t *testing.T) {
   699  		claims := map[string]interface{}{
   700  			"degree": map[string]interface{}{
   701  				key:    "whatever",
   702  				"type": "BachelorDegree",
   703  			},
   704  		}
   705  
   706  		exists := KeyExistsInMap(key, claims)
   707  		r.True(exists)
   708  	})
   709  
   710  	t.Run("false - _sd key not present in claims", func(t *testing.T) {
   711  		claims := map[string]interface{}{
   712  			"key-x": "value-y",
   713  			"degree": map[string]interface{}{
   714  				"key-x": "whatever",
   715  				"type":  "BachelorDegree",
   716  			},
   717  		}
   718  
   719  		exists := KeyExistsInMap(key, claims)
   720  		r.False(exists)
   721  	})
   722  }
   723  
   724  func printObject(t *testing.T, name string, obj interface{}) {
   725  	t.Helper()
   726  
   727  	objBytes, err := json.Marshal(obj)
   728  	require.NoError(t, err)
   729  
   730  	prettyJSON, err := prettyPrint(objBytes)
   731  	require.NoError(t, err)
   732  
   733  	fmt.Println(name + ":")
   734  	fmt.Println(prettyJSON)
   735  }
   736  
   737  func prettyPrint(msg []byte) (string, error) {
   738  	var prettyJSON bytes.Buffer
   739  
   740  	err := json.Indent(&prettyJSON, msg, "", "\t")
   741  	if err != nil {
   742  		return "", err
   743  	}
   744  
   745  	return prettyJSON.String(), nil
   746  }
   747  
   748  type NoopSignatureVerifier struct {
   749  }
   750  
   751  func (sv *NoopSignatureVerifier) Verify(joseHeaders jose.Headers, payload, signingInput, signature []byte) error {
   752  	return nil
   753  }
   754  
   755  const additionalDisclosure = `WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0`
   756  
   757  // nolint: lll
   758  const testCombinedFormatForIssuance = `eyJhbGciOiJFZERTQSJ9.eyJfc2QiOlsicXF2Y3FuY3pBTWdZeDdFeWtJNnd3dHNweXZ5dks3OTBnZTdNQmJRLU51cyJdLCJfc2RfYWxnIjoic2hhLTI1NiIsImV4cCI6MTcwMzAyMzg1NSwiaWF0IjoxNjcxNDg3ODU1LCJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsIm5iZiI6MTY3MTQ4Nzg1NX0.vscuzfwcHGi04pWtJCadc4iDELug6NH6YK-qxhY1qacsciIHuoLELAfon1tGamHtuu8TSs6OjtLk3lHE16jqAQ~WyIzanFjYjY3ejl3a3MwOHp3aUs3RXlRIiwgImdpdmVuX25hbWUiLCAiSm9obiJd`
   759  
   760  // nolint: lll
   761  const testSDJWT = `eyJhbGciOiJFZERTQSJ9.eyJfc2QiOlsicXF2Y3FuY3pBTWdZeDdFeWtJNnd3dHNweXZ5dks3OTBnZTdNQmJRLU51cyJdLCJfc2RfYWxnIjoic2hhLTI1NiIsImV4cCI6MTcwMzAyMzg1NSwiaWF0IjoxNjcxNDg3ODU1LCJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsIm5iZiI6MTY3MTQ4Nzg1NX0.vscuzfwcHGi04pWtJCadc4iDELug6NH6YK-qxhY1qacsciIHuoLELAfon1tGamHtuu8TSs6OjtLk3lHE16jqAQ`
   762  
   763  // nolint: lll
   764  const specCombinedFormatForIssuance = `eyJhbGciOiAiUlMyNTYiLCAia2lkIjogImNBRUlVcUowY21MekQxa3pHemhlaUJhZzBZUkF6VmRsZnhOMjgwTmdIYUEifQ.eyJfc2QiOiBbIk5ZQ29TUktFWXdYZHBlNXlkdUpYQ3h4aHluRVU4ei1iNFR5TmlhcDc3VVkiLCAiU1k4bjJCYmtYOWxyWTNleEhsU3dQUkZYb0QwOUdGOGE5Q1BPLUc4ajIwOCIsICJUUHNHTlBZQTQ2d21CeGZ2MnpuT0poZmRvTjVZMUdrZXpicGFHWkNUMWFjIiwgIlprU0p4eGVHbHVJZFlCYjdDcWtaYkpWbTB3MlY1VXJSZU5UekFRQ1lCanciLCAibDlxSUo5SlRRd0xHN09MRUlDVEZCVnhtQXJ3OFBqeTY1ZEQ2bXRRVkc1YyIsICJvMVNBc0ozM1lNaW9POXBYNVZlQU0xbHh1SEY2aFpXMmtHZGtLS0JuVmxvIiwgInFxdmNxbmN6QU1nWXg3RXlrSTZ3d3RzcHl2eXZLNzkwZ2U3TUJiUS1OdXMiXSwgImlzcyI6ICJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsICJpYXQiOiAxNTE2MjM5MDIyLCAiZXhwIjogMTUxNjI0NzAyMiwgIl9zZF9hbGciOiAic2hhLTI1NiIsICJjbmYiOiB7Imp3ayI6IHsia3R5IjogIlJTQSIsICJuIjogInBtNGJPSEJnLW9ZaEF5UFd6UjU2QVdYM3JVSVhwMTFfSUNEa0dnUzZXM1pXTHRzLWh6d0kzeDY1NjU5a2c0aFZvOWRiR29DSkUzWkdGX2VhZXRFMzBVaEJVRWdwR3dyRHJRaUo5enFwcm1jRmZyM3F2dmtHanR0aDhaZ2wxZU0yYkpjT3dFN1BDQkhXVEtXWXMxNTJSN2c2SmcyT1ZwaC1hOHJxLXE3OU1oS0c1UW9XX21UejEwUVRfNkg0YzdQaldHMWZqaDhocFdObmJQX3B2NmQxelN3WmZjNWZsNnlWUkwwRFYwVjNsR0hLZTJXcWZfZU5HakJyQkxWa2xEVGs4LXN0WF9NV0xjUi1FR21YQU92MFVCV2l0U19kWEpLSnUtdlhKeXcxNG5IU0d1eFRJSzJoeDFwdHRNZnQ5Q3N2cWltWEtlRFRVMTRxUUwxZUU3aWhjdyIsICJlIjogIkFRQUIifX19.xqgKrDO6dK_oBL3fiqdcq_elaIGxM6Z-RyuysglGyddR1O1IiE3mIk8kCpoqcRLR88opkVWN2392K_XYfAuAmeT9kJVisD8ZcgNcv-MQlWW9s8WaViXxBRe7EZWkWRQcQVR6jf95XZ5H2-_KA54POq3L42xjk0y5vDr8yc08Reak6vvJVvjXpp-Wk6uxsdEEAKFspt_EYIvISFJhfTuQqyhCjnaW13X312MSQBPwjbHn74ylUqVLljDvqcemxeqjh42KWJq4C3RqNJ7anA2i3FU1kB4-KNZWsijY7-op49iL7BrnIBxdlAMrbHEkoGTbFWdl7Ki17GHtDxxa1jaxQg~WyJkcVR2WE14UzBHYTNEb2FHbmU5eDBRIiwgInN1YiIsICJqb2huX2RvZV80MiJd~WyIzanFjYjY3ejl3a3MwOHp3aUs3RXlRIiwgImdpdmVuX25hbWUiLCAiSm9obiJd~WyJxUVdtakpsMXMxUjRscWhFTkxScnJ3IiwgImZhbWlseV9uYW1lIiwgIkRvZSJd~WyJLVXhTNWhFX1hiVmFjckdBYzdFRnd3IiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ~WyIzcXZWSjFCQURwSERTUzkzOVEtUml3IiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ~WyIweEd6bjNNaXFzY3RaSV9PcERsQWJRIiwgImFkZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0~WyJFUktNMENOZUZKa2FENW1UWFZfWDh3IiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0`
   765  
   766  // Payload represents JWT payload.
   767  type payload struct {
   768  	Issuer  string `json:"iss,omitempty"`
   769  	Subject string `json:"sub,omitempty"`
   770  
   771  	SDAlg string `json:"_sd_alg,omitempty"`
   772  }
   773  
   774  const specExample2bJWT = `eyJhbGciOiAiRVMyNTYifQ.eyJfc2QiOiBbIjBsa1NjS2ppSk1IZWRKZnE3c0pCN0hRM3FvbUdmckVMYm81Z1podktSV28iLCAiMWgyOWdnUWkxeG91LV9OalZ5eW9DaEsyYXN3VXRvMlVqQ2ZGLTFMYWhBOCIsICIzQ29MVUxtRHh4VXdfTGR5WUVUanVkdVh1RXBHdUJ5NHJYSG90dUQ0MFg0IiwgIkFJRHlveHgxaXB5NDUtR0ZwS2d2Yy1ISWJjVnJsTGxyWUxYbXgzZXYyZTQiLCAiT2x0aGZSb0ZkUy1KNlM4Mk9XbHJPNHBXaG9lUk1ySF9LR1BfaDZFYXBZUSIsICJyNGRicEdlZWlhMDJTeUdMNWdCZEhZMXc4SWhqVDN4eDA1UnNmeXlIVWs0Il0sICJhZGRyZXNzIjogeyJfc2QiOiBbIjZPS053bkdHS1dYQ0k5dWlqTkFzdjY0dTIyZUxTNHJNZExObGcxZnFKcDQiLCAiSEVWTWdELU5LSzVOdlhQYkFSb3JWZE9ESVRta1V5dU1wQ3NfbTdIWG5ZYyIsICJVcTAyblY3M0swYmRSSzIzcnphYm1uRGE0TzhZTlFadnQ5eDhMeWtva19ZIiwgIm94RlJpbG5vMjZVWWU3a3FNTTRiZHE4SXZOTXRJaTZGOHB0dC11aVBMYk0iXX0sICJpc3MiOiAiaHR0cHM6Ly9leGFtcGxlLmNvbS9pc3N1ZXIiLCAiaWF0IjogMTUxNjIzOTAyMiwgImV4cCI6IDE1MTYyNDcwMjIsICJfc2RfYWxnIjogInNoYS0yNTYifQ.M45AUExpi9THOTVIHfBmb2GL0WXJf4TeWB5QPmsxdBkj9pUcLOPR8YVafLIt8m_imYHTBYYcAyf7qSnquxMxGQ` // nolint:lll
   775  const specExample2bDisclosures = `~WyJSdHczZUFFUE5wWjIwTkhZSzNNRWNnIiwgImZhbWlseV9uYW1lIiwgIlx1NWM3MVx1NzUzMCJd~WyJicjgxenVSc0NUcXJuWEp4MHVqMkRRIiwgImdpdmVuX25hbWUiLCAiXHU1OTJhXHU5MGNlIl0~WyI1Z2NXRmxWSEM1VVEwbktrallybDlnIiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0~WyJJTms2bkx4WGFybDF4NmVabHdBOTV3IiwgImVtYWlsIiwgIlwidW51c3VhbCBlbWFpbCBhZGRyZXNzXCJAbmlob24uY29tIl0~WyJNOVY2N3V0UC1hTF9lR1B0UU5hM0RRIiwgInJlZ2lvbiIsICJcdTZlMmZcdTUzM2EiXQ~WyJzNFhNSmxXQ2Eza3hDWk4wSVVrbnlBIiwgImNvdW50cnkiLCAiSlAiXQ~`                                                                                                                                                                                                                                                                                                                                                                                                                                                             // nolint:lll
   776  
   777  const vcSample = `
   778  {
   779  	"iat": 1673987547,
   780  	"iss": "did:example:76e12ec712ebc6f1c221ebfeb1f",
   781  	"jti": "http://example.edu/credentials/1872",
   782  	"nbf": 1673987547,
   783  	"sub": "did:example:ebfeb1f712ebc6f1c276e12ec21",
   784  	"vc": {
   785  		"@context": [
   786  			"https://www.w3.org/2018/credentials/v1"
   787  		],
   788  		"credentialSubject": {
   789  			"_sd": [
   790  				"GJFkje8c1iayy1HQW__JEhuHTz8QGlkcMaxDTjT1wpQ",
   791  				"goPn0hokFnQBktqzXxgTK-4CCldmLjlRwUVCIltDyRg",
   792  				"FAiNODIxDMwGTljNYcVKkx7LBsr1pb-U6XuAfVFuOGY"
   793  			],
   794  			"id": "did:example:ebfeb1f712ebc6f1c276e12ec21"
   795  		},
   796  		"_sd_alg": "sha-256",
   797  		"cnf": {
   798  			"jwk": {
   799  				"crv": "Ed25519",
   800  				"kty": "OKP",
   801  				"x": "7jtkxxk0Pb3E0O6JXJiN8HyIp2DpCiqaHCWfMXl9ZFo"
   802  			}
   803  		},
   804  		"first_name": "First name",
   805  		"id": "http://example.edu/credentials/1872",
   806  		"info": "Info",
   807  		"issuanceDate": "2023-01-17T22:32:27.468109817+02:00",
   808  		"issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
   809  		"last_name": "Last name",
   810  		"type": "VerifiableCredential"
   811  	}
   812  }`