github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/verifiable/credential_ldp_test.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 SPDX-License-Identifier: Apache-2.0 4 */ 5 6 package verifiable 7 8 import ( 9 "crypto/ed25519" 10 "crypto/sha256" 11 "encoding/base64" 12 "encoding/json" 13 "errors" 14 "fmt" 15 "strings" 16 "testing" 17 18 "github.com/btcsuite/btcutil/base58" 19 "github.com/google/uuid" 20 "github.com/stretchr/testify/require" 21 22 "github.com/hyperledger/aries-framework-go/pkg/crypto/primitive/bbs12381g2pub" 23 "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto" 24 "github.com/hyperledger/aries-framework-go/pkg/doc/jose/jwk" 25 "github.com/hyperledger/aries-framework-go/pkg/doc/jose/jwk/jwksupport" 26 "github.com/hyperledger/aries-framework-go/pkg/doc/ldcontext" 27 jsonldsig "github.com/hyperledger/aries-framework-go/pkg/doc/signature/jsonld" 28 "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite" 29 "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/bbsblssignature2020" 30 "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/bbsblssignatureproof2020" 31 "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/ecdsasecp256k1signature2019" 32 "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/ed25519signature2018" 33 "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/ed25519signature2020" 34 "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/jsonwebsignature2020" 35 sigverifier "github.com/hyperledger/aries-framework-go/pkg/doc/signature/verifier" 36 jsonutil "github.com/hyperledger/aries-framework-go/pkg/doc/util/json" 37 "github.com/hyperledger/aries-framework-go/pkg/kms" 38 "github.com/hyperledger/aries-framework-go/pkg/kms/localkms" 39 ) 40 41 func TestParseCredentialFromLinkedDataProof_Ed25519Signature2018(t *testing.T) { 42 r := require.New(t) 43 44 signer, err := newCryptoSigner(kms.ED25519Type) 45 r.NoError(err) 46 47 sigSuite := ed25519signature2018.New( 48 suite.WithSigner(signer), 49 suite.WithVerifier(ed25519signature2018.NewPublicKeyVerifier())) 50 51 ldpContext := &LinkedDataProofContext{ 52 SignatureType: "Ed25519Signature2018", 53 SignatureRepresentation: SignatureProofValue, 54 Suite: sigSuite, 55 VerificationMethod: "did:example:123456#key1", 56 } 57 58 vc, err := parseTestCredential(t, []byte(validCredential)) 59 r.NoError(err) 60 61 err = vc.AddLinkedDataProof(ldpContext, jsonldsig.WithDocumentLoader(createTestDocumentLoader(t))) 62 r.NoError(err) 63 64 vcBytes, err := json.Marshal(vc) 65 r.NoError(err) 66 67 vcWithLdp, err := parseTestCredential(t, vcBytes, 68 WithEmbeddedSignatureSuites(sigSuite), 69 WithPublicKeyFetcher(SingleKey(signer.PublicKeyBytes(), kms.ED25519))) 70 r.NoError(err) 71 r.Equal(vc, vcWithLdp) 72 } 73 74 func TestParseCredentialFromLinkedDataProof_Ed25519Signature2020(t *testing.T) { 75 r := require.New(t) 76 77 signer, err := newCryptoSigner(kms.ED25519Type) 78 r.NoError(err) 79 80 sigSuite := ed25519signature2020.New( 81 suite.WithSigner(signer), 82 suite.WithVerifier(ed25519signature2020.NewPublicKeyVerifier())) 83 84 ldpContext := &LinkedDataProofContext{ 85 SignatureType: "Ed25519Signature2020", 86 SignatureRepresentation: SignatureProofValue, 87 Suite: sigSuite, 88 VerificationMethod: "did:example:123456#key1", 89 } 90 91 vc, err := parseTestCredential(t, []byte(validCredential)) 92 r.NoError(err) 93 94 err = vc.AddLinkedDataProof(ldpContext, jsonldsig.WithDocumentLoader(createTestDocumentLoader(t))) 95 r.NoError(err) 96 97 vcBytes, err := json.Marshal(vc) 98 r.NoError(err) 99 100 vcWithLdp, err := parseTestCredential(t, vcBytes, 101 WithEmbeddedSignatureSuites(sigSuite), 102 WithPublicKeyFetcher(SingleKey(signer.PublicKeyBytes(), kms.ED25519))) 103 r.NoError(err) 104 r.Equal(vc, vcWithLdp) 105 } 106 107 //nolint:lll 108 func TestParseCredentialFromLinkedDataProof_JSONLD_Validation(t *testing.T) { 109 r := require.New(t) 110 111 pubKeyBytes := base58.Decode("DqS5F3GVe3rCxucgi4JBNagjv4dKoHc8TDLDw9kR58Pz") 112 113 localCrypto, err := createLocalCrypto() 114 r.NoError(err) 115 116 sigSuite := ed25519signature2018.New( 117 suite.WithVerifier(suite.NewCryptoVerifier(localCrypto))) 118 119 vcOptions := []CredentialOpt{ 120 WithEmbeddedSignatureSuites(sigSuite), 121 WithPublicKeyFetcher(SingleKey(pubKeyBytes, "Ed25519Signature2018")), 122 WithStrictValidation(), 123 } 124 125 t.Run("valid VC", func(t *testing.T) { 126 vcJSON := ` 127 { 128 "@context": [ 129 "https://www.w3.org/2018/credentials/v1", 130 "https://www.w3.org/2018/credentials/examples/v1" 131 ], 132 "type": [ 133 "VerifiableCredential", 134 "UniversityDegreeCredential" 135 ], 136 "id": "http://example.gov/credentials/3732", 137 "issuanceDate": "2020-03-16T22:37:26.544Z", 138 "credentialSubject": { 139 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 140 "degree": { 141 "type": "BachelorDegree", 142 "degree": "MIT" 143 }, 144 "name": "Jayden Doe", 145 "spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1" 146 }, 147 "profile": "", 148 "issuer": "did:web:vc.transmute.world", 149 "proof": { 150 "type": "Ed25519Signature2018", 151 "created": "2019-12-11T03:50:55Z", 152 "verificationMethod": "did:web:vc.transmute.world#z6MksHh7qHWvybLg5QTPPdG2DgEjjduBDArV9EF9mRiRzMBN", 153 "proofPurpose": "assertionMethod", 154 "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..MlJy4Sn47kgse7SKc56OKkJUhu-Z3CPiv2_MdjOQXJk8Bpzxa-JuinjJNN3YkYb6tPE6poIhBTlgnc_c5qQsBA" 155 } 156 } 157 ` 158 159 vcWithLdp, err := parseTestCredential(t, []byte(vcJSON), vcOptions...) 160 r.NoError(err) 161 r.NotNil(t, vcWithLdp) 162 }) 163 164 t.Run("VC with unknown field", func(t *testing.T) { 165 // "newProp" is a field not defined in any context. 166 vcJSON := ` 167 { 168 "@context": [ 169 "https://www.w3.org/2018/credentials/v1", 170 "https://www.w3.org/2018/credentials/examples/v1" 171 ], 172 "type": [ 173 "VerifiableCredential", 174 "UniversityDegreeCredential" 175 ], 176 "id": "http://example.gov/credentials/3732", 177 "issuanceDate": "2020-03-16T22:37:26.544Z", 178 "credentialSubject": { 179 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 180 "degree": { 181 "type": "BachelorDegree", 182 "degree": "MIT" 183 }, 184 "name": "Jayden Doe", 185 "spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1" 186 }, 187 "profile": "", 188 "issuer": "did:web:vc.transmute.world", 189 "proof": { 190 "type": "Ed25519Signature2018", 191 "created": "2019-12-11T03:50:55Z", 192 "verificationMethod": "did:web:vc.transmute.world#z6MksHh7qHWvybLg5QTPPdG2DgEjjduBDArV9EF9mRiRzMBN", 193 "proofPurpose": "assertionMethod", 194 "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..MlJy4Sn47kgse7SKc56OKkJUhu-Z3CPiv2_MdjOQXJk8Bpzxa-JuinjJNN3YkYb6tPE6poIhBTlgnc_c5qQsBA" 195 }, 196 "newProp": "foo" 197 } 198 ` 199 200 vcWithLdp, err := parseTestCredential(t, []byte(vcJSON), vcOptions...) 201 r.Error(err) 202 r.EqualError(err, "JSON-LD doc has different structure after compaction") 203 r.Nil(vcWithLdp) 204 }) 205 206 t.Run("VC with unknown proof field", func(t *testing.T) { 207 // "newProp" is a field not defined in any context. 208 vcJSON := ` 209 { 210 "@context": [ 211 "https://www.w3.org/2018/credentials/v1", 212 "https://www.w3.org/2018/credentials/examples/v1" 213 ], 214 "type": [ 215 "VerifiableCredential", 216 "UniversityDegreeCredential" 217 ], 218 "id": "http://example.gov/credentials/3732", 219 "issuanceDate": "2020-03-16T22:37:26.544Z", 220 "credentialSubject": { 221 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 222 "degree": { 223 "type": "BachelorDegree", 224 "degree": "MIT" 225 }, 226 "name": "Jayden Doe", 227 "spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1" 228 }, 229 "profile": "", 230 "issuer": "did:web:vc.transmute.world", 231 "proof": { 232 "type": "Ed25519Signature2018", 233 "created": "2019-12-11T03:50:55Z", 234 "verificationMethod": "did:web:vc.transmute.world#z6MksHh7qHWvybLg5QTPPdG2DgEjjduBDArV9EF9mRiRzMBN", 235 "proofPurpose": "assertionMethod", 236 "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..MlJy4Sn47kgse7SKc56OKkJUhu-Z3CPiv2_MdjOQXJk8Bpzxa-JuinjJNN3YkYb6tPE6poIhBTlgnc_c5qQsBA", 237 "newProp": "foo" 238 } 239 } 240 ` 241 242 vcWithLdp, err := parseTestCredential(t, []byte(vcJSON), vcOptions...) 243 r.Error(err) 244 r.EqualError(err, "JSON-LD doc has different structure after compaction") 245 r.Nil(vcWithLdp) 246 }) 247 248 t.Run("VC with different mapped field", func(t *testing.T) { 249 localJSONLDContext := ` 250 { 251 "@context": 252 { 253 "@version": 1.1, 254 "xsd": "http://www.w3.org/2001/XMLSchema#", 255 "schema": "http://schema.org/", 256 "comments": "schema:text" 257 } 258 } 259 ` 260 261 docLoader := createTestDocumentLoader(t, ldcontext.Document{ 262 URL: "http://localhost:9191/example.jsonld", 263 Content: []byte(localJSONLDContext), 264 }) 265 266 vcJSON := ` 267 { 268 "@context": [ 269 "https://www.w3.org/2018/credentials/v1", 270 "http://localhost:9191/example.jsonld" 271 ], 272 "id": "http://neo-flow.com/credentials/e94a16cb-35b2-4301-9fb6-7af3d8fe7b81", 273 "type": ["VerifiableCredential", "BillOfLadingCredential"], 274 "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f", 275 "issuanceDate": "2020-04-09T21:13:13Z", 276 "credentialSubject": { 277 "id": "https://example.edu/status/24", 278 "comments": "" 279 }, 280 "proof": { 281 "type": "Ed25519Signature2018", 282 "created": "2020-04-26T20:14:44Z", 283 "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..LFKayh8S3hxHc2hZJP-ARH6qZO06pBUJgPg9osvH2OD-OftB-SvIv3Tni_j0fVwK5iYWfChAs8Cvw-czQ2S1Dw", 284 "proofPurpose": "assertionMethod", 285 "verificationMethod": "did:v1:test:nym:z6MkfG5HTrBXzsAP8AbayNpG3ZaoyM4PCqNPrdWQRSpHDV6J#z6MkqfvdBsFw4QdGrZrnx7L1EKfY5zh9tT4gumUGsMMEZHY3" 286 } 287 } 288 ` 289 290 vc, err := parseTestCredential(t, []byte(vcJSON), 291 WithDisabledProofCheck(), 292 WithStrictValidation(), 293 WithJSONLDDocumentLoader(docLoader), 294 ) 295 require.NoError(t, err) 296 require.NotNil(t, vc) 297 }) 298 } 299 300 //nolint:lll 301 func TestWithStrictValidationOfJsonWebSignature2020(t *testing.T) { 302 vcJSON := ` 303 { 304 "@context": [ 305 "https://www.w3.org/2018/credentials/v1", 306 "https://www.w3.org/2018/credentials/examples/v1", 307 "https://trustbloc.github.io/context/vc/examples-v1.jsonld" 308 ], 309 "credentialStatus": { 310 "id": "http://issuer.vc.rest.example.com:8070/status/1", 311 "type": "CredentialStatusList2017" 312 }, 313 "credentialSubject": { 314 "degree": { 315 "degree": "MIT", 316 "type": "BachelorDegree" 317 }, 318 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", 319 "name": "Jayden Doe", 320 "spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1" 321 }, 322 "id": "https://example.com/credentials/720df5b8-d6c9-47e6-a024-0abc1507e549", 323 "issuanceDate": "2020-03-16T22:37:26.544Z", 324 "issuer": { 325 "id": "did:example:76e12ec712ebc6f1c221ebfeb1f", 326 "name": "Example University" 327 }, 328 "proof": { 329 "created": "2021-04-23T20:01:46.987287+03:00", 330 "jws": "eyJhbGciOiJKc29uV2ViU2lnbmF0dXJlMjAyMCIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..MQIszCkfU3EfFEor_TQ5-BDhQYd9pH6fqY2cHHmaNt5bYkJL15IzA8OZPDOk8YvLLxhQv1ZS1V32JkKdHvePBw", 331 "proofPurpose": "assertionMethod", 332 "type": "JsonWebSignature2020", 333 "verificationMethod": "did:key:z6MknC1wwS6DEYwtGbZZo2QvjQjkh2qSBjb4GYmbye8dv4S5#z6MknC1wwS6DEYwtGbZZo2QvjQjkh2qSBjb4GYmbye8dv4S5" 334 }, 335 "type": [ 336 "VerifiableCredential", 337 "UniversityDegreeCredential" 338 ] 339 }` 340 sigSuite := jsonwebsignature2020.New( 341 suite.WithVerifier(jsonwebsignature2020.NewPublicKeyVerifier())) 342 343 decoded, err := base64.StdEncoding.DecodeString("cvXX3pUdyfEgL2k73NtHOxPX0T4NyABBAfthTYKtFkI=") 344 require.NoError(t, err) 345 346 publicKey := make([]byte, ed25519.PublicKeySize) 347 copy(publicKey[0:32], decoded) 348 rv := ed25519.PublicKey(publicKey) 349 350 j, err := jwksupport.JWKFromKey(rv) 351 require.NoError(t, err) 352 353 vcWithLdp, err := parseTestCredential(t, []byte(vcJSON), 354 WithEmbeddedSignatureSuites(sigSuite), 355 WithPublicKeyFetcher(func(issuerID, keyID string) (*sigverifier.PublicKey, error) { 356 return &sigverifier.PublicKey{ 357 Type: "JsonWebKey2020", 358 JWK: j, 359 }, nil 360 }), 361 WithExternalJSONLDContext("https://w3id.org/security/jws/v1"), 362 WithStrictValidation()) 363 364 require.NoError(t, err) 365 require.NotNil(t, vcWithLdp) 366 } 367 368 func TestExtraContextWithLDP(t *testing.T) { 369 r := require.New(t) 370 371 vcJSON := ` 372 { 373 "@context": [ 374 "https://www.w3.org/2018/credentials/v1", 375 "https://trustbloc.github.io/context/vc/examples-v1.jsonld" 376 ], 377 "id": "http://example.edu/credentials/3732", 378 "type": ["VerifiableCredential", "SupportingActivity"], 379 "issuer": "https://example.edu/issuers/14", 380 "issuanceDate": "2010-01-01T19:23:24Z", 381 "credentialSubject": { 382 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" 383 }, 384 "credentialStatus": { 385 "id": "https://example.edu/status/24", 386 "type": "CredentialStatusList2017" 387 } 388 }` 389 390 signer, err := newCryptoSigner(kms.ED25519Type) 391 r.NoError(err) 392 393 sigSuite := ed25519signature2018.New( 394 suite.WithSigner(signer), 395 suite.WithVerifier(ed25519signature2018.NewPublicKeyVerifier())) 396 397 ldpContext := &LinkedDataProofContext{ 398 SignatureType: "Ed25519Signature2018", 399 SignatureRepresentation: SignatureProofValue, 400 Suite: sigSuite, 401 VerificationMethod: "did:example:123456#key1", 402 } 403 404 vc, err := parseTestCredential(t, []byte(vcJSON)) 405 r.NoError(err) 406 407 err = vc.AddLinkedDataProof(ldpContext, jsonldsig.WithDocumentLoader(createTestDocumentLoader(t))) 408 r.NoError(err) 409 410 vcBytes, err := json.Marshal(vc) 411 r.NoError(err) 412 413 vcWithLdp, err := parseTestCredential(t, vcBytes, 414 WithEmbeddedSignatureSuites(sigSuite), 415 WithPublicKeyFetcher(SingleKey(signer.PublicKeyBytes(), kms.ED25519)), 416 WithStrictValidation()) 417 r.NoError(err) 418 r.Equal(vc, vcWithLdp) 419 r.NotNil(vcWithLdp) 420 421 // Drop https://trustbloc.github.io/context/vc/examples-v1.jsonld context where 422 // SupportingActivity and CredentialStatusList2017 are defined. 423 vcMap, err := jsonutil.ToMap(vcBytes) 424 r.NoError(err) 425 426 vcMap["@context"] = baseContext 427 vcBytes, err = json.Marshal(vcMap) 428 r.NoError(err) 429 430 vcWithLdp, err = parseTestCredential(t, vcBytes, 431 WithEmbeddedSignatureSuites(sigSuite), 432 WithPublicKeyFetcher(SingleKey(signer.PublicKeyBytes(), kms.ED25519)), 433 WithStrictValidation()) 434 r.Error(err) 435 r.EqualError(err, "decode new credential: check embedded proof: check linked data proof: invalid JSON-LD context") 436 r.Nil(vcWithLdp) 437 438 // Use extra context. 439 vcWithLdp, err = parseTestCredential(t, vcBytes, 440 WithEmbeddedSignatureSuites(sigSuite), 441 WithPublicKeyFetcher(SingleKey(signer.PublicKeyBytes(), kms.ED25519)), 442 WithExternalJSONLDContext("https://trustbloc.github.io/context/vc/examples-v1.jsonld"), 443 WithStrictValidation()) 444 r.NoError(err) 445 r.NotNil(vcWithLdp) 446 447 // Use extra context. 448 vcWithLdp, err = parseTestCredential(t, vcBytes, 449 WithEmbeddedSignatureSuites(sigSuite), 450 WithPublicKeyFetcher(SingleKey(signer.PublicKeyBytes(), kms.ED25519)), 451 WithExternalJSONLDContext("https://trustbloc.github.io/context/vc/examples-v1.jsonld"), 452 WithStrictValidation()) 453 r.NoError(err) 454 r.NotNil(vcWithLdp) 455 456 // Use extra in-memory context. 457 dummyContext := ` 458 { 459 "@context": { 460 "@version": 1.1, 461 462 "id": "@id", 463 "type": "@type", 464 465 "ex": "https://example.org/examples#", 466 467 "CredentialStatusList2017": "ex:CredentialStatusList2017", 468 "DocumentVerification": "ex:DocumentVerification", 469 "SupportingActivity": "ex:SupportingActivity" 470 } 471 } 472 ` 473 loader := createTestDocumentLoader(t, ldcontext.Document{ 474 URL: "http://localhost:8652/dummy.jsonld", 475 Content: []byte(dummyContext), 476 }) 477 478 vcWithLdp, err = ParseCredential(vcBytes, 479 WithEmbeddedSignatureSuites(sigSuite), 480 WithPublicKeyFetcher(SingleKey(signer.PublicKeyBytes(), kms.ED25519)), 481 WithExternalJSONLDContext("http://localhost:8652/dummy.jsonld"), 482 WithJSONLDDocumentLoader(loader), 483 WithStrictValidation()) 484 r.NoError(err) 485 r.NotNil(vcWithLdp) 486 } 487 488 func TestParseCredentialFromLinkedDataProof_BbsBlsSignature2020(t *testing.T) { 489 r := require.New(t) 490 491 pubKey, privKey, err := bbs12381g2pub.GenerateKeyPair(sha256.New, nil) 492 r.NoError(err) 493 494 bbsSigner, err := newBBSSigner(privKey) 495 r.NoError(err) 496 497 sigSuite := bbsblssignature2020.New( 498 suite.WithSigner(bbsSigner), 499 suite.WithVerifier(bbsblssignature2020.NewG2PublicKeyVerifier())) 500 501 ldpContext := &LinkedDataProofContext{ 502 SignatureType: "BbsBlsSignature2020", 503 SignatureRepresentation: SignatureProofValue, 504 Suite: sigSuite, 505 VerificationMethod: "did:example:123456#key1", 506 } 507 508 vcJSON := ` 509 { 510 "@context": [ 511 "https://www.w3.org/2018/credentials/v1", 512 "https://w3id.org/citizenship/v1", 513 "https://w3id.org/security/bbs/v1" 514 ], 515 "id": "https://issuer.oidp.uscis.gov/credentials/83627465", 516 "type": [ 517 "VerifiableCredential", 518 "PermanentResidentCard" 519 ], 520 "issuer": "did:example:489398593", 521 "identifier": "83627465", 522 "name": "Permanent Resident Card", 523 "description": "Government of Example Permanent Resident Card.", 524 "issuanceDate": "2019-12-03T12:19:52Z", 525 "expirationDate": "2029-12-03T12:19:52Z", 526 "credentialSubject": { 527 "id": "did:example:b34ca6cd37bbf23", 528 "type": [ 529 "PermanentResident", 530 "Person" 531 ], 532 "givenName": "JOHN", 533 "familyName": "SMITH", 534 "gender": "Male", 535 "image": "data:image/png;base64,iVBORw0KGgokJggg==", 536 "residentSince": "2015-01-01", 537 "lprCategory": "C09", 538 "lprNumber": "999-999-999", 539 "commuterClassification": "C1", 540 "birthCountry": "Bahamas", 541 "birthDate": "1958-07-17" 542 } 543 } 544 ` 545 546 vc, err := parseTestCredential(t, []byte(vcJSON)) 547 r.NoError(err) 548 r.Len(vc.Proofs, 0) 549 550 err = vc.AddLinkedDataProof(ldpContext, jsonldsig.WithDocumentLoader(createTestDocumentLoader(t))) 551 r.NoError(err) 552 r.Len(vc.Proofs, 1) 553 r.Equal("BbsBlsSignature2020", vc.Proofs[0]["type"]) 554 r.NotEmpty(vc.Proofs[0]["proofValue"]) 555 556 vcBytes, err := json.Marshal(vc) 557 r.NoError(err) 558 r.NotEmpty(vcBytes) 559 560 pubKeyBytes, err := pubKey.Marshal() 561 r.NoError(err) 562 563 vcVerified, err := parseTestCredential(t, vcBytes, 564 WithEmbeddedSignatureSuites(sigSuite), 565 WithPublicKeyFetcher(SingleKey(pubKeyBytes, "Bls12381G2Key2020")), 566 ) 567 r.NoError(err) 568 r.NotNil(vcVerified) 569 r.Equal(vc, vcVerified) 570 } 571 572 //nolint:lll 573 func TestParseCredentialFromLinkedDataProof_BbsBlsSignatureProof2020(t *testing.T) { 574 r := require.New(t) 575 576 // Case 17 (https://github.com/w3c-ccg/vc-http-api/pull/128) 577 vcJSON := `{ 578 "@context": [ 579 "https://www.w3.org/2018/credentials/v1", 580 "https://w3id.org/citizenship/v1", 581 "https://w3id.org/security/bbs/v1" 582 ], 583 "id": "https://issuer.oidp.uscis.gov/credentials/83627465", 584 "type": [ 585 "PermanentResidentCard", 586 "VerifiableCredential" 587 ], 588 "description": "Government of Example Permanent Resident Card.", 589 "name": "Permanent Resident Card", 590 "credentialSubject": { 591 "id": "did:example:b34ca6cd37bbf23", 592 "type": [ 593 "Person", 594 "PermanentResident" 595 ], 596 "birthDate": "1958-07-17" 597 }, 598 "expirationDate": "2029-12-03T12:19:52Z", 599 "issuanceDate": "2019-12-03T12:19:52Z", 600 "issuer": "did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2", 601 "proof": { 602 "type": "BbsBlsSignatureProof2020", 603 "created": "2021-02-23T19:31:12Z", 604 "nonce": "G/hn9Ca9bIWZpJGlhnr/41r8RB0OO0TLChZASr3QJVztdri/JzS8Zf/xWJT5jW78zlM=", 605 "proofPurpose": "assertionMethod", 606 "proofValue": "ABgA/wYfjSxZz8DBQHTIuX+F0MmeskKbywg6NSMGHOqJ9LvYrfaakmMaPh+UsJxIK1z5v3NuiRP4OGhIbYgjo0KovKMZzluSzCGwzAyXui2hnFlrySj3RP+WNmWd+6QZQ6bEm+pyhNC6VrEMVDxJ2TH7DShbx6GFQ6RLvuS0Xf38GuOhX26+5RJ9RBs5Qaj4/UKsTfc9AAAAdKGdxxloz3ZJ2QnoFlqicO6MviT8yzeyf5gILHg8YUjNIAVJJNsh26kBqIdQkaROpQAAAAIVX5Y1Jy9hgEQgqUld/aGN2uxOLZAJsri9BRRHoFNWkkcF73EV4BE9+Hs+8fuvX0SNDAmomTVz6vSrq58bjHZ+tmJ5JddwT1tCunHV330hqleI47eAqwGuY9hdeSixzfL0/CGnZ2XoV2YAybVTcupSAAAACw03E8CoLBvqXeMV7EtRTwMpKQmEUyAM5iwC2ZaAkDLnFOt2iHR4P8VExFmOZCl94gt6bqWuODhJ5mNCJXjEO9wmx3RNM5prB7Au5g59mdcuuY/GCKmKNt087BoHYG//dEFi4Q+bRpVE5MKaGv/JZd/LmPAfKfuj5Tr37m0m3hx6HROmIv0yHcakQlNQqM6QuRQLMr2U+nj4U4OFQZfMg3A+f6fVS6T18WLq4xbHc/2L1bYhIw+SjXwkj20cGhEBsmFOqj4oY5AzjN1t4gfzb5itxQNkZFVE2IdBP9v/Ck8rMQLmxs68PDPcp6CAb9dvMS0fX5CTTbJHqG4XEjYRaBVG0Ji5g3vTpGVAA4jqOzpTbxKQawA4SvddV8NUUm4N/zCeWMermi3yRhZRl1AXa8BqGO+mXNI7yAPjn1YDoGliQkoQc5B4CYY/5ldP19XS2hV5Ak16AJtD4tdeqbaX0bo=", 607 "verificationMethod": "did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2#zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2" 608 } 609 }` 610 611 nonceBytes, err := base64.StdEncoding.DecodeString("G/hn9Ca9bIWZpJGlhnr/41r8RB0OO0TLChZASr3QJVztdri/JzS8Zf/xWJT5jW78zlM=") 612 require.NoError(t, err) 613 614 sigSuite := bbsblssignatureproof2020.New( 615 suite.WithCompactProof(), 616 suite.WithVerifier(bbsblssignatureproof2020.NewG2PublicKeyVerifier(nonceBytes))) 617 618 // pkBase58 from did:key:zUC724vuGvHpnCGFG1qqpXb81SiBLu3KLSqVzenwEZNPoY35i2Bscb8DLaVwHvRFs6F2NkNNXRcPWvqnPDUd9ukdjLkjZd3u9zzL4wDZDUpkPAatLDGLEYVo8kkAzuAKJQMr7N2 619 pkBase58 := "nEP2DEdbRaQ2r5Azeatui9MG6cj7JUHa8GD7khub4egHJREEuvj4Y8YG8w51LnhPEXxVV1ka93HpSLkVzeQuuPE1mH9oCMrqoHXAKGBsuDT1yJvj9cKgxxLCXiRRirCycki" 620 pubKeyBytes := base58.Decode(pkBase58) 621 622 vcVerified, err := parseTestCredential(t, []byte(vcJSON), 623 WithEmbeddedSignatureSuites(sigSuite), 624 WithPublicKeyFetcher(SingleKey(pubKeyBytes, "Bls12381G2Key2020")), 625 ) 626 r.NoError(err) 627 r.NotNil(vcVerified) 628 } 629 630 func TestParseCredentialFromLinkedDataProof_JsonWebSignature2020_Ed25519(t *testing.T) { 631 r := require.New(t) 632 633 signer, err := newCryptoSigner(kms.ED25519Type) 634 r.NoError(err) 635 636 localCrypto, err := createLocalCrypto() 637 r.NoError(err) 638 639 sigSuite := jsonwebsignature2020.New( 640 suite.WithSigner(signer), // TODO replace getEd25519TestSigner with LocalCrypto/KMS 641 suite.WithVerifier(suite.NewCryptoVerifier(localCrypto))) 642 643 ldpContext := &LinkedDataProofContext{ 644 SignatureType: "JsonWebSignature2020", 645 SignatureRepresentation: SignatureJWS, 646 Suite: sigSuite, 647 VerificationMethod: "did:example:123456#key1", 648 } 649 650 vc, err := parseTestCredential(t, []byte(validCredential)) 651 r.NoError(err) 652 653 err = vc.AddLinkedDataProof(ldpContext, jsonldsig.WithDocumentLoader(createTestDocumentLoader(t))) 654 r.NoError(err) 655 656 vcBytes, err := json.Marshal(vc) 657 r.NoError(err) 658 659 vcWithLdp, err := parseTestCredential(t, vcBytes, 660 WithEmbeddedSignatureSuites(sigSuite), 661 WithPublicKeyFetcher(SingleKey(signer.PublicKeyBytes(), "Ed25519Signature2018"))) 662 r.NoError(err) 663 r.Equal(vc, vcWithLdp) 664 } 665 666 func TestParseCredentialFromLinkedDataProof_JsonWebSignature2020_ecdsaP256(t *testing.T) { 667 r := require.New(t) 668 669 signer, err := newCryptoSigner(kms.ECDSAP256TypeIEEEP1363) 670 require.NoError(t, err) 671 672 localCrypto, err := createLocalCrypto() 673 r.NoError(err) 674 675 sigSuite := jsonwebsignature2020.New( 676 suite.WithSigner(signer), 677 suite.WithVerifier(suite.NewCryptoVerifier(localCrypto))) 678 679 ldpContext := &LinkedDataProofContext{ 680 SignatureType: "JsonWebSignature2020", 681 SignatureRepresentation: SignatureJWS, 682 Suite: sigSuite, 683 VerificationMethod: "did:example:123456#key1", 684 } 685 686 vc, err := parseTestCredential(t, []byte(validCredential)) 687 r.NoError(err) 688 689 err = vc.AddLinkedDataProof(ldpContext, jsonldsig.WithDocumentLoader(createTestDocumentLoader(t))) 690 r.NoError(err) 691 692 vcBytes, err := json.Marshal(vc) 693 r.NoError(err) 694 695 j, err := jwksupport.JWKFromKey(signer.PublicKey()) 696 require.NoError(t, err) 697 698 vcWithLdp, err := parseTestCredential(t, vcBytes, 699 WithEmbeddedSignatureSuites(sigSuite), 700 WithPublicKeyFetcher(func(issuerID, keyID string) (*sigverifier.PublicKey, error) { 701 return &sigverifier.PublicKey{ 702 Type: "JwsVerificationKey2020", 703 Value: signer.PublicKeyBytes(), 704 JWK: j, 705 }, nil 706 })) 707 r.NoError(err) 708 r.Equal(vc, vcWithLdp) 709 } 710 711 func TestParseCredentialFromLinkedDataProof_EcdsaSecp256k1Signature2019(t *testing.T) { 712 r := require.New(t) 713 714 signer, err := newCryptoSigner(kms.ECDSASecp256k1TypeIEEEP1363) 715 require.NoError(t, err) 716 717 sigSuite := ecdsasecp256k1signature2019.New( 718 suite.WithSigner(signer), 719 // TODO use suite.NewCryptoVerifier(createLocalCrypto()) verifier as soon as 720 // tinkcrypto will support secp256k1 (https://github.com/hyperledger/aries-framework-go/issues/1285) 721 suite.WithVerifier(ecdsasecp256k1signature2019.NewPublicKeyVerifier())) 722 723 ldpContext := &LinkedDataProofContext{ 724 SignatureType: "EcdsaSecp256k1Signature2019", 725 SignatureRepresentation: SignatureJWS, 726 Suite: sigSuite, 727 VerificationMethod: "did:example:123456#key1", 728 } 729 730 vc, err := parseTestCredential(t, []byte(validCredential)) 731 r.NoError(err) 732 733 err = vc.AddLinkedDataProof(ldpContext, jsonldsig.WithDocumentLoader(createTestDocumentLoader(t))) 734 r.NoError(err) 735 736 vcBytes, err := json.Marshal(vc) 737 r.NoError(err) 738 739 j, err := jwksupport.JWKFromKey(signer.PublicKey()) 740 require.NoError(t, err) 741 742 // JWK encoded public key 743 vcWithLdp, err := parseTestCredential(t, vcBytes, 744 WithEmbeddedSignatureSuites(sigSuite), 745 WithPublicKeyFetcher(func(issuerID, keyID string) (*sigverifier.PublicKey, error) { 746 return &sigverifier.PublicKey{ 747 Type: "EcdsaSecp256k1VerificationKey2019", 748 JWK: j, 749 }, nil 750 })) 751 r.NoError(err) 752 r.Equal(vc, vcWithLdp) 753 754 // Bytes encoded public key (can come in e.g. publicKeyHex field) 755 vcWithLdp, err = parseTestCredential(t, vcBytes, 756 WithEmbeddedSignatureSuites(sigSuite), 757 WithPublicKeyFetcher(func(issuerID, keyID string) (*sigverifier.PublicKey, error) { 758 return &sigverifier.PublicKey{ 759 Type: "EcdsaSecp256k1VerificationKey2019", 760 Value: signer.PublicKeyBytes(), 761 }, nil 762 })) 763 r.NoError(err) 764 r.Equal(vc, vcWithLdp) 765 } 766 767 //nolint:lll 768 func TestParseCredential_JSONLiteralsNotSupported(t *testing.T) { 769 cmtrJSONLD := ` 770 { 771 "@context": { 772 "@version": 1.1, 773 "@protected": true, 774 "name": "http://schema.org/name", 775 "description": "http://schema.org/description", 776 "image": { 777 "@id": "http://schema.org/image", 778 "@type": "@id" 779 }, 780 "hetc": "http://localhost:9393/cmtr#", 781 "cmtr": { 782 "@id": "hetc:cmtr", 783 "@type": "@json" 784 } 785 } 786 } 787 ` 788 789 docLoader := createTestDocumentLoader(t, ldcontext.Document{ 790 URL: "http://127.0.0.1:53401/cmtr.jsonld", 791 Content: []byte(cmtrJSONLD), 792 }) 793 794 vcJSON := `{ 795 "@context": [ 796 "https://www.w3.org/2018/credentials/v1", 797 "http://127.0.0.1:53401/cmtr.jsonld" 798 ], 799 "id": "http://example.com/credentials/123", 800 "type": [ 801 "VerifiableCredential", 802 "CertifiedMillTestReport" 803 ], 804 "issuer": "did:elem:ropsten:EiBJJPdo-ONF0jxqt8mZYEj9Z7FbdC87m2xvN0_HAbcoEg", 805 "issuanceDate": "2020-03-09T18:19:10.033Z", 806 "name": "Certified Mill Test Report", 807 "description": "A mill test report (MTR) and often also called a certified mill test report, certified material test report, mill test certificate (MTC), inspection certificate, certificate of test, or a host of other names, is a quality assurance document used in the metals industry that certifies a material's chemical and physical properties and states a product made of metal (steel, aluminum, brass or other alloys) complies with an international standards organization (such as ANSI, ASME, etc.) specific standards.", 808 "credentialSubject": { 809 "cmtr": { 810 "additionalRemarks": "Product is coated for high temperatures. STEEL-IT High Temp coatings are intended for use where surface temperatures reach above 200°F, such as the external surfaces of industrial ovens, certain types of piping used in chemical and other manufacturing, and more. Customers choose which high temp coating is right for them based on whether USDA approval is required; whether the surface will be exposed to corrosive chemicals; or whether the surface will be exposed to sunlight or other sources of ultraviolet radiation.", 811 "authorizingPartyDate": "February 19, 2020", 812 "authorizingPartyName": "Stacy Slater", 813 "authorizingPartyTitle": "Chief Quality Assurance Officer", 814 "certificateNumber": "CT 001", 815 "chemicalProperties": { 816 "columns": [ 817 { 818 "field": "heatNumber", 819 "title": "Heat Number" 820 }, 821 { 822 "field": "C", 823 "title": "C" 824 }, 825 { 826 "field": "Si", 827 "title": "Si" 828 }, 829 { 830 "field": "P", 831 "title": "P" 832 }, 833 { 834 "field": "S", 835 "title": "S" 836 }, 837 { 838 "field": "V", 839 "title": "V" 840 }, 841 { 842 "field": "Cr", 843 "title": "Cr" 844 }, 845 { 846 "field": "Mn", 847 "title": "Mn" 848 }, 849 { 850 "field": "Ni", 851 "title": "Ni" 852 }, 853 { 854 "field": "Cu", 855 "title": "Cu" 856 }, 857 { 858 "field": "Mo", 859 "title": "Mo" 860 }, 861 { 862 "field": "Sn", 863 "title": "Sn" 864 } 865 ], 866 "rows": [ 867 { 868 "C": ".1", 869 "heatNumber": "404012" 870 }, 871 { 872 "C": ".4", 873 "heatNumber": "387230" 874 } 875 ] 876 }, 877 "companyAddress": "3260 46 Ave SE #30, Calgary, AB T2B 3K7, Canada", 878 "companyBrandMark": "data:image/png;base64,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", 879 "companyContactPersonName": "Test Test", 880 "companyEmail": "stacy@example.com", 881 "companyName": "Steel Inc.", 882 "companyPhoneNumber": "555 555 5555", 883 "companyWebsite": "https://example.com", 884 "customerLocationAddressCountry": "USA", 885 "customerLocationAddressLocality": "Jewett", 886 "customerLocationAddressRegion": "TX", 887 "customerLocationCompanyName": "Nucor Steel Jewett", 888 "customerLocationPostalCode": "", 889 "customerLocationStreetAddress": "U.S. 79", 890 "invoiceNumber": "IN 456", 891 "manufacturerLocationAddressCountry": "Canada", 892 "manufacturerLocationAddressLocality": "Calgary", 893 "manufacturerLocationAddressRegion": "AB T2B 3K7", 894 "manufacturerLocationCompanyName": "Steel Inc.", 895 "manufacturerLocationPostalCode": "", 896 "manufacturerLocationStreetAddress": "3260 46 Ave SE #30", 897 "mechanicalProperties": { 898 "columns": [ 899 { 900 "field": "heatNumber", 901 "title": "Heat Number" 902 }, 903 { 904 "field": "description", 905 "title": "Item Description" 906 }, 907 { 908 "field": "quantity", 909 "title": "Quantity" 910 }, 911 { 912 "field": "dimension", 913 "title": "Dimension" 914 }, 915 { 916 "field": "weight", 917 "title": "Net Weight (Kg)" 918 }, 919 { 920 "field": "yieldToTensileRatio", 921 "title": "Yield to Tensile Ratio" 922 }, 923 { 924 "field": "yieldStrength", 925 "title": "Yield Strength (PSI)" 926 }, 927 { 928 "field": "tensileStrength", 929 "title": "Tensile Strength (PSI)" 930 }, 931 { 932 "field": "elongation", 933 "title": "Elongation (%)" 934 }, 935 { 936 "field": "charpyImpactTempDegreesC", 937 "title": "CHARPY IMPACT Temp (C)" 938 }, 939 { 940 "field": "charpyImpactEnergyJoules", 941 "title": "CHARPY IMPACT Energy (J)" 942 } 943 ], 944 "rows": [ 945 { 946 "description": "Hot Rolled Steel Pipe", 947 "dimension": "203.2 mm dia. x 5609 + 5663 mm (8\" dia.)", 948 "elongation": "27", 949 "heatNumber": "404012", 950 "quantity": "2", 951 "tensileStrength": "71000", 952 "weight": "2900.27", 953 "yieldStrength": "52000", 954 "yieldToTensileRatio": "0.73" 955 }, 956 { 957 "description": "Cold Rolled Steel Bar", 958 "dimension": "203.2 mm dia. x 5609 + 5663 mm", 959 "elongation": "27", 960 "heatNumber": "387230", 961 "quantity": "500", 962 "tensileStrength": "76000", 963 "weight": "2900.27", 964 "yieldStrength": "55000", 965 "yieldToTensileRatio": "0.72" 966 } 967 ] 968 }, 969 "productDescription": "SS490 steel is a structural hot Rolled steel in the form of plates, sheets \u0026 strips for general structural applications. SS490 is a material grade and designation defined in JIS G 3101 standard. JIS G 3101 is a Japanese material standard for hot Rolled steel plates, sheets, strips for general structural usage. The structural quality hot rolled SS490 steel is more reliable in its tensile strength than SS400 steel...", 970 "proprietaryGrades": [ 971 { 972 "description": "BF-4122", 973 "title": "BF-4122" 974 } 975 ], 976 "proprietarySpecifications": [ 977 { 978 "description": "ASTM-51", 979 "title": "ASTM-51" 980 } 981 ], 982 "purchaseOrder": "PO 123", 983 "standardGrades": [ 984 { 985 "description": "SUS201", 986 "title": "SUS201" 987 } 988 ], 989 "standardSpecifications": [ 990 { 991 "description": "Rolled steels for general structure", 992 "isoCode": "JIS G 3101", 993 "title": "JIS G 3101" 994 } 995 ] 996 }, 997 "id": "did:key:z6MkjRagNiMu91DduvCvgEsqLZDVzrJzFrwahc4tXLt9DoHd" 998 }, 999 "proof": { 1000 "type": "Ed25519Signature2018", 1001 "created": "2020-05-14T15:22:26.065935+03:00", 1002 "verificationMethod": "did:example:123456#key1", 1003 "proofPurpose": "assertionMethod", 1004 "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..dmLYprMM4-E9XnEsd6iQHvmrgeC8pKe2liEKcSAu53A7Ok6LjognKQKLNdJSLsJd8cGh2g15ZTu6BKAnp2v7AQ" 1005 } 1006 }` 1007 1008 publicKeyBytes := base58.Decode("At4yQndGdrJs5AVFjYXqwDRALfm3ghLAmzhLux5eJkhh") 1009 1010 localCrypto, err := createLocalCrypto() 1011 require.NoError(t, err) 1012 vc, err := ParseCredential([]byte(vcJSON), 1013 WithPublicKeyFetcher(SingleKey(publicKeyBytes, "Ed25519Signature2018")), 1014 WithEmbeddedSignatureSuites(ed25519signature2018.New( 1015 suite.WithVerifier(suite.NewCryptoVerifier(localCrypto)))), 1016 WithJSONLDOnlyValidRDF(), 1017 WithStrictValidation(), 1018 WithJSONLDDocumentLoader(docLoader)) 1019 1020 require.NoError(t, err) 1021 require.NotNil(t, vc) 1022 } 1023 1024 //nolint:lll 1025 func TestParseCredential_ProofCreatedWithMillisec(t *testing.T) { 1026 vcJSON := ` 1027 { 1028 "issuanceDate": "2020-03-10T04:24:12.164Z", 1029 "credentialSubject": { 1030 "degree": { 1031 "name": "Bachelor of Science and Arts", 1032 "type": "BachelorDegree" 1033 }, 1034 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" 1035 }, 1036 "id": "http://example.gov/credentials/3732", 1037 "type": [ 1038 "VerifiableCredential", 1039 "UniversityDegreeCredential" 1040 ], 1041 "@context": [ 1042 "https://www.w3.org/2018/credentials/v1", 1043 "https://www.w3.org/2018/credentials/examples/v1" 1044 ], 1045 "issuer": { 1046 "id": "did:key:z6MkrqCMy45WhL3UEa1gGTHUtr17AvU4czfP5fH9KNDoYaYN" 1047 }, 1048 "proof": { 1049 "created": "2020-05-04T14:30:37.972Z", 1050 "proofPurpose": "assertionMethod", 1051 "type": "Ed25519Signature2018", 1052 "verificationMethod": "did:key:z6MkrqCMy45WhL3UEa1gGTHUtr17AvU4czfP5fH9KNDoYaYN#z6MkrqCMy45WhL3UEa1gGTHUtr17AvU4czfP5fH9KNDoYaYN", 1053 "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..SVA8JpQQU9-XP9mlEB-V0TVeX0V7d_jDImQyXrV1-SzfOTP7M6CERVmj7ppAAed1CgIQceIoiIZ8sUN3n_0UDg" 1054 } 1055 } 1056 ` 1057 1058 publicKeyBytes := base58.Decode("DNwKNoq5MnZ185AyatKe3kT7MMCDD7R2PeNDV6FndMkz") 1059 1060 localCrypto, err := createLocalCrypto() 1061 require.NoError(t, err) 1062 vc, err := parseTestCredential(t, []byte(vcJSON), 1063 WithPublicKeyFetcher(SingleKey(publicKeyBytes, "Ed25519Signature2018")), 1064 WithEmbeddedSignatureSuites(ed25519signature2018.New( 1065 suite.WithVerifier(suite.NewCryptoVerifier(localCrypto)))), 1066 WithStrictValidation()) 1067 1068 require.NoError(t, err) 1069 require.NotNil(t, vc) 1070 } 1071 1072 func TestParseCredentialWithSeveralLinkedDataProofs(t *testing.T) { 1073 r := require.New(t) 1074 1075 ed25519Signer, err := newCryptoSigner(kms.ED25519Type) 1076 r.NoError(err) 1077 1078 ed25519SigSuite := ed25519signature2018.New( 1079 suite.WithSigner(ed25519Signer), 1080 suite.WithVerifier(ed25519signature2018.NewPublicKeyVerifier())) 1081 1082 vc, err := parseTestCredential(t, []byte(validCredential)) 1083 r.NoError(err) 1084 1085 err = vc.AddLinkedDataProof(&LinkedDataProofContext{ 1086 SignatureType: "Ed25519Signature2018", 1087 SignatureRepresentation: SignatureProofValue, 1088 Suite: ed25519SigSuite, 1089 VerificationMethod: "did:example:123456#key1", 1090 }, jsonldsig.WithDocumentLoader(createTestDocumentLoader(t))) 1091 r.NoError(err) 1092 1093 ecdsaSigner, err := newCryptoSigner(kms.ECDSAP256TypeIEEEP1363) 1094 require.NoError(t, err) 1095 1096 ecdsaSigSuite := jsonwebsignature2020.New( 1097 suite.WithSigner(ecdsaSigner), 1098 suite.WithVerifier(jsonwebsignature2020.NewPublicKeyVerifier())) 1099 1100 err = vc.AddLinkedDataProof(&LinkedDataProofContext{ 1101 SignatureType: "JsonWebSignature2020", 1102 SignatureRepresentation: SignatureJWS, 1103 Suite: ecdsaSigSuite, 1104 VerificationMethod: "did:example:123456#key2", 1105 }, jsonldsig.WithDocumentLoader(createTestDocumentLoader(t))) 1106 r.NoError(err) 1107 1108 vcBytes, err := json.Marshal(vc) 1109 r.NoError(err) 1110 r.NotEmpty(vcBytes) 1111 1112 j, err := jwksupport.JWKFromKey(ecdsaSigner.PublicKey()) 1113 require.NoError(t, err) 1114 1115 vcWithLdp, err := parseTestCredential(t, vcBytes, 1116 WithEmbeddedSignatureSuites(ed25519SigSuite, ecdsaSigSuite), 1117 WithPublicKeyFetcher(func(issuerID, keyID string) (*sigverifier.PublicKey, error) { 1118 switch keyID { 1119 case "#key1": 1120 return &sigverifier.PublicKey{ 1121 Type: "Ed25519Signature2018", 1122 Value: ed25519Signer.PublicKeyBytes(), 1123 }, nil 1124 1125 case "#key2": 1126 return &sigverifier.PublicKey{ 1127 Type: "JsonWebKey2020", 1128 Value: ecdsaSigner.PublicKeyBytes(), 1129 JWK: j, 1130 }, nil 1131 } 1132 1133 return nil, errors.New("unsupported keyID") 1134 })) 1135 r.NoError(err) 1136 r.Equal(vc, vcWithLdp) 1137 } 1138 1139 func createLocalCrypto() (*LocalCrypto, error) { 1140 lKMS, err := createKMS() 1141 if err != nil { 1142 return nil, err 1143 } 1144 1145 tinkCrypto, err := tinkcrypto.New() 1146 if err != nil { 1147 return nil, err 1148 } 1149 1150 return &LocalCrypto{ 1151 Crypto: tinkCrypto, 1152 localKMS: lKMS, 1153 }, nil 1154 } 1155 1156 // LocalCrypto defines a verifier which is based on Local KMS and Crypto 1157 // which uses keyset.Handle as input for verification. 1158 type LocalCrypto struct { 1159 *tinkcrypto.Crypto 1160 localKMS *localkms.LocalKMS 1161 } 1162 1163 func (t *LocalCrypto) Verify(sig, msg []byte, kh interface{}) error { 1164 pubKey, ok := kh.(*sigverifier.PublicKey) 1165 if !ok { 1166 return errors.New("bad key handle format") 1167 } 1168 1169 kmsKeyType, err := mapPublicKeyToKMSKeyType(pubKey) 1170 if err != nil { 1171 return err 1172 } 1173 1174 handle, err := t.localKMS.PubKeyBytesToHandle(pubKey.Value, kmsKeyType) 1175 if err != nil { 1176 return err 1177 } 1178 1179 return t.Crypto.Verify(sig, msg, handle) 1180 } 1181 1182 func mapPublicKeyToKMSKeyType(pubKey *sigverifier.PublicKey) (kms.KeyType, error) { 1183 switch pubKey.Type { 1184 case "Ed25519Signature2018": 1185 return kms.ED25519Type, nil 1186 case "JwsVerificationKey2020": 1187 return mapJWKToKMSKeyType(pubKey.JWK) 1188 default: 1189 return "", fmt.Errorf("unsupported key type: %s", pubKey.Type) 1190 } 1191 } 1192 1193 func mapJWKToKMSKeyType(j *jwk.JWK) (kms.KeyType, error) { 1194 switch j.Kty { 1195 case "OKP": 1196 return kms.ED25519Type, nil 1197 case "EC": 1198 switch j.Crv { 1199 case "P-256": 1200 return kms.ECDSAP256TypeIEEEP1363, nil 1201 case "P-384": 1202 return kms.ECDSAP384TypeIEEEP1363, nil 1203 case "P-521": 1204 return kms.ECDSAP521TypeIEEEP1363, nil 1205 } 1206 } 1207 1208 return "", fmt.Errorf("unsupported JWK: %v", j) 1209 } 1210 1211 func TestCredential_AddLinkedDataProof(t *testing.T) { 1212 r := require.New(t) 1213 1214 signer, err := newCryptoSigner(kms.ED25519Type) 1215 r.NoError(err) 1216 1217 t.Run("Add a valid JWS Linked Data proof to VC", func(t *testing.T) { 1218 vc, err := parseTestCredential(t, []byte(validCredential)) 1219 r.NoError(err) 1220 1221 originalVCMap, err := jsonutil.ToMap(vc) 1222 r.NoError(err) 1223 1224 err = vc.AddLinkedDataProof(&LinkedDataProofContext{ 1225 SignatureType: "Ed25519Signature2018", 1226 SignatureRepresentation: SignatureJWS, 1227 Suite: ed25519signature2018.New(suite.WithSigner(signer)), 1228 VerificationMethod: "did:example:xyz#key-1", 1229 Challenge: uuid.New().String(), 1230 Domain: "issuer.service.com", 1231 Purpose: "authentication", 1232 }, jsonldsig.WithDocumentLoader(createTestDocumentLoader(t))) 1233 r.NoError(err) 1234 1235 vcMap, err := jsonutil.ToMap(vc) 1236 r.NoError(err) 1237 1238 r.Contains(vcMap, "proof") 1239 vcProof := vcMap["proof"] 1240 vcProofMap, ok := vcProof.(map[string]interface{}) 1241 r.True(ok) 1242 r.Contains(vcProofMap, "created") 1243 r.Contains(vcProofMap, "jws") 1244 r.Contains(vcProofMap, "challenge") 1245 r.Contains(vcProofMap, "domain") 1246 r.Contains(vcProofMap, "verificationMethod") 1247 r.Contains(vcProofMap, "proofPurpose") 1248 r.Equal("Ed25519Signature2018", vcProofMap["type"]) 1249 r.Equal("authentication", vcProofMap["proofPurpose"]) 1250 1251 // check that only "proof" element was added as a result of AddLinkedDataProof(). 1252 delete(vcMap, "proof") 1253 r.Equal(originalVCMap, vcMap) 1254 }) 1255 1256 t.Run("Add invalid Linked Data proof to VC", func(t *testing.T) { 1257 vc, err := parseTestCredential(t, []byte(validCredential)) 1258 require.NoError(t, err) 1259 1260 vc.CustomFields = map[string]interface{}{ 1261 "invalidField": make(chan int), 1262 } 1263 1264 err = vc.AddLinkedDataProof(&LinkedDataProofContext{ 1265 SignatureType: "Ed25519Signature2018", 1266 SignatureRepresentation: SignatureProofValue, 1267 Suite: ed25519signature2018.New(suite.WithSigner(signer)), 1268 }) 1269 r.Error(err) 1270 1271 vc.CustomFields = nil 1272 ldpContextWithMissingSignatureType := &LinkedDataProofContext{ 1273 Suite: ed25519signature2018.New(suite.WithSigner(signer)), 1274 SignatureRepresentation: SignatureProofValue, 1275 } 1276 1277 err = vc.AddLinkedDataProof(ldpContextWithMissingSignatureType) 1278 r.Error(err) 1279 }) 1280 1281 t.Run("sign and verify proof with capabilityChain", func(t *testing.T) { 1282 rootCapability := "https://edv.com/foo/zcap/123" 1283 vc, err := parseTestCredential(t, []byte(validCredential)) 1284 r.NoError(err) 1285 1286 err = vc.AddLinkedDataProof(&LinkedDataProofContext{ 1287 SignatureType: "Ed25519Signature2018", 1288 SignatureRepresentation: SignatureJWS, 1289 Suite: ed25519signature2018.New(suite.WithSigner(signer)), 1290 VerificationMethod: "did:example:xyz#key-1", 1291 Challenge: uuid.New().String(), 1292 Domain: "issuer.service.com", 1293 Purpose: "capabilityDelegation", 1294 CapabilityChain: []interface{}{rootCapability}, 1295 }, jsonldsig.WithDocumentLoader(createTestDocumentLoader(t))) 1296 r.NoError(err) 1297 1298 r.Len(vc.Proofs, 1) 1299 proof := vc.Proofs[0] 1300 r.Contains(proof, "proofPurpose") 1301 r.Equal("capabilityDelegation", proof["proofPurpose"]) 1302 r.Contains(proof, "capabilityChain") 1303 chain, ok := proof["capabilityChain"].([]interface{}) 1304 r.True(ok) 1305 r.Len(chain, 1) 1306 r.Equal(rootCapability, chain[0]) 1307 1308 // parse 1309 raw, err := json.Marshal(vc) 1310 r.NoError(err) 1311 result, err := ParseCredential(raw, 1312 WithJSONLDDocumentLoader(createTestDocumentLoader(t)), 1313 WithPublicKeyFetcher(SingleKey(signer.PublicKeyBytes(), kms.ED25519)), 1314 ) 1315 r.NoError(err) 1316 r.Len(result.Proofs, 1) 1317 proof = result.Proofs[0] 1318 r.Contains(proof, "proofPurpose") 1319 r.Equal("capabilityDelegation", proof["proofPurpose"]) 1320 r.Contains(proof, "capabilityChain") 1321 capabilities, ok := proof["capabilityChain"].([]interface{}) 1322 r.True(ok) 1323 r.Len(capabilities, 1) 1324 r.Equal(rootCapability, capabilities[0]) 1325 }) 1326 } 1327 1328 type bbsSigner struct { 1329 privKeyBytes []byte 1330 } 1331 1332 func newBBSSigner(privKey *bbs12381g2pub.PrivateKey) (*bbsSigner, error) { 1333 privKeyBytes, err := privKey.Marshal() 1334 if err != nil { 1335 return nil, err 1336 } 1337 1338 return &bbsSigner{privKeyBytes: privKeyBytes}, nil 1339 } 1340 1341 func (s *bbsSigner) Sign(data []byte) ([]byte, error) { 1342 msgs := s.textToLines(string(data)) 1343 1344 return bbs12381g2pub.New().Sign(msgs, s.privKeyBytes) 1345 } 1346 1347 func (s *bbsSigner) Alg() string { 1348 return "" 1349 } 1350 1351 func (s *bbsSigner) textToLines(txt string) [][]byte { 1352 lines := strings.Split(txt, "\n") 1353 linesBytes := make([][]byte, 0, len(lines)) 1354 1355 for i := range lines { 1356 if strings.TrimSpace(lines[i]) != "" { 1357 linesBytes = append(linesBytes, []byte(lines[i])) 1358 } 1359 } 1360 1361 return linesBytes 1362 }