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

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package issuer
     8  
     9  import (
    10  	"bytes"
    11  	"crypto"
    12  	"crypto/ed25519"
    13  	"crypto/rand"
    14  	"crypto/rsa"
    15  	"errors"
    16  	"fmt"
    17  	"strings"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/PaesslerAG/jsonpath"
    22  	"github.com/go-jose/go-jose/v3"
    23  	"github.com/go-jose/go-jose/v3/json"
    24  	"github.com/go-jose/go-jose/v3/jwt"
    25  	"github.com/stretchr/testify/require"
    26  
    27  	afjose "github.com/hyperledger/aries-framework-go/pkg/doc/jose"
    28  	"github.com/hyperledger/aries-framework-go/pkg/doc/jose/jwk"
    29  	"github.com/hyperledger/aries-framework-go/pkg/doc/jose/jwk/jwksupport"
    30  	afjwt "github.com/hyperledger/aries-framework-go/pkg/doc/jwt"
    31  	"github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/common"
    32  )
    33  
    34  const (
    35  	issuer                 = "https://example.com/issuer"
    36  	expectedHashWithSpaces = "qqvcqnczAMgYx7EykI6wwtspyvyvK790ge7MBbQ-Nus"
    37  	sampleSalt             = "3jqcb67z9wks08zwiK7EyQ"
    38  )
    39  
    40  func TestNew(t *testing.T) {
    41  	claims := createClaims()
    42  
    43  	t.Run("Create SD-JWT without signing", func(t *testing.T) {
    44  		r := require.New(t)
    45  
    46  		token, err := New(issuer, claims, nil, &unsecuredJWTSigner{})
    47  		r.NoError(err)
    48  		combinedFormatForIssuance, err := token.Serialize(false)
    49  		require.NoError(t, err)
    50  
    51  		cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance)
    52  		require.Equal(t, 1, len(cfi.Disclosures))
    53  
    54  		var payload map[string]interface{}
    55  		err = token.DecodeClaims(&payload)
    56  		r.NoError(err)
    57  
    58  		r.Len(payload[common.SDKey], 1)
    59  		r.Equal("sha-256", payload[common.SDAlgorithmKey])
    60  		r.Equal(issuer, payload["iss"])
    61  	})
    62  
    63  	t.Run("Create JWS signed by EdDSA", func(t *testing.T) {
    64  		r := require.New(t)
    65  
    66  		pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
    67  		r.NoError(err)
    68  
    69  		token, err := New(issuer, claims, nil, afjwt.NewEd25519Signer(privKey),
    70  			WithJSONMarshaller(jsonMarshalWithSpace),
    71  			WithSaltFnc(func() (string, error) {
    72  				return sampleSalt, nil
    73  			}))
    74  		r.NoError(err)
    75  		combinedFormatForIssuance, err := token.Serialize(false)
    76  		require.NoError(t, err)
    77  
    78  		fmt.Printf(combinedFormatForIssuance)
    79  
    80  		cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance)
    81  		require.Equal(t, 1, len(cfi.Disclosures))
    82  
    83  		var parsedClaims map[string]interface{}
    84  		err = verifyEd25519ViaGoJose(cfi.SDJWT, pubKey, &parsedClaims)
    85  		r.NoError(err)
    86  
    87  		parsedClaimsBytes, err := json.Marshal(parsedClaims)
    88  		require.NoError(t, err)
    89  
    90  		prettyJSON, err := prettyPrint(parsedClaimsBytes)
    91  		require.NoError(t, err)
    92  
    93  		fmt.Println(prettyJSON)
    94  
    95  		require.True(t, existsInDisclosures(parsedClaims, expectedHashWithSpaces))
    96  
    97  		err = verifyEd25519(cfi.SDJWT, pubKey)
    98  		r.NoError(err)
    99  	})
   100  
   101  	t.Run("Create JWS signed by RS256", func(t *testing.T) {
   102  		r := require.New(t)
   103  
   104  		privKey, err := rsa.GenerateKey(rand.Reader, 2048)
   105  		r.NoError(err)
   106  
   107  		pubKey := &privKey.PublicKey
   108  
   109  		token, err := New(issuer, claims, nil, afjwt.NewRS256Signer(privKey, nil),
   110  			WithJSONMarshaller(jsonMarshalWithSpace),
   111  			WithSaltFnc(func() (string, error) {
   112  				return sampleSalt, nil
   113  			}))
   114  		r.NoError(err)
   115  		combinedFormatForIssuance, err := token.Serialize(false)
   116  		require.NoError(t, err)
   117  
   118  		cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance)
   119  		require.Equal(t, 1, len(cfi.Disclosures))
   120  
   121  		var parsedClaims map[string]interface{}
   122  		err = verifyRS256ViaGoJose(cfi.SDJWT, pubKey, &parsedClaims)
   123  		r.NoError(err)
   124  
   125  		parsedClaimsBytes, err := json.Marshal(parsedClaims)
   126  		require.NoError(t, err)
   127  
   128  		prettyJSON, err := prettyPrint(parsedClaimsBytes)
   129  		require.NoError(t, err)
   130  
   131  		fmt.Println(prettyJSON)
   132  
   133  		expectedHashWithSpaces := expectedHashWithSpaces
   134  		require.True(t, existsInDisclosures(parsedClaims, expectedHashWithSpaces))
   135  
   136  		err = verifyRS256(cfi.SDJWT, pubKey)
   137  		r.NoError(err)
   138  	})
   139  
   140  	t.Run("Create Complex Claims JWS signed by EdDSA", func(t *testing.T) {
   141  		r := require.New(t)
   142  
   143  		pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
   144  		r.NoError(err)
   145  
   146  		complexClaims := createComplexClaims()
   147  
   148  		issued := time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)
   149  		expiry := time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)
   150  		notBefore := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
   151  
   152  		var newOpts []NewOpt
   153  
   154  		newOpts = append(newOpts,
   155  			WithIssuedAt(jwt.NewNumericDate(issued)),
   156  			WithExpiry(jwt.NewNumericDate(expiry)),
   157  			WithNotBefore(jwt.NewNumericDate(notBefore)),
   158  			WithJTI("jti"),
   159  			WithID("id"),
   160  			WithSubject("subject"),
   161  			WithAudience("audience"),
   162  			WithSaltFnc(generateSalt),
   163  			WithJSONMarshaller(json.Marshal),
   164  			WithHashAlgorithm(crypto.SHA256),
   165  		)
   166  
   167  		token, err := New(issuer, complexClaims, nil, afjwt.NewEd25519Signer(privKey), newOpts...)
   168  		r.NoError(err)
   169  		combinedFormatForIssuance, err := token.Serialize(false)
   170  		require.NoError(t, err)
   171  
   172  		fmt.Printf(combinedFormatForIssuance)
   173  
   174  		cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance)
   175  		require.Equal(t, 7, len(cfi.Disclosures))
   176  
   177  		var parsedClaims map[string]interface{}
   178  		err = verifyEd25519ViaGoJose(cfi.SDJWT, pubKey, &parsedClaims)
   179  		r.NoError(err)
   180  
   181  		printObject(t, "Parsed Claims:", parsedClaims)
   182  
   183  		r.Equal(issuer, parsedClaims["iss"])
   184  		r.Equal("audience", parsedClaims["aud"])
   185  		r.Equal("subject", parsedClaims["sub"])
   186  		r.Equal("id", parsedClaims["id"])
   187  		r.Equal("jti", parsedClaims["jti"])
   188  		r.Equal("sha-256", parsedClaims["_sd_alg"])
   189  
   190  		_, ok := parsedClaims["nbf"]
   191  		r.True(ok)
   192  
   193  		_, ok = parsedClaims["iat"]
   194  		r.True(ok)
   195  
   196  		_, ok = parsedClaims["exp"]
   197  		r.True(ok)
   198  
   199  		err = verifyEd25519(cfi.SDJWT, pubKey)
   200  		r.NoError(err)
   201  	})
   202  
   203  	t.Run("Create Complex Claims JWS with structured claims flag", func(t *testing.T) {
   204  		r := require.New(t)
   205  
   206  		pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
   207  		r.NoError(err)
   208  
   209  		complexClaims := createComplexClaims()
   210  
   211  		var newOpts []NewOpt
   212  
   213  		newOpts = append(newOpts,
   214  			WithStructuredClaims(true),
   215  		)
   216  
   217  		token, err := New(issuer, complexClaims, nil, afjwt.NewEd25519Signer(privKey), newOpts...)
   218  		r.NoError(err)
   219  		combinedFormatForIssuance, err := token.Serialize(false)
   220  		require.NoError(t, err)
   221  
   222  		fmt.Printf(combinedFormatForIssuance)
   223  
   224  		cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance)
   225  		// expected 6 simple + 4 address object disclosures
   226  		require.Equal(t, 10, len(cfi.Disclosures))
   227  
   228  		var parsedClaims map[string]interface{}
   229  		err = verifyEd25519ViaGoJose(cfi.SDJWT, pubKey, &parsedClaims)
   230  		r.NoError(err)
   231  
   232  		parsedClaimsBytes, err := json.Marshal(parsedClaims)
   233  		require.NoError(t, err)
   234  
   235  		prettyJSON, err := prettyPrint(parsedClaimsBytes)
   236  		require.NoError(t, err)
   237  
   238  		fmt.Println(prettyJSON)
   239  
   240  		err = verifyEd25519(cfi.SDJWT, pubKey)
   241  		r.NoError(err)
   242  	})
   243  
   244  	t.Run("Create Mixed (SD + non-SD) JWS with structured claims flag", func(t *testing.T) {
   245  		r := require.New(t)
   246  
   247  		_, privKey, err := ed25519.GenerateKey(rand.Reader)
   248  		r.NoError(err)
   249  
   250  		complexClaims := map[string]interface{}{
   251  			"degree": map[string]interface{}{
   252  				"degree": "MIT",
   253  				"type":   "BachelorDegree",
   254  				"id":     "some-id",
   255  			},
   256  			"name":   "Jayden Doe",
   257  			"id":     "did:example:ebfeb1f712ebc6f1c276e12ec21",
   258  			"spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1",
   259  		}
   260  
   261  		var newOpts []NewOpt
   262  
   263  		newOpts = append(newOpts,
   264  			WithStructuredClaims(true),
   265  			WithNonSelectivelyDisclosableClaims([]string{"id", "degree.type"}),
   266  		)
   267  
   268  		token, err := New(issuer, complexClaims, nil, afjwt.NewEd25519Signer(privKey), newOpts...)
   269  		r.NoError(err)
   270  
   271  		var tokenClaims map[string]interface{}
   272  		err = token.DecodeClaims(&tokenClaims)
   273  		r.NoError(err)
   274  
   275  		printObject(t, "Token Claims", tokenClaims)
   276  
   277  		combinedFormatForIssuance, err := token.Serialize(false)
   278  		require.NoError(t, err)
   279  
   280  		fmt.Printf(combinedFormatForIssuance)
   281  
   282  		cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance)
   283  		require.Equal(t, 4, len(cfi.Disclosures))
   284  
   285  		id, err := jsonpath.Get("$.id", tokenClaims)
   286  		r.NoError(err)
   287  		r.Equal("did:example:ebfeb1f712ebc6f1c276e12ec21", id)
   288  
   289  		degreeType, err := jsonpath.Get("$.degree.type", tokenClaims)
   290  		r.NoError(err)
   291  		r.Equal("BachelorDegree", degreeType)
   292  
   293  		degreeID, err := jsonpath.Get("$.degree.id", tokenClaims)
   294  		r.Error(err)
   295  		r.Nil(degreeID)
   296  		r.Contains(err.Error(), "unknown key id")
   297  	})
   298  
   299  	t.Run("Create Mixed (SD + non-SD) JWS with flat claims flag, SHA-512", func(t *testing.T) {
   300  		r := require.New(t)
   301  
   302  		_, privKey, err := ed25519.GenerateKey(rand.Reader)
   303  		r.NoError(err)
   304  
   305  		complexClaims := map[string]interface{}{
   306  			"degree": map[string]interface{}{
   307  				"degree": "MIT",
   308  				"type":   "BachelorDegree",
   309  				"id":     "some-id",
   310  			},
   311  			"name":   "Jayden Doe",
   312  			"id":     "did:example:ebfeb1f712ebc6f1c276e12ec21",
   313  			"spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1",
   314  		}
   315  
   316  		var newOpts []NewOpt
   317  
   318  		newOpts = append(newOpts,
   319  			WithNonSelectivelyDisclosableClaims([]string{"id", "degree.type"}),
   320  			WithHashAlgorithm(crypto.SHA512),
   321  		)
   322  
   323  		token, err := New(issuer, complexClaims, nil, afjwt.NewEd25519Signer(privKey), newOpts...)
   324  		r.NoError(err)
   325  
   326  		var tokenClaims map[string]interface{}
   327  		err = token.DecodeClaims(&tokenClaims)
   328  		r.NoError(err)
   329  
   330  		r.Equal("sha-512", tokenClaims[common.SDAlgorithmKey])
   331  
   332  		printObject(t, "Token Claims", tokenClaims)
   333  
   334  		combinedFormatForIssuance, err := token.Serialize(false)
   335  		require.NoError(t, err)
   336  
   337  		fmt.Printf(combinedFormatForIssuance)
   338  
   339  		cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance)
   340  		require.Equal(t, 3, len(cfi.Disclosures))
   341  
   342  		id, err := jsonpath.Get("$.id", tokenClaims)
   343  		r.NoError(err)
   344  		r.Equal("did:example:ebfeb1f712ebc6f1c276e12ec21", id)
   345  
   346  		degreeType, err := jsonpath.Get("$.degree.type", tokenClaims)
   347  		r.Error(err)
   348  		r.Nil(degreeType)
   349  		r.Contains(err.Error(), "unknown key degree")
   350  	})
   351  
   352  	t.Run("Create SD-JWS with decoy disclosures", func(t *testing.T) {
   353  		r := require.New(t)
   354  
   355  		pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
   356  		r.NoError(err)
   357  
   358  		verifier, e := afjwt.NewEd25519Verifier(pubKey)
   359  		r.NoError(e)
   360  
   361  		token, err := New(issuer, claims, nil, afjwt.NewEd25519Signer(privKey),
   362  			WithDecoyDigests(true))
   363  		r.NoError(err)
   364  		combinedFormatForIssuance, err := token.Serialize(false)
   365  		r.NoError(err)
   366  
   367  		cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance)
   368  		r.Equal(1, len(cfi.Disclosures))
   369  
   370  		afjwtToken, _, err := afjwt.Parse(cfi.SDJWT, afjwt.WithSignatureVerifier(verifier))
   371  		r.NoError(err)
   372  
   373  		var parsedClaims map[string]interface{}
   374  		err = afjwtToken.DecodeClaims(&parsedClaims)
   375  		r.NoError(err)
   376  
   377  		digests, err := common.GetDisclosureDigests(parsedClaims)
   378  		require.NoError(t, err)
   379  
   380  		if len(digests) < 1+decoyMinElements || len(digests) > 1+decoyMaxElements {
   381  			r.Fail(fmt.Sprintf("invalid number of digests: %d", len(digests)))
   382  		}
   383  	})
   384  
   385  	t.Run("Create JWS with holder public key", func(t *testing.T) {
   386  		r := require.New(t)
   387  
   388  		pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
   389  		r.NoError(err)
   390  
   391  		_, holderPublicKey, err := ed25519.GenerateKey(rand.Reader)
   392  		require.NoError(t, err)
   393  
   394  		holderJWK, err := jwksupport.JWKFromKey(holderPublicKey)
   395  		require.NoError(t, err)
   396  
   397  		token, err := New(issuer, claims, nil, afjwt.NewEd25519Signer(privKey),
   398  			WithHolderPublicKey(holderJWK))
   399  		r.NoError(err)
   400  		combinedFormatForIssuance, err := token.Serialize(false)
   401  		require.NoError(t, err)
   402  
   403  		cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance)
   404  		require.Equal(t, 1, len(cfi.Disclosures))
   405  
   406  		var parsedClaims map[string]interface{}
   407  		err = verifyEd25519ViaGoJose(cfi.SDJWT, pubKey, &parsedClaims)
   408  		r.NoError(err)
   409  		require.NotEmpty(t, parsedClaims["cnf"])
   410  
   411  		parsedClaimsBytes, err := json.Marshal(parsedClaims)
   412  		require.NoError(t, err)
   413  
   414  		prettyJSON, err := prettyPrint(parsedClaimsBytes)
   415  		require.NoError(t, err)
   416  
   417  		fmt.Println(prettyJSON)
   418  	})
   419  
   420  	t.Run("error - claims contain _sd key (top level object)", func(t *testing.T) {
   421  		r := require.New(t)
   422  
   423  		_, privKey, err := ed25519.GenerateKey(rand.Reader)
   424  		r.NoError(err)
   425  
   426  		complexClaims := map[string]interface{}{
   427  			"_sd": "whatever",
   428  		}
   429  
   430  		token, err := New(issuer, complexClaims, nil, afjwt.NewEd25519Signer(privKey))
   431  		r.Error(err)
   432  		r.Nil(token)
   433  		r.Contains(err.Error(), "key '_sd' cannot be present in the claims")
   434  	})
   435  
   436  	t.Run("error - claims contain _sd key (inner object)", func(t *testing.T) {
   437  		r := require.New(t)
   438  
   439  		_, privKey, err := ed25519.GenerateKey(rand.Reader)
   440  		r.NoError(err)
   441  
   442  		complexClaims := map[string]interface{}{
   443  			"degree": map[string]interface{}{
   444  				"_sd":  "whatever",
   445  				"type": "BachelorDegree",
   446  			},
   447  		}
   448  
   449  		token, err := New(issuer, complexClaims, nil, afjwt.NewEd25519Signer(privKey))
   450  		r.Error(err)
   451  		r.Nil(token)
   452  		r.Contains(err.Error(), "key '_sd' cannot be present in the claims")
   453  	})
   454  
   455  	t.Run("error - invalid holder public key", func(t *testing.T) {
   456  		r := require.New(t)
   457  
   458  		_, privKey, err := ed25519.GenerateKey(rand.Reader)
   459  		r.NoError(err)
   460  
   461  		token, err := New(issuer, claims, nil, afjwt.NewEd25519Signer(privKey),
   462  			WithHolderPublicKey(&jwk.JWK{JSONWebKey: jose.JSONWebKey{Key: "abc"}}))
   463  		r.Error(err)
   464  		r.Nil(token)
   465  
   466  		r.Contains(err.Error(),
   467  			"failed to merge payload and digests: json: error calling MarshalJSON for type *jwk.JWK: go-jose/go-jose: unknown key type 'string'") //nolint:lll
   468  	})
   469  
   470  	t.Run("error - create decoy disclosures failed", func(t *testing.T) {
   471  		r := require.New(t)
   472  
   473  		_, privKey, err := ed25519.GenerateKey(rand.Reader)
   474  		r.NoError(err)
   475  
   476  		token, err := New(issuer, claims, nil, afjwt.NewEd25519Signer(privKey),
   477  			WithDecoyDigests(true),
   478  			WithSaltFnc(func() (string, error) {
   479  				return "", fmt.Errorf("salt error")
   480  			}))
   481  		r.Error(err)
   482  		r.Nil(token)
   483  		r.Contains(err.Error(), "failed to create decoy disclosures: salt error")
   484  	})
   485  
   486  	t.Run("error - wrong hash function", func(t *testing.T) {
   487  		r := require.New(t)
   488  
   489  		privKey, err := rsa.GenerateKey(rand.Reader, 2048)
   490  		r.NoError(err)
   491  		token, err := New(issuer, claims, nil, afjwt.NewRS256Signer(privKey, nil),
   492  			WithJSONMarshaller(jsonMarshalWithSpace),
   493  			WithSaltFnc(func() (string, error) {
   494  				return sampleSalt, nil
   495  			}),
   496  			WithHashAlgorithm(0))
   497  		r.Error(err)
   498  		r.Nil(token)
   499  		r.Contains(err.Error(), "hash disclosure: hash function not available for: 0")
   500  	})
   501  
   502  	t.Run("error - get salt error", func(t *testing.T) {
   503  		r := require.New(t)
   504  
   505  		privKey, err := rsa.GenerateKey(rand.Reader, 2048)
   506  		r.NoError(err)
   507  		token, err := New(issuer, claims, nil, afjwt.NewRS256Signer(privKey, nil),
   508  			WithJSONMarshaller(jsonMarshalWithSpace),
   509  			WithSaltFnc(func() (string, error) {
   510  				return "", fmt.Errorf("salt error")
   511  			}))
   512  		r.Error(err)
   513  		r.Nil(token)
   514  		r.Contains(err.Error(), "create disclosure: generate salt: salt error")
   515  	})
   516  
   517  	t.Run("error - marshal error", func(t *testing.T) {
   518  		r := require.New(t)
   519  
   520  		privKey, err := rsa.GenerateKey(rand.Reader, 2048)
   521  		r.NoError(err)
   522  		token, err := New(issuer, claims, nil, afjwt.NewRS256Signer(privKey, nil),
   523  			WithJSONMarshaller(func(v interface{}) ([]byte, error) {
   524  				return nil, fmt.Errorf("marshal error")
   525  			}))
   526  		r.Error(err)
   527  		r.Nil(token)
   528  		r.Contains(err.Error(), "create disclosure: marshal disclosure: marshal error")
   529  	})
   530  }
   531  
   532  func TestNewFromVC(t *testing.T) {
   533  	r := require.New(t)
   534  
   535  	_, issuerPrivateKey, e := ed25519.GenerateKey(rand.Reader)
   536  	r.NoError(e)
   537  
   538  	signer := afjwt.NewEd25519Signer(issuerPrivateKey)
   539  
   540  	t.Run("success - structured claims + holder binding", func(t *testing.T) {
   541  		holderPublicKey, _, err := ed25519.GenerateKey(rand.Reader)
   542  		r.NoError(err)
   543  
   544  		holderPublicJWK, err := jwksupport.JWKFromKey(holderPublicKey)
   545  		require.NoError(t, err)
   546  
   547  		// create VC - we will use template here
   548  		var vc map[string]interface{}
   549  		err = json.Unmarshal([]byte(sampleVCFull), &vc)
   550  		r.NoError(err)
   551  
   552  		token, err := NewFromVC(vc, nil, signer,
   553  			WithHolderPublicKey(holderPublicJWK),
   554  			WithStructuredClaims(true),
   555  			WithNonSelectivelyDisclosableClaims([]string{"id", "degree.type"}))
   556  		r.NoError(err)
   557  
   558  		vcCombinedFormatForIssuance, err := token.Serialize(false)
   559  		r.NoError(err)
   560  
   561  		fmt.Println(fmt.Sprintf("issuer SD-JWT: %s", vcCombinedFormatForIssuance))
   562  
   563  		var vcWithSelectedDisclosures map[string]interface{}
   564  		err = token.DecodeClaims(&vcWithSelectedDisclosures)
   565  		r.NoError(err)
   566  
   567  		printObject(t, "VC with selected disclosures", vcWithSelectedDisclosures)
   568  
   569  		id, err := jsonpath.Get("$.vc.credentialSubject.id", vcWithSelectedDisclosures)
   570  		r.NoError(err)
   571  		r.Equal("did:example:ebfeb1f712ebc6f1c276e12ec21", id)
   572  
   573  		degreeType, err := jsonpath.Get("$.vc.credentialSubject.degree.type", vcWithSelectedDisclosures)
   574  		r.NoError(err)
   575  		r.Equal("BachelorDegree", degreeType)
   576  
   577  		degreeID, err := jsonpath.Get("$.vc.credentialSubject.degree.id", vcWithSelectedDisclosures)
   578  		r.Error(err)
   579  		r.Nil(degreeID)
   580  		r.Contains(err.Error(), "unknown key id")
   581  	})
   582  
   583  	t.Run("success - flat claims + holder binding", func(t *testing.T) {
   584  		holderPublicKey, _, err := ed25519.GenerateKey(rand.Reader)
   585  		r.NoError(err)
   586  
   587  		holderPublicJWK, err := jwksupport.JWKFromKey(holderPublicKey)
   588  		require.NoError(t, err)
   589  
   590  		// create VC - we will use template here
   591  		var vc map[string]interface{}
   592  		err = json.Unmarshal([]byte(sampleVCFull), &vc)
   593  		r.NoError(err)
   594  
   595  		token, err := NewFromVC(vc, nil, signer,
   596  			WithHolderPublicKey(holderPublicJWK),
   597  			WithNonSelectivelyDisclosableClaims([]string{"id"}))
   598  		r.NoError(err)
   599  
   600  		vcCombinedFormatForIssuance, err := token.Serialize(false)
   601  		r.NoError(err)
   602  
   603  		fmt.Println(fmt.Sprintf("issuer SD-JWT: %s", vcCombinedFormatForIssuance))
   604  
   605  		var vcWithSelectedDisclosures map[string]interface{}
   606  		err = token.DecodeClaims(&vcWithSelectedDisclosures)
   607  		r.NoError(err)
   608  
   609  		printObject(t, "VC with selected disclosures", vcWithSelectedDisclosures)
   610  
   611  		id, err := jsonpath.Get("$.vc.credentialSubject.id", vcWithSelectedDisclosures)
   612  		r.NoError(err)
   613  		r.Equal("did:example:ebfeb1f712ebc6f1c276e12ec21", id)
   614  	})
   615  
   616  	t.Run("error - missing credential subject", func(t *testing.T) {
   617  		vc := make(map[string]interface{})
   618  
   619  		token, err := NewFromVC(vc, nil, signer,
   620  			WithID("did:example:ebfeb1f712ebc6f1c276e12ec21"),
   621  			WithStructuredClaims(true))
   622  		r.Error(err)
   623  		r.Nil(token)
   624  
   625  		r.Contains(err.Error(), "credential subject not found")
   626  	})
   627  
   628  	t.Run("error - credential subject no an object", func(t *testing.T) {
   629  		vc := map[string]interface{}{
   630  			"vc": map[string]interface{}{
   631  				"credentialSubject": "invalid",
   632  			},
   633  		}
   634  
   635  		token, err := NewFromVC(vc, nil, signer,
   636  			WithID("did:example:ebfeb1f712ebc6f1c276e12ec21"),
   637  			WithStructuredClaims(true))
   638  		r.Error(err)
   639  		r.Nil(token)
   640  
   641  		r.Contains(err.Error(), "credential subject must be an object")
   642  	})
   643  
   644  	t.Run("error - signing error", func(t *testing.T) {
   645  		// create VC - we will use template here
   646  		var vc map[string]interface{}
   647  		err := json.Unmarshal([]byte(sampleVCFull), &vc)
   648  		r.NoError(err)
   649  
   650  		token, err := NewFromVC(vc, nil, &mockSigner{Err: fmt.Errorf("signing error")},
   651  			WithID("did:example:ebfeb1f712ebc6f1c276e12ec21"))
   652  		r.Error(err)
   653  		r.Nil(token)
   654  
   655  		r.Contains(err.Error(), "create JWS: sign JWS: sign JWS verification data: signing error")
   656  	})
   657  }
   658  
   659  func TestJSONWebToken_DecodeClaims(t *testing.T) {
   660  	token, err := getValidJSONWebToken(
   661  		WithJSONMarshaller(jsonMarshalWithSpace),
   662  		WithSaltFnc(func() (string, error) {
   663  			return sampleSalt, nil
   664  		}))
   665  	require.NoError(t, err)
   666  
   667  	var tokensMap map[string]interface{}
   668  
   669  	err = token.DecodeClaims(&tokensMap)
   670  	require.NoError(t, err)
   671  
   672  	expectedHashWithSpaces := expectedHashWithSpaces
   673  	require.True(t, existsInDisclosures(tokensMap, expectedHashWithSpaces))
   674  
   675  	var claims Claims
   676  
   677  	err = token.DecodeClaims(&claims)
   678  	require.NoError(t, err)
   679  	require.Equal(t, claims.Issuer, issuer)
   680  
   681  	token, err = getJSONWebTokenWithInvalidPayload()
   682  	require.NoError(t, err)
   683  
   684  	err = token.DecodeClaims(&claims)
   685  	require.Error(t, err)
   686  }
   687  
   688  func TestJSONWebToken_LookupStringHeader(t *testing.T) {
   689  	token, err := getValidJSONWebToken()
   690  	require.NoError(t, err)
   691  
   692  	require.Equal(t, "JWT", token.LookupStringHeader("typ"))
   693  
   694  	require.Empty(t, token.LookupStringHeader("undef"))
   695  
   696  	token.SignedJWT.Headers["not_str"] = 55
   697  	require.Empty(t, token.LookupStringHeader("not_str"))
   698  }
   699  
   700  func TestJSONWebToken_Serialize(t *testing.T) {
   701  	token, err := getValidJSONWebToken()
   702  	require.NoError(t, err)
   703  
   704  	tokenSerialized, err := token.Serialize(false)
   705  	require.NoError(t, err)
   706  	require.NotEmpty(t, tokenSerialized)
   707  
   708  	// cannot serialize without signature
   709  	token.SignedJWT = nil
   710  	tokenSerialized, err = token.Serialize(false)
   711  	require.Error(t, err)
   712  	require.EqualError(t, err, "JWS serialization is supported only")
   713  	require.Empty(t, tokenSerialized)
   714  }
   715  
   716  func TestJSONWebToken_hashDisclosure(t *testing.T) {
   717  	t.Run("success - data from spec", func(t *testing.T) {
   718  		dh, err := common.GetHash(defaultHash, "WyI2cU1RdlJMNWhhaiIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0")
   719  		require.NoError(t, err)
   720  		require.Equal(t, "uutlBuYeMDyjLLTpf6Jxi7yNkEF35jdyWMn9U7b_RYY", dh)
   721  	})
   722  }
   723  
   724  func TestJSONWebToken_createDisclosure(t *testing.T) {
   725  	t.Run("success - given name", func(t *testing.T) {
   726  		nOpts := getOpts(
   727  			WithJSONMarshaller(jsonMarshalWithSpace),
   728  			WithSaltFnc(func() (string, error) {
   729  				return sampleSalt, nil
   730  			}))
   731  
   732  		// Disclosure data from spec: ["3jqcb67z9wks08zwiK7EyQ", "given_name", "John"]
   733  		expectedDisclosureWithSpaces := "WyIzanFjYjY3ejl3a3MwOHp3aUs3RXlRIiwgImdpdmVuX25hbWUiLCAiSm9obiJd"
   734  		expectedHashWithSpaces := expectedHashWithSpaces
   735  
   736  		disclosure, err := createDisclosure("given_name", "John", nOpts)
   737  		require.NoError(t, err)
   738  		require.Equal(t, expectedDisclosureWithSpaces, disclosure)
   739  
   740  		dh, err := common.GetHash(defaultHash, disclosure)
   741  		require.NoError(t, err)
   742  		require.Equal(t, expectedHashWithSpaces, dh)
   743  	})
   744  
   745  	t.Run("success - family name", func(t *testing.T) {
   746  		// Disclosure data from spec: ["_26bc4LT-ac6q2KI6cBW5es", "family_name", "Möbius"]
   747  
   748  		expectedDisclosureWithoutSpaces := "WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsImZhbWlseV9uYW1lIiwiTcO2Yml1cyJd"
   749  		expectedDisclosureWithSpaces := "WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0"
   750  
   751  		nOpts := getOpts(
   752  			WithSaltFnc(func() (string, error) {
   753  				return "_26bc4LT-ac6q2KI6cBW5es", nil
   754  			}))
   755  
   756  		disclosure, err := createDisclosure("family_name", "Möbius", nOpts)
   757  		require.NoError(t, err)
   758  		require.Equal(t, expectedDisclosureWithoutSpaces, disclosure)
   759  
   760  		nOpts = getOpts(
   761  			WithJSONMarshaller(jsonMarshalWithSpace),
   762  			WithSaltFnc(func() (string, error) {
   763  				return "_26bc4LT-ac6q2KI6cBW5es", nil
   764  			}))
   765  
   766  		disclosure, err = createDisclosure("family_name", "Möbius", nOpts)
   767  		require.NoError(t, err)
   768  		require.Equal(t, expectedDisclosureWithSpaces, disclosure)
   769  	})
   770  }
   771  
   772  func getOpts(opts ...NewOpt) *newOpts {
   773  	nOpts := &newOpts{
   774  		jsonMarshal: json.Marshal,
   775  		getSalt:     generateSalt,
   776  		HashAlg:     defaultHash,
   777  	}
   778  
   779  	for _, opt := range opts {
   780  		opt(nOpts)
   781  	}
   782  
   783  	return nOpts
   784  }
   785  
   786  func getValidJSONWebToken(opts ...NewOpt) (*SelectiveDisclosureJWT, error) {
   787  	headers := map[string]interface{}{"typ": "JWT", "alg": "EdDSA"}
   788  	claims := map[string]interface{}{"given_name": "John"}
   789  
   790  	_, privKey, err := ed25519.GenerateKey(rand.Reader)
   791  	if err != nil {
   792  		return nil, err
   793  	}
   794  
   795  	signer := afjwt.NewEd25519Signer(privKey)
   796  
   797  	return New(issuer, claims, headers, signer, opts...)
   798  }
   799  
   800  func getJSONWebTokenWithInvalidPayload() (*SelectiveDisclosureJWT, error) {
   801  	token, err := getValidJSONWebToken()
   802  	if err != nil {
   803  		return nil, err
   804  	}
   805  
   806  	// hack the token
   807  	token.SignedJWT.Payload = getUnmarshallableMap()
   808  
   809  	return token, nil
   810  }
   811  
   812  func verifyEd25519ViaGoJose(jws string, pubKey ed25519.PublicKey, claims interface{}) error {
   813  	jwtToken, err := jwt.ParseSigned(jws)
   814  	if err != nil {
   815  		return fmt.Errorf("parse VC from signed JWS: %w", err)
   816  	}
   817  
   818  	if err = jwtToken.Claims(pubKey, claims); err != nil {
   819  		return fmt.Errorf("verify JWT signature: %w", err)
   820  	}
   821  
   822  	return nil
   823  }
   824  
   825  func verifyRS256ViaGoJose(jws string, pubKey *rsa.PublicKey, claims interface{}) error {
   826  	jwtToken, err := jwt.ParseSigned(jws)
   827  	if err != nil {
   828  		return fmt.Errorf("parse VC from signed JWS: %w", err)
   829  	}
   830  
   831  	if err = jwtToken.Claims(pubKey, claims); err != nil {
   832  		return fmt.Errorf("verify JWT signature: %w", err)
   833  	}
   834  
   835  	return nil
   836  }
   837  
   838  func getUnmarshallableMap() map[string]interface{} {
   839  	return map[string]interface{}{"error": map[chan int]interface{}{make(chan int): 6}}
   840  }
   841  
   842  func createClaims() map[string]interface{} {
   843  	claims := map[string]interface{}{
   844  		"given_name": "John",
   845  	}
   846  
   847  	return claims
   848  }
   849  
   850  func createComplexClaims() map[string]interface{} {
   851  	claims := map[string]interface{}{
   852  		"sub":          "john_doe_42",
   853  		"given_name":   "John",
   854  		"family_name":  "Doe",
   855  		"email":        "johndoe@example.com",
   856  		"phone_number": "+1-202-555-0101",
   857  		"birthdate":    "1940-01-01",
   858  		"address": map[string]interface{}{
   859  			"street_address": "123 Main St",
   860  			"locality":       "Anytown",
   861  			"region":         "Anystate",
   862  			"country":        "US",
   863  		},
   864  	}
   865  
   866  	return claims
   867  }
   868  
   869  func verifyEd25519(jws string, pubKey ed25519.PublicKey) error {
   870  	v, err := afjwt.NewEd25519Verifier(pubKey)
   871  	if err != nil {
   872  		return err
   873  	}
   874  
   875  	sVerifier := afjose.NewCompositeAlgSigVerifier(afjose.AlgSignatureVerifier{
   876  		Alg:      "EdDSA",
   877  		Verifier: v,
   878  	})
   879  
   880  	token, _, err := afjwt.Parse(jws, afjwt.WithSignatureVerifier(sVerifier))
   881  	if err != nil {
   882  		return err
   883  	}
   884  
   885  	if token == nil {
   886  		return errors.New("nil token")
   887  	}
   888  
   889  	return nil
   890  }
   891  
   892  func verifyRS256(jws string, pubKey *rsa.PublicKey) error {
   893  	v := afjwt.NewRS256Verifier(pubKey)
   894  
   895  	sVerifier := afjose.NewCompositeAlgSigVerifier(afjose.AlgSignatureVerifier{
   896  		Alg:      "RS256",
   897  		Verifier: v,
   898  	})
   899  
   900  	token, _, err := afjwt.Parse(jws, afjwt.WithSignatureVerifier(sVerifier))
   901  	if err != nil {
   902  		return err
   903  	}
   904  
   905  	if token == nil {
   906  		return errors.New("nil token")
   907  	}
   908  
   909  	return nil
   910  }
   911  
   912  func existsInDisclosures(claims map[string]interface{}, val string) bool {
   913  	disclosuresObj, ok := claims[common.SDKey]
   914  	if !ok {
   915  		return false
   916  	}
   917  
   918  	disclosures, ok := disclosuresObj.([]interface{})
   919  	if !ok {
   920  		return false
   921  	}
   922  
   923  	for _, d := range disclosures {
   924  		if d.(string) == val {
   925  			return true
   926  		}
   927  	}
   928  
   929  	return false
   930  }
   931  
   932  func jsonMarshalWithSpace(v interface{}) ([]byte, error) {
   933  	vBytes, err := json.Marshal(v)
   934  	if err != nil {
   935  		return nil, err
   936  	}
   937  
   938  	return []byte(strings.ReplaceAll(string(vBytes), ",", ", ")), nil
   939  }
   940  
   941  func prettyPrint(msg []byte) (string, error) {
   942  	var prettyJSON bytes.Buffer
   943  
   944  	err := json.Indent(&prettyJSON, msg, "", "\t")
   945  	if err != nil {
   946  		return "", err
   947  	}
   948  
   949  	return prettyJSON.String(), nil
   950  }
   951  
   952  func printObject(t *testing.T, name string, obj interface{}) {
   953  	t.Helper()
   954  
   955  	objBytes, err := json.Marshal(obj)
   956  	require.NoError(t, err)
   957  
   958  	prettyJSON, err := prettyPrint(objBytes)
   959  	require.NoError(t, err)
   960  
   961  	fmt.Println(name + ":")
   962  	fmt.Println(prettyJSON)
   963  }
   964  
   965  // Signer defines JWS Signer interface. It makes signing of data and provides custom JWS headers relevant to the signer.
   966  type mockSigner struct {
   967  	Err error
   968  }
   969  
   970  // Sign signs.
   971  func (m *mockSigner) Sign(_ []byte) ([]byte, error) {
   972  	if m.Err != nil {
   973  		return nil, m.Err
   974  	}
   975  
   976  	return nil, nil
   977  }
   978  
   979  // Headers provides JWS headers.
   980  func (m *mockSigner) Headers() afjose.Headers {
   981  	headers := make(afjose.Headers)
   982  	headers["alg"] = "EdDSA"
   983  
   984  	return headers
   985  }
   986  
   987  const sampleVCFull = `
   988  {
   989  	"iat": 1673987547,
   990  	"iss": "did:example:76e12ec712ebc6f1c221ebfeb1f",
   991  	"jti": "http://example.edu/credentials/1872",
   992  	"nbf": 1673987547,
   993  	"sub": "did:example:ebfeb1f712ebc6f1c276e12ec21",
   994  	"vc": {
   995  		"@context": [
   996  			"https://www.w3.org/2018/credentials/v1"
   997  		],
   998  		"credentialSubject": {
   999  			"degree": {
  1000  				"degree": "MIT",
  1001  				"type": "BachelorDegree",
  1002  				"id": "some-id"
  1003  			},
  1004  			"name": "Jayden Doe",
  1005  			"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
  1006  			"spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1"
  1007  		},
  1008  		"first_name": "First name",
  1009  		"id": "http://example.edu/credentials/1872",
  1010  		"info": "Info",
  1011  		"issuanceDate": "2023-01-17T22:32:27.468109817+02:00",
  1012  		"issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
  1013  		"last_name": "Last name",
  1014  		"type": "VerifiableCredential"
  1015  	}
  1016  }`