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 }`