@ew-did-registry/claims
Version:
The package exposes functionality needed to create, inspect, approve, and verify Private and Public claims
173 lines (155 loc) • 4.98 kB
text/typescript
import { Algorithms, JWT } from '@ew-did-registry/jwt';
import { addressOf } from '@ew-did-registry/did-ethr-resolver';
import { Keys } from '@ew-did-registry/keys';
import {
IAuthentication,
IDIDDocument,
IPublicKey,
PubKeyType,
} from '@ew-did-registry/did-resolver-interface';
import { utils } from 'ethers';
import base64url from 'base64url';
const { arrayify, recoverAddress, keccak256, hashMessage } = utils;
export class ProofVerifier {
private _jwt = new JWT(new Keys());
private _didDocument: IDIDDocument;
constructor(didDocument: IDIDDocument) {
this._didDocument = didDocument;
}
/**
* @description checks that token was issued by identity represented by
* this verifier or his authentication delegate
*
* @param token
*
* @returns: DID of authenticated identity on successful verification or null otherwise
*/
public async verifyAuthenticationProof(
token: string
): Promise<string | null> {
if (
(await this.isIdentity(token)) ||
(await this.isAuthenticationDelegate(token))
) {
return this._didDocument.id;
}
return null;
}
/**
* @description Checks that token is issued by identity verification delegate
*
* @param token: JWT token to verify
* @returns: DID of the authenticated identity on successful verification or null otherwise
*/
public async verifyAssertionProof(token: string): Promise<string | null> {
if (
(await this.isIdentity(token)) ||
(await this.isVerificationDelegate(token))
) {
return this._didDocument.id;
}
return null;
}
/**
* @description Determines if a token was signed with an Ethereum signature
* by the address referenced by the id of the DID Document
* Note that JWT-compliant signatures can't be used to recover an ethereum
*/
private async isIdentity(token: string) {
const [encodedHeader, encodedPayload, encodedSignature] = token.split('.');
const msg = `0x${Buffer.from(`${encodedHeader}.${encodedPayload}`).toString(
'hex'
)}`;
const signature = base64url.decode(encodedSignature);
const hash = arrayify(keccak256(msg));
const claimedAddress = addressOf(this._didDocument.id);
try {
if (claimedAddress === recoverAddress(hash, signature)) {
return true;
}
} catch {
// ignore catch
}
const digest = arrayify(hashMessage(hash));
try {
if (claimedAddress === recoverAddress(digest, signature)) {
return true;
}
} catch {
// ignore catch
}
return false;
}
private async isAuthenticationDelegate(token: string) {
const validKeys = await this.verifySignature(
this.authenticationKeys(),
token
);
return validKeys.length !== 0;
}
private async isVerificationDelegate(token: string) {
const validKeys = await this.verifySignature(
this.verificationKeys(),
token
);
return validKeys.length !== 0;
}
private verifySignature = async (keys: IPublicKey[], token: string) => {
const results = await Promise.all(
keys.map(async (pubKeyField: IPublicKey): Promise<boolean> => {
try {
if (pubKeyField.publicKeyHex) {
const parts = pubKeyField.publicKeyHex.split('x');
const publickey = parts.length === 2 ? parts[1] : parts[0];
const decodedClaim = await this._jwt.verify(token, publickey, {
algorithms: [Algorithms.ES256, Algorithms.EIP191],
});
return decodedClaim !== undefined;
}
return false;
} catch (error) {
return false;
}
})
);
return keys.filter((_key, index) => results[index]);
};
private authenticationKeys(): IPublicKey[] {
const didPubKeys = this._didDocument.publicKey;
if (didPubKeys.length === 0) {
return [];
}
return didPubKeys.filter(
(key) =>
this.isSigAuth(key.type) ||
this._didDocument.authentication.some(
(auth) =>
(auth as IAuthentication).publicKey &&
this.areLinked((auth as IAuthentication).publicKey, key.id)
)
);
}
private verificationKeys(): IPublicKey[] {
const didPubKeys = this._didDocument.publicKey;
if (didPubKeys.length === 0) {
return [];
}
return didPubKeys.filter((key) => this.isVeriKey(key.type));
}
// used to check if publicKey field in authentication refers to publicKey ID in publicKey field
private areLinked = (authId: string, pubKeyID: string) => {
if (authId === pubKeyID) {
return true;
}
if (authId.includes('#')) {
return pubKeyID.split('#')[0] === authId.split('#')[0];
}
return false;
};
private isSigAuth(pubKeyType: string) {
return pubKeyType.endsWith(PubKeyType.SignatureAuthentication2018);
}
private isVeriKey(pubKeyType: string) {
return pubKeyType.endsWith(PubKeyType.VerificationKey2018);
}
}