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