Skip to content

Commit

Permalink
cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
OR13 committed Oct 12, 2023
1 parent f63a72e commit 4b9b503
Show file tree
Hide file tree
Showing 2 changed files with 292 additions and 49 deletions.
290 changes: 290 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,298 @@

## Usage

```bash
npm i @transmute/vc-jwt-sd --save
```

```ts
import sd from '@transmute/vc-jwt-sd'
```

### Issuer Claims

This implementation relies on custom yaml tags to indicate disclosability:

```yaml
# Based on https://w3c.github.io/vc-data-model/#example-a-simple-example-of-a-verifiable-credential
"@context":
- https://www.w3.org/ns/credentials/v2
- https://www.w3.org/ns/credentials/examples/v2
id: http://university.example/credentials/3732
type:
- VerifiableCredential
- ExampleDegreeCredential
issuer:
id: https://university.example/issuers/565049
name:
- value: test value 0
lang: en
- !sd # indicates a disclosable claim
value: test value 1
lang: en
- value: test value 2
lang: en
- !sd # indicates a disclosable claim
value: test value 3
lang: en
- value: test value 4
lang: en
validFrom: 2015-05-10T12:30:00Z
credentialStatus:
- id: https://vendor.example/status-list/urn:uuid:d31ada5d-1d3d-4f68-8587-8ff9bb3038d6#0
type: StatusList2021Entry
statusPurpose: revocation
statusListIndex: "0"
statusListCredential: https://vendor.example/status-list/urn:uuid:d31ada5d-1d3d-4f68-8587-8ff9bb3038d6
credentialSubject:
id: did:example:ebfeb1f712ebc6f1c276e12ec21
degree:
type: ExampleBachelorDegree
subtype: Bachelor of Science and Arts
```
### Credential Issuance
```ts

const alg = 'ES384';
const claimset = `... yaml example above ... `;

const issuerRole = await sd.key.generate(alg);
const issuerId = 'https://university.example/issuers/565049'
const issuerKeyId = `${issuerId}#key-42`

const holderRole = await sd.key.generate(alg); // or get it some other way.
const holderId = 'did:example:ebfeb1f712ebc6f1c276e12ec21'
const holderKeyId = `${holderId}#${holderRole.publicKeyJwk.kid}`

const vc = await sd.issuer({
iss: issuerId,
kid: issuerKeyId,
typ: `application/vc+ld+json+sd-jwt`,
secretKeyJwk: issuerRole.secretKeyJwk
})
.issue({
holder: holderKeyId,
claimset
})
```

### Holder Disclosure

This implementation relies on yaml to indicate holder disclosures:

```yaml
"@context":
- https://www.w3.org/ns/credentials/v2
- https://www.w3.org/ns/credentials/examples/v2
id: http://university.example/credentials/3732
type:
- VerifiableCredential
- ExampleDegreeCredential
issuer:
id: https://university.example/issuers/565049
name:
- value: test value 0
lang: en
- value: test value 1 # The value or True can be provided to signal intentional disclosure.
lang: en
- value: test value 2
lang: en
- False # This boolean indicates that this claim should not be disclosed in a presentation
- value: test value 4
lang: en
validFrom: 2015-05-10T12:30:00Z
credentialStatus:
- id: https://vendor.example/status-list/urn:uuid:d31ada5d-1d3d-4f68-8587-8ff9bb3038d6#0
type: StatusList2021Entry
statusPurpose: revocation
statusListIndex: "0"
statusListCredential: https://vendor.example/status-list/urn:uuid:d31ada5d-1d3d-4f68-8587-8ff9bb3038d6
credentialSubject:
id: did:example:ebfeb1f712ebc6f1c276e12ec21
degree:
type: ExampleBachelorDegree
subtype: Bachelor of Science and Arts
```
### Presentation with Holder Binding
```ts
const audience = 'aud-9877'
const nonce = 'nonce-5486168'
const disclosure = `... yaml example above ... `;
const vp = await sd.holder({
secretKeyJwk: holderRole.secretKeyJwk,
iss: holderId,
kid: holderKeyId
})
.issue({
token: vc,
disclosure,
audience,
nonce
})
```


### Verification by Key Resolution

Some protocols allow for discovery of public keys from identifiers.

🍃 This interface is safer,
since it performs verification internally,
which will fail closed when the incorrect public key is provided.

```ts
const verification = await sd.verifier({
resolver: {
resolve: async (kid: string) => {
if (kid === issuerKeyId){
return issuerRole.publicKeyJwk
}
if (kid === holderKeyId){
return holderRole.publicKeyJwk
}
throw new Error('Unsupported kid: ' + kid)
}
}
})
.verify({
token: vp,
audience,
nonce
})
```

### Verification by Token

Some protocols require disovery of public keys from protected header and payload claims.

🍂 This interface is less safe, but more flexible.
When misconfigured, this interface can lead to decoded values being treated as if they had been verified.

```ts
// for testing, not a real dereferencer
const dereference = async (url: string) => {
if (url.startsWith('https://university.example/issuers/565049')){
return {
id: issuerKeyId,
type: 'JsonWebKey',
controller: issuerId,
publicKeyJwk: issuerRole.publicKeyJwk
}
}
if (url.startsWith('did:example:ebfeb1f712ebc6f1c276e12ec21')){
return {
id: holderKeyId,
type: 'JsonWebKey',
controller: holderId,
publicKeyJwk: holderRole.publicKeyJwk
}
}
throw new Error('Unsupported didUrl: ' + didUrl)
}
const verification = await sd.verifier({
verifier: {
verify: async (token: string) => {
const jwt = token.split('~')[0]
const decodedHeader = decodeProtectedHeader(jwt)
if (decodedHeader.typ === 'application/vc+ld+json+sd-jwt'){
const decodedPayload = decodeJwt(jwt)
const iss = (decodedHeader.iss || decodedPayload.iss) as string
const kid = decodedHeader.kid as string
const absoluteDidUrl = kid && kid.startsWith(iss)? kid : `${iss}#${kid}`
const { publicKeyJwk } = await dereference(absoluteDidUrl)
const verifier = await sd.JWS.verifier(publicKeyJwk)
return verifier.verify(jwt)
}
if (decodedHeader.typ === 'kb+jwt'){
const decodedPayload = decodeJwt(jwt)
const iss = (decodedHeader.iss || decodedPayload.iss) as string
const kid = decodedHeader.kid as string
const absoluteDidUrl = kid && kid.startsWith(iss)? kid : `${iss}#${kid}`
const { publicKeyJwk } = await dereference(absoluteDidUrl)
const verifier = await sd.JWS.verifier(publicKeyJwk)
return verifier.verify(jwt)
}
throw new Error('Unsupported token typ')
}
}
})
.verify({
token: vp,
audience,
nonce
})
```

### Validation

🚧 This library does not perform validation currently.
This is the result of the verification operations above:

```json
{
"protectedHeader": {
"alg": "ES384",
"kid": "https://university.example/issuers/565049#key-42",
"typ": "application/vc+ld+json+sd-jwt"
},
"claimset": {
"iss": "https://university.example/issuers/565049",
"cnf": {
"jkt": "did:example:ebfeb1f712ebc6f1c276e12ec21#KmC0EKbs0kL2v6kxPP_c4g-HMMy-n8C5NwtN2tH_msc"
},
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://www.w3.org/ns/credentials/examples/v2"
],
"id": "http://university.example/credentials/3732",
"type": [
"VerifiableCredential",
"ExampleDegreeCredential"
],
"issuer": {
"id": "https://university.example/issuers/565049",
"name": [
{
"value": "test value 0",
"lang": "en"
},
{
"value": "test value 1",
"lang": "en"
},
{
"value": "test value 2",
"lang": "en"
},
{
"value": "test value 4",
"lang": "en"
}
]
},
"validFrom": "2015-05-10T12:30:00Z",
"credentialStatus": [
{
"id": "https://vendor.example/status-list/urn:uuid:d31ada5d-1d3d-4f68-8587-8ff9bb3038d6#0",
"type": "StatusList2021Entry",
"statusPurpose": "revocation",
"statusListIndex": "0",
"statusListCredential": "https://vendor.example/status-list/urn:uuid:d31ada5d-1d3d-4f68-8587-8ff9bb3038d6"
}
],
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "ExampleBachelorDegree",
"subtype": "Bachelor of Science and Arts"
}
},
}
}
```

## Develop
Expand Down
51 changes: 2 additions & 49 deletions test/controller-documents/kid.resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,54 +110,7 @@ it('End to End Test', async () => {
audience,
nonce
})

const dereference = async (didUrl: string)=>{
// for testing, not a real dereferencer
if (didUrl.startsWith('https://university.example/issuers/565049')){
return {
id: issuerKeyId,
type: 'JsonWebKey',
controller: issuerId,
publicKeyJwk: issuerRole.publicKeyJwk
}
}
if (didUrl.startsWith('did:example:ebfeb1f712ebc6f1c276e12ec21')){
return {
id: holderKeyId,
type: 'JsonWebKey',
controller: holderId,
publicKeyJwk: holderRole.publicKeyJwk
}
}
throw new Error('Unsupported didUrl: ' + didUrl)
}

const tokenVerifier = {
verify: async (token: string) => {
const jwt = token.split('~')[0]
const decodedHeader = decodeProtectedHeader(jwt)
if (decodedHeader.typ === 'application/vc+ld+json+sd-jwt'){
const decodedPayload = decodeJwt(jwt)
const iss = (decodedHeader.iss || decodedPayload.iss) as string
const kid = decodedHeader.kid as string
const absoluteDidUrl = kid && kid.startsWith(iss)? kid : `${iss}#${kid}`
const { publicKeyJwk } = await dereference(absoluteDidUrl)
const verifier = await sd.JWS.verifier(publicKeyJwk)
return verifier.verify(jwt)
}
if (decodedHeader.typ === 'kb+jwt'){
const decodedPayload = decodeJwt(jwt)
const iss = (decodedHeader.iss || decodedPayload.iss) as string
const kid = decodedHeader.kid as string
const absoluteDidUrl = kid && kid.startsWith(iss)? kid : `${iss}#${kid}`
const { publicKeyJwk } = await dereference(absoluteDidUrl)
const verifier = await sd.JWS.verifier(publicKeyJwk)
return verifier.verify(jwt)
}
throw new Error('Unsupported token typ')
}
}


const keyIdResolver = {
resolve: async (kid: string) => {
if (kid === issuerKeyId){
Expand All @@ -178,5 +131,5 @@ it('End to End Test', async () => {
audience,
nonce
})
expect(verification.claimset.cnf.jkt).toBeDefined()
expect(verification.claimset.cnf.jkt).toBeDefined()
});

0 comments on commit 4b9b503

Please sign in to comment.