Skip to content

Commit

Permalink
fix(credential-w3c): correct verification of credentials given as obj…
Browse files Browse the repository at this point in the history
…ects with jwt proofs (#1071)

* test(credential-w3c): add unit test to exercise case when credential is modified outside of jwt proof

note: this test *should* pass, but currently fails since there is a bug

* test(credential-w3c): add more tests exercising verifyCredential

* fix(credential-w3c): better verification of credentials given as objects with jwt proofs

* chore(did-provider-ion): explicitly declare dependency

* chore: cleanup

* chore: cleanup
  • Loading branch information
nickreynolds authored Nov 23, 2022
1 parent 6df8520 commit b0d75e9
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 5 deletions.
1 change: 1 addition & 0 deletions packages/credential-w3c/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@veramo/did-resolver": "^4.1.1",
"@veramo/message-handler": "^4.1.1",
"@veramo/utils": "^4.1.1",
"canonicalize": "^1.0.8",
"debug": "^4.3.3",
"did-jwt-vc": "^3.1.0",
"did-resolver": "^4.0.1",
Expand Down
100 changes: 95 additions & 5 deletions packages/credential-w3c/src/__tests__/issue-verify-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import { EthrDIDProvider } from '../../../did-provider-ethr/src'
import { ContextDoc } from '../../../credential-ld/src/types'
import { Resolver } from 'did-resolver'
import { getResolver as ethrDidResolver } from 'ethr-did-resolver'
import { CredentialIssuerLD } from '../../../credential-ld/src/action-handler'
import { LdDefaultContexts } from '../../../credential-ld/src/ld-default-contexts'
import { VeramoEd25519Signature2018 } from '../../../credential-ld/src/suites/Ed25519Signature2018'
import { VeramoEcdsaSecp256k1RecoverySignature2020 } from '../../../credential-ld/src/suites/EcdsaSecp256k1RecoverySignature2020'
import { VerifiableCredential } from '../../../core/src'

jest.setTimeout(300000)

Expand All @@ -35,6 +40,7 @@ describe('credential-w3c full flow', () => {
let didKeyIdentifier: IIdentifier
let didEthrIdentifier: IIdentifier
let agent: TAgent<IResolver & IKeyManager & IDIDManager & ICredentialPlugin>
let credential: CredentialPayload

beforeAll(async () => {
agent = createAgent({
Expand Down Expand Up @@ -63,20 +69,103 @@ describe('credential-w3c full flow', () => {
}),
}),
new CredentialIssuer(),
new CredentialIssuerLD({
contextMaps: [LdDefaultContexts, customContext],
suites: [new VeramoEd25519Signature2018(), new VeramoEcdsaSecp256k1RecoverySignature2020()],
}),
],
})
didKeyIdentifier = await agent.didManagerCreate()
didEthrIdentifier = await agent.didManagerCreate({ provider: 'did:ethr:goerli' })
})

it('verify a verifiablePresentation', async () => {
const credential: CredentialPayload = {
credential = {
issuer: didKeyIdentifier.did,
'@context': ['custom:example.context'],
credentialSubject: {
nothing: 'else matters',
},
}
})

it(`verifies a credential created with jwt proofType`, async () => {
const verifiableCredential1 = await agent.createVerifiableCredential({
credential,
proofFormat: 'jwt',
})
const verifyResult = await agent.verifyCredential({ credential: verifiableCredential1 })
expect(verifyResult.verified).toBeTruthy()
})

it(`verifies a credential created with lds proofType`, async () => {
const verifiableCredential1 = await agent.createVerifiableCredential({
credential,
proofFormat: 'lds',
})
const verifyResult = await agent.verifyCredential({ credential: verifiableCredential1 })
expect(verifyResult.verified).toBeTruthy()
})

it(`fails to verify a credential created with lds proofType with modified values`, async () => {
const verifiableCredential1 = await agent.createVerifiableCredential({
credential,
proofFormat: 'lds',
})
const modifiedCredential: VerifiableCredential = { ...verifiableCredential1, issuer: { id: 'did:fake:wrong' }}
const verifyResult = await agent.verifyCredential({ credential: modifiedCredential })
expect(verifyResult.verified).toBeFalsy()
})


it('fails the verification of a jwt credential with false value outside of proof', async () => {
const verifiableCredential1 = await agent.createVerifiableCredential({
credential,
proofFormat: 'jwt',
})

const modifiedCredential: VerifiableCredential = { ...verifiableCredential1, issuer: { id: 'did:fake:wrong' }}
const verifyResult = await agent.verifyCredential({ credential: modifiedCredential })

expect(verifyResult.verified).toBeFalsy()
})

// uncomment when https://github.com/uport-project/veramo/issues/1073 is resolved
// example credential found at: https://learn.mattr.global/tutorials/web-credentials/issue/issue-basic
// it(`verifies a credential created with lds proofType via Mattr`, async () => {
// const verifiableCredential1 = {
// "@context": [
// "https://www.w3.org/2018/credentials/v1",
// {
// "@vocab": "https://w3id.org/security/undefinedTerm#"
// },
// "https://schema.org"
// ],
// "type": [
// "VerifiableCredential",
// "CourseCredential"
// ],
// "issuer": {
// "id": "did:key:z6MkndAHigYrXNpape7jgaC7jHiWwxzB3chuKUGXJg2b5RSj",
// "name": "tenant"
// },
// "issuanceDate": "2021-07-26T01:05:05.152Z",
// "credentialSubject": {
// "id": "did:key:z6MkfxQU7dy8eKxyHpG267FV23agZQu9zmokd8BprepfHALi",
// "givenName": "Chris",
// "familyName": "Shin",
// "educationalCredentialAwarded": "Certificate Name"
// },
// "proof": {
// "type": "Ed25519Signature2018",
// "created": "2021-07-26T01:05:06Z",
// "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..o6hnrrWpArG8LQz2Ex_u66_BtuPdp3Hkz18nhNdNhJ7J1k_2lmCCwsNdmo-kNFirZdSIMzqO-V3wEjMDphVEAA",
// "proofPurpose": "assertionMethod",
// "verificationMethod": "did:key:z6MkndAHigYrXNpape7jgaC7jHiWwxzB3chuKUGXJg2b5RSj#z6MkndAHigYrXNpape7jgaC7jHiWwxzB3chuKUGXJg2b5RSj"
// }
// }
// const verifyResult = await agent.verifyCredential({ credential: verifiableCredential1 })
// expect(verifyResult.verified).toBeTruthy()
// })

it('verify a verifiablePresentation', async () => {
const verifiableCredential1 = await agent.createVerifiableCredential({
credential,
proofFormat: 'jwt',
Expand All @@ -101,7 +190,8 @@ describe('credential-w3c full flow', () => {
expect(response.verified).toBe(true)
})

it.only('fails the verification of an expired credential', async () => {

it('fails the verification of an expired credential', async () => {
const presentationJWT =
'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjAyOTcyMTAsInZwIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZVByZXNlbnRhdGlvbiJdLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUpoYkdjaU9pSkZaRVJUUVNJc0luUjVjQ0k2SWtwWFZDSjkuZXlKbGVIQWlPakUyTmpBeU9UY3lNVEFzSW5aaklqcDdJa0JqYjI1MFpYaDBJanBiSW1oMGRIQnpPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1ERTRMMk55WldSbGJuUnBZV3h6TDNZeElpd2lZM1Z6ZEc5dE9tVjRZVzF3YkdVdVkyOXVkR1Y0ZENKZExDSjBlWEJsSWpwYklsWmxjbWxtYVdGaWJHVkRjbVZrWlc1MGFXRnNJbDBzSW1OeVpXUmxiblJwWVd4VGRXSnFaV04wSWpwN0ltNXZkR2hwYm1jaU9pSmxiSE5sSUcxaGRIUmxjbk1pZlgwc0ltNWlaaUk2TVRZMk1ESTVOekl4TUN3aWFYTnpJam9pWkdsa09tdGxlVHA2TmsxcmFWVTNVbk5hVnpOeWFXVmxRMjg1U25OMVVEUnpRWEZYZFdGRE0zbGhjbWwxWVZCMlVXcHRZVzVsWTFBaWZRLkZhdzBEUWNNdXpacEVkcy1LR3dOalMyM2IzbUEzZFhQWXBQcGJzNmRVSnhIOVBrZzVieGF3UDVwMlNPajdQM25IdEpCR3lwTjJ3NzRfZjc3SjF5dUJ3Il19LCJuYmYiOjE2NjAyOTcyMTAsImlzcyI6ImRpZDprZXk6ejZNa2lVN1JzWlczcmllZUNvOUpzdVA0c0FxV3VhQzN5YXJpdWFQdlFqbWFuZWNQIn0.YcYbyqVlD8YsTjVw0kCEs0P_ie6SFMakf_ncPntEjsmS9C4cKyiS50ZhNkOv0R3Roy1NrzX7h93WBU55KeJlCw'

Expand Down
17 changes: 17 additions & 0 deletions packages/credential-w3c/src/action-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {
import Debug from 'debug'
import { Resolvable } from 'did-resolver'

import canonicalize from 'canonicalize'

const enum DocumentFormat {
JWT,
JSONLD,
Expand Down Expand Up @@ -274,6 +276,7 @@ export class CredentialPlugin implements IAgentPlugin {

const resolver = { resolve: (didUrl: string) => context.agent.resolveDid({ didUrl }) } as Resolvable
try {
// needs broader credential as well to check equivalence with jwt
verificationResult = await verifyCredentialJWT(jwt, resolver, {
...otherOptions,
policies: {
Expand All @@ -285,6 +288,20 @@ export class CredentialPlugin implements IAgentPlugin {
},
})
verifiedCredential = verificationResult.verifiableCredential

// if credential was presented with other fields, make sure those fields match what's in the JWT
if (typeof credential !== 'string') {
const credentialCopy = JSON.parse(JSON.stringify(credential))
delete credentialCopy.proof.jwt

const verifiedCopy = JSON.parse(JSON.stringify(verifiedCredential))
delete verifiedCopy.proof.jwt

if(canonicalize(credentialCopy) !== canonicalize(verifiedCopy)) {
verificationResult.verified = false
verificationResult.error = new Error('Credential does not match JWT')
}
}
} catch (e: any) {
let { message, errorCode } = e
return {
Expand Down
1 change: 1 addition & 0 deletions packages/did-provider-ion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@veramo/key-manager": "^4.1.1",
"@veramo/kms-local": "^4.1.1",
"base64url": "^3.0.1",
"canonicalize": "^1.0.8",
"debug": "^4.3.3",
"uint8arrays": "^3.0.0"
},
Expand Down

0 comments on commit b0d75e9

Please sign in to comment.