Skip to content

Commit

Permalink
feat(did auth): update resolution of authentication entries in DIDDoc…
Browse files Browse the repository at this point in the history
…ument (#143)
  • Loading branch information
mirceanis authored Dec 9, 2020
1 parent e03f515 commit a10ca34
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 30 deletions.
15 changes: 7 additions & 8 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
"request": "launch",
"name": "Jest All",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": [
"--runInBand", "--coverage=false"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"args": ["--runInBand", "--coverage=false", "JWT-test"],
"sourceMaps": true,
"disableOptimisticBPs": true,
"windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
}
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
},
"resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/typescript/lib/typescript.js.map"],
"runtimeArgs": ["--preserve-symlinks"]
}
]
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"@stablelib/sha256": "^1.0.0",
"@stablelib/x25519": "^1.0.0",
"@stablelib/xchacha20poly1305": "^1.0.0",
"did-resolver": "^2.1.1",
"did-resolver": "^2.1.2",
"elliptic": "^6.5.3",
"js-sha3": "^0.8.0",
"uint8arrays": "^1.1.0"
Expand Down
60 changes: 44 additions & 16 deletions src/JWT.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import VerifierAlgorithm from './VerifierAlgorithm'
import SignerAlgorithm from './SignerAlgorithm'
import { encodeBase64url, decodeBase64url, EcdsaSignature } from './util'
import { DIDDocument, PublicKey } from 'did-resolver'
import { DIDDocument, PublicKey, Authentication } from 'did-resolver'

export type Signer = (data: string) => Promise<EcdsaSignature | string>
export type SignerAlgorithm = (payload: string, signer: Signer) => Promise<string>
Expand Down Expand Up @@ -75,8 +75,18 @@ export interface PublicKeyTypes {
[name: string]: string[]
}
export const SUPPORTED_PUBLIC_KEY_TYPES: PublicKeyTypes = {
ES256K: ['Secp256k1VerificationKey2018', 'Secp256k1SignatureVerificationKey2018', 'EcdsaPublicKeySecp256k1', 'EcdsaSecp256k1VerificationKey2019'],
'ES256K-R': ['Secp256k1VerificationKey2018', 'Secp256k1SignatureVerificationKey2018', 'EcdsaPublicKeySecp256k1', 'EcdsaSecp256k1VerificationKey2019'],
ES256K: [
'Secp256k1VerificationKey2018',
'Secp256k1SignatureVerificationKey2018',
'EcdsaPublicKeySecp256k1',
'EcdsaSecp256k1VerificationKey2019'
],
'ES256K-R': [
'Secp256k1VerificationKey2018',
'Secp256k1SignatureVerificationKey2018',
'EcdsaPublicKeySecp256k1',
'EcdsaSecp256k1VerificationKey2019'
],
Ed25519: ['ED25519SignatureVerification', 'Ed25519VerificationKey2018'],
EdDSA: ['ED25519SignatureVerification', 'Ed25519VerificationKey2018']
}
Expand Down Expand Up @@ -119,7 +129,7 @@ export function decodeJWT(jwt: string): JWTDecoded {
const jws = decodeJWS(jwt)
const decodedJwt: JWTDecoded = Object.assign(jws, { payload: JSON.parse(decodeBase64url(jws.payload)) })
return decodedJwt
} catch(e) {
} catch (e) {
throw new Error('Incorrect format JWT')
}
}
Expand All @@ -136,7 +146,11 @@ export function decodeJWT(jwt: string): JWTDecoded {
* @param {Object} header optional object to specify or customize the JWS header
* @return {Promise<Object, Error>} a promise which resolves with a JWS string or rejects with an error
*/
export async function createJWS(payload: string | any, signer: Signer, header: Partial<JWTHeader> = {}): Promise<string> {
export async function createJWS(
payload: string | any,
signer: Signer,
header: Partial<JWTHeader> = {}
): Promise<string> {
if (!header.alg) header.alg = defaultAlg
const encodedPayload = typeof payload === 'string' ? payload : encodeSection(payload)
const signingInput: string = [encodeSection(header), encodedPayload].join('.')
Expand Down Expand Up @@ -269,7 +283,7 @@ export async function verifyJWT(
throw new Error('JWT audience is required but your app address has not been configured')
}
const audArray = Array.isArray(payload.aud) ? payload.aud : [payload.aud]
const matchedAudience = audArray.find(item => options.audience === item || options.callbackUrl === item)
const matchedAudience = audArray.find((item) => options.audience === item || options.callbackUrl === item)

if (typeof matchedAudience === 'undefined') {
throw new Error(`JWT audience does not match your DID or callback url`)
Expand Down Expand Up @@ -307,19 +321,33 @@ export async function resolveAuthenticator(
}
const doc: DIDDocument = await resolver.resolve(issuer)
if (!doc) throw new Error(`Unable to resolve DID document for ${issuer}`)
// is there some way to have authenticationKeys be a single type?
const authenticationKeys: boolean | string[] = auth
? (doc.authentication || []).map(({ publicKey }) => publicKey)
: true
const authenticators: PublicKey[] = (doc.publicKey || []).filter(({ type, id }) =>
types.find(
supported =>
supported === type && (!auth || (Array.isArray(authenticationKeys) && authenticationKeys.indexOf(id) >= 0))
)

let getPublicKeyById = (doc: DIDDocument, pubid: string): PublicKey | null => {
const filtered = doc.publicKey.filter(({ id }) => pubid === id)
return filtered.length > 0 ? filtered[0] : null
}

let publicKeysToCheck: PublicKey[] = doc.publicKey || []
if (auth) {
publicKeysToCheck = (doc.authentication || [])
.map((authEntry) => {
if (typeof authEntry === 'string') {
return getPublicKeyById(doc, authEntry)
} else if (typeof (<Authentication>authEntry).publicKey === 'string') {
return getPublicKeyById(doc, (<Authentication>authEntry).publicKey)
} else {
return <PublicKey>authEntry
}
})
.filter((key) => key != null)
}

const authenticators: PublicKey[] = publicKeysToCheck.filter(({ type }) =>
types.find((supported) => supported === type)
)

if (auth && (!authenticators || authenticators.length === 0)) {
throw new Error(`DID document for ${issuer} does not have public keys suitable for authenticationg user`)
throw new Error(`DID document for ${issuer} does not have public keys suitable for authenticating user`)
}
if (!authenticators || authenticators.length === 0) {
throw new Error(`DID document for ${issuer} does not have public keys for ${alg}`)
Expand Down
58 changes: 57 additions & 1 deletion src/__tests__/JWT-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,27 @@ describe('resolveAuthenticator()', () => {
publicKey: edKey.id
}

const edKey6 = {
id: `${did}#keys-auth6`,
type: 'ED25519SignatureVerification',
owner: did,
publicKeyBase58: 'dummyvalue'
}

const ecKey7 = {
id: `${did}#keys-auth7`,
type: 'EcdsaSecp256k1VerificationKey2019',
owner: did,
publicKeyBase58: 'dummyvalue'
}

const edKey8 = {
id: `${did}#keys-auth8`,
type: 'Ed25519VerificationKey2018',
owner: did,
publicKeyBase58: 'dummyvalue'
}

const singleKey = {
'@context': 'https://w3id.org/did/v1',
id: did,
Expand All @@ -449,6 +470,13 @@ describe('resolveAuthenticator()', () => {
authentication: [authKey1, authKey2, edAuthKey]
}

const multipleAuthTypes = {
'@context': 'https://w3id.org/did/v1',
id: did,
publicKey: [ecKey1, ecKey2, ecKey3, encKey1, edKey, edKey2, edKey6, ecKey7],
authentication: [authKey1, authKey2, edAuthKey, `${did}#keys-auth6`, `${did}#keys-auth7`, edKey8]
}

const unsupportedFormat = {
'@context': 'https://w3id.org/did/v1',
id: did,
Expand Down Expand Up @@ -497,6 +525,20 @@ describe('resolveAuthenticator()', () => {
})
})

it('lists authenticators with multiple key types in doc', async () => {
const authenticators = await resolveAuthenticator(
{ resolve: jest.fn().mockReturnValue(multipleAuthTypes) },
alg,
did,
true
)
return expect(authenticators).toEqual({
authenticators: [ecKey1, ecKey2, ecKey7],
issuer: did,
doc: multipleAuthTypes
})
})

it('errors if no suitable public keys exist', async () => {
return await expect(
resolveAuthenticator({ resolve: jest.fn().mockReturnValue(unsupportedFormat) }, alg, did)
Expand Down Expand Up @@ -533,6 +575,20 @@ describe('resolveAuthenticator()', () => {
})
})

it('lists authenticators with multiple key types in doc', async () => {
const authenticators = await resolveAuthenticator(
{ resolve: jest.fn().mockReturnValue(multipleAuthTypes) },
alg,
did,
true
)
return expect(authenticators).toEqual({
authenticators: [edKey, edKey6, edKey8],
issuer: did,
doc: multipleAuthTypes
})
})

it('errors if no suitable public keys exist', async () => {
return await expect(
resolveAuthenticator({ resolve: jest.fn().mockReturnValue(unsupportedFormat) }, alg, did)
Expand All @@ -544,7 +600,7 @@ describe('resolveAuthenticator()', () => {
return await expect(
resolveAuthenticator({ resolve: jest.fn().mockReturnValue(singleKey) }, alg, did, true)
).rejects.toEqual(
new Error(`DID document for ${did} does not have public keys suitable for authenticationg user`)
new Error(`DID document for ${did} does not have public keys suitable for authenticating user`)
)
})

Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4226,10 +4226,10 @@ did-resolver@^1.0.0:
resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-1.1.0.tgz#27a63b6f2aa8dee3d622cd8b8b47360661e24f1e"
integrity sha512-Q02Sc5VuQnJzzR8fQ/DzyCHiYb31WpQdocOsxppI66wwT4XalYRDeCr3a48mL6sYPQo76AkBh0mxte9ZBuQzxA==

did-resolver@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-2.1.1.tgz#43796f8a3e921644e5fb15a8147684ca87019cfd"
integrity sha512-FYLTkNWofjYNDGV1HTQlyVu1OqZiFxR4I8KM+oxGVOkbXva15NfWzbzciqdXUDqOhe6so5aroAdrVip6gSAYSA==
did-resolver@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-2.1.2.tgz#f1194fdbc087161809ce545e13c11a596f4a3928"
integrity sha512-n4YGS1CzbX48U/ChLRY3SdgiV5N3B/+yh0ToS5t+Sx4QjhVZN6ZyijUSSYbFGvz+I4qImeSZOdo6RX8JaieN7A==

diff-sequences@^25.2.6:
version "25.2.6"
Expand Down

0 comments on commit a10ca34

Please sign in to comment.