Skip to content

Commit

Permalink
support profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
vmidyllic committed Jul 20, 2023
1 parent 92ef8d6 commit 3fbc333
Show file tree
Hide file tree
Showing 12 changed files with 532 additions and 274 deletions.
292 changes: 129 additions & 163 deletions src/iden3comm/handlers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,21 @@ import { PROTOCOL_MESSAGE_TYPE } from '../constants';

import {
AuthorizationRequestMessage,
AuthorizationRequestMessageBody,
AuthorizationResponseMessage,
IPackageManager,
PackerParams,
JWSPackerParams,
ZeroKnowledgeProofRequest,
ZeroKnowledgeProofResponse
} from '../types';
import { DID } from '@iden3/js-iden3-core';
import { proving } from '@iden3/js-jwz';
import { ProvingMethodAlg, proving } from '@iden3/js-jwz';

import * as uuid from 'uuid';
import { ICredentialWallet } from '../../credentials';
import { W3CCredential } from '../../verifiable';
import { ProofQuery, W3CCredential } from '../../verifiable';
import { byteDecoder, byteEncoder } from '../../utils';
import { IIdentityWallet, generateProfileDID } from '../../identity';

/**
* ZKP request and credential that satisfies the zkp query conditions
*
* @export
* @interface ZKPRequestWithCredential
*/
export interface ZKPRequestWithCredential {
req: ZeroKnowledgeProofRequest;
credential: W3CCredential;
credentialSubjectProfileNonce: number;
}
/**
* Interface that allows the processing of the authorization request in the raw format for given identifier
*
Expand All @@ -39,29 +28,6 @@ export interface ZKPRequestWithCredential {
* @interface IAuthHandler
*/
export interface IAuthHandler {
/**
* Handle authorization request protocol message
*
* @param {DID} id - identifier that will handle request
* @param {Uint8Array} request - request payload
* @returns `Promise<{
* token: string;
* authRequest: AuthorizationRequestMessage;
* authResponse: AuthorizationResponseMessage;
* }>`
*/
handleAuthorizationRequestForGenesisDID(options: {
did: DID;
request: Uint8Array;
packer: {
mediaType: MediaType;
} & PackerParams;
}): Promise<{
token: string;
authRequest: AuthorizationRequestMessage;
authResponse: AuthorizationResponseMessage;
}>;

/**
* unpacks authorization request
* @export
Expand All @@ -72,30 +38,39 @@ export interface IAuthHandler {
parseAuthorizationRequest(request: Uint8Array): Promise<AuthorizationRequestMessage>;

/**
* Generates zero-knowledge proofs for given requests and credentials
* unpacks authorization request
* @export
* @beta
* @param {DID} userGenesisDID - user genesis identifier for which user holds key pair.
* @param {number} authProfileNonce - profile nonce that will be used for authorization.
* @param {AuthorizationRequestMessage} authRequest - authorization request, protocol message.
* @param {ZKPRequestWithCredential[]} zkpRequestsWithCreds - zero knowledge proof request with chosen credential to use.
* @param {did} did - sender DID
* @param {Uint8Array} request - raw byte message
* @returns `Promise<{
* token: string;
* authRequest: AuthorizationRequestMessage;
* authResponse: AuthorizationResponseMessage;
* }>}`
token: string;
authRequest: AuthorizationRequestMessage;
authResponse: AuthorizationResponseMessage;
}>`
*/
generateAuthorizationResponse(
userGenesisDID: DID,
authProfileNonce: number,
authRequest: AuthorizationRequestMessage,
zkpRequestsWithCreds: ZKPRequestWithCredential[]
handleAuthorizationRequest(
did: DID,
request: Uint8Array,
opts?: AuthHandlerOptions
): Promise<{
token: string;
authRequest: AuthorizationRequestMessage;
authResponse: AuthorizationResponseMessage;
}>;
}

interface AuthHandlerOptions {
mediaType: MediaType;
packerOptions:
| {
profileNonce: number;
provingMethodAlg: ProvingMethodAlg;
}
| JWSPackerParams;
credential?: W3CCredential;
}

/**
*
* Allows to process AuthorizationRequest protocol message and produce JWZ response.
Expand All @@ -112,170 +87,161 @@ export class AuthHandler implements IAuthHandler {
* @param {IPackageManager} _packerMgr - package manager to unpack message envelope
* @param {IProofService} _proofService - proof service to verify zk proofs
* @param {ICredentialWallet} _credentialWallet - wallet to search credentials
* @param {IIdentityWallet} _identityWallet - wallet to search profiles and identities
*
*
*/
constructor(
private readonly _packerMgr: IPackageManager,
private readonly _proofService: IProofService,
private readonly _credentialWallet: ICredentialWallet
private readonly _credentialWallet: ICredentialWallet,
private readonly _identityWallet: IIdentityWallet
) {}

/**
* Handles only messages with authorization/v1.0/request type
* Generates all requested proofs and wraps authorization response message to JWZ token
* works when profiles are not supported
* @param {DID} did - an identity that will process the request
* @param {Uint8Array} request - raw request
* @returns `Promise<{token: string; authRequest: AuthorizationRequestMessage; authResponse: AuthorizationResponseMessage;}>` JWZ token, parsed request and response
* unpacks authorization request
* @export
* @beta
* @param {Uint8Array} request - raw byte message
* @returns `Promise<AuthorizationRequestMessage>`
*/
async handleAuthorizationRequestForGenesisDID(options: {
did: DID;
request: Uint8Array;
packer: {
mediaType: MediaType;
} & PackerParams;
}): Promise<{
async parseAuthorizationRequest(request: Uint8Array): Promise<AuthorizationRequestMessage> {
const { unpackedMessage: message } = await this._packerMgr.unpack(request);
const authRequest = message as unknown as AuthorizationRequestMessage;
if (message.type !== PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE) {
throw new Error('Invalid media type');
}
return authRequest;
}

async handleAuthorizationRequest(
did: DID,
request: Uint8Array,
opts?: AuthHandlerOptions
): Promise<{
token: string;
authRequest: AuthorizationRequestMessage;
authResponse: AuthorizationResponseMessage;
}> {
const {
did,
request,
packer: { mediaType, ...packerParams }
} = options;
const authRequest = await this.parseAuthorizationRequest(request);

const { unpackedMessage: message } = await this._packerMgr.unpack(request);
const authRequest = message as unknown as AuthorizationRequestMessage;
if (message.type !== PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE) {
throw new Error('Invalid media type');
if (authRequest.type !== PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE) {
throw new Error('Invalid message type for authorization request');
}

if (!opts) {
const zkpPackerOpts = {
profileNonce: 0,
provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg,
alg: ''
};

opts = {
packerOptions: zkpPackerOpts,
mediaType: MediaType.ZKPMessage
};
}
const authRequestBody = message.body as unknown as AuthorizationRequestMessageBody;

const guid = uuid.v4();
const senderDID =
opts!.mediaType === MediaType.SignedMessage

Check warning on line 147 in src/iden3comm/handlers/auth.ts

View workflow job for this annotation

GitHub Actions / build (18.16.1)

Forbidden non-null assertion
? did.string()
: generateProfileDID(did, opts!.packerOptions!.profileNonce).string();

Check warning on line 149 in src/iden3comm/handlers/auth.ts

View workflow job for this annotation

GitHub Actions / build (18.16.1)

Forbidden non-null assertion

Check warning on line 149 in src/iden3comm/handlers/auth.ts

View workflow job for this annotation

GitHub Actions / build (18.16.1)

Forbidden non-null assertion
const authResponse: AuthorizationResponseMessage = {
id: guid,
typ: mediaType,
typ: opts!.mediaType,

Check warning on line 152 in src/iden3comm/handlers/auth.ts

View workflow job for this annotation

GitHub Actions / build (18.16.1)

Forbidden non-null assertion
type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_RESPONSE_MESSAGE_TYPE,
thid: message.thid ?? guid,
thid: authRequest.thid ?? guid,
body: {
did_doc: undefined, // slipped for now, todo: get did doc for id
message: authRequestBody.message,
message: authRequest?.body?.message,
scope: []
},
from: options.did.string(),
to: message.from
from: senderDID,
to: authRequest.from
};

for (const proofReq of authRequestBody.scope) {
for (const proofReq of authRequest.body!.scope) {

Check warning on line 163 in src/iden3comm/handlers/auth.ts

View workflow job for this annotation

GitHub Actions / build (18.16.1)

Forbidden non-null assertion
const zkpReq: ZeroKnowledgeProofRequest = {
id: proofReq.id,
circuitId: proofReq.circuitId as CircuitId,
query: proofReq.query
};

const creds = await this._credentialWallet.findByQuery(proofReq.query);
const query = proofReq.query as unknown as ProofQuery;

const credsForGenesisDID = await this._credentialWallet.filterByCredentialSubject(creds, did);
if (credsForGenesisDID.length == 0) {
throw new Error(`no credential were issued on the given id ${did.string()}`);
if (opts!.credential) {

Check warning on line 172 in src/iden3comm/handlers/auth.ts

View workflow job for this annotation

GitHub Actions / build (18.16.1)

Forbidden non-null assertion
proofReq.query.claimId = opts!.credential.id;

Check warning on line 173 in src/iden3comm/handlers/auth.ts

View workflow job for this annotation

GitHub Actions / build (18.16.1)

Forbidden non-null assertion
}
const { cred } = await this._credentialWallet.findNonRevokedCredential(credsForGenesisDID);

const { credential, nonce } = await this.findCredentialForDID(did, query);

const zkpRes: ZeroKnowledgeProofResponse = await this._proofService.generateProof(
zkpReq,
did,
cred
credential,
{
credentialSubjectProfileNonce: nonce,
skipRevocation: query.skipClaimRevocationCheck ?? false,
authProfileNonce: opts!.packerOptions!.profileNonce

Check warning on line 185 in src/iden3comm/handlers/auth.ts

View workflow job for this annotation

GitHub Actions / build (18.16.1)

Forbidden non-null assertion
}
);

authResponse.body.scope.push(zkpRes);
}

const msgBytes = byteEncoder.encode(JSON.stringify(authResponse));
const token = byteDecoder.decode(
await this._packerMgr.pack(mediaType, msgBytes, {
await this._packerMgr.pack(opts!.mediaType, msgBytes, {
senderDID: did,
...packerParams
...opts!.packerOptions
})
);
return { authRequest, authResponse, token };
}

/**
* unpacks authorization request
* @export
* @beta
* @param {Uint8Array} request - raw byte message
* @returns `Promise<AuthorizationRequestMessage>`
*/
async parseAuthorizationRequest(request: Uint8Array): Promise<AuthorizationRequestMessage> {
const { unpackedMessage: message } = await this._packerMgr.unpack(request);
const authRequest = message as unknown as AuthorizationRequestMessage;
if (message.type !== PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE) {
throw new Error('Invalid media type');
async findCredentialForDID(
did: DID,
query: ProofQuery
): Promise<{ credential: W3CCredential; nonce: number }> {
const creds = await this._credentialWallet.findByQuery(query);
if (!creds.length) {
throw new Error(`no credential satisfied query`);
}
return authRequest;
}

/**
* Generates zero-knowledge proofs for given requests and credentials
* @export
* @beta
* @param {DID} userGenesisDID - user genesis identifier for which user holds key pair.
* @param {number} authProfileNonce - profile nonce that will be used for authorization.
* @param {AuthorizationRequestMessage} authRequest - authorization request, protocol message.
* @param {ZKPRequestWithCredential[]} zkpRequestsWithCreds - zero knowledge proof request with chosen credential to use.
* @returns `Promise<{
* token: string;
* authRequest: AuthorizationRequestMessage;
* authResponse: AuthorizationResponseMessage;
* }>}`
*/
async generateAuthorizationResponse(
userGenesisDID: DID,
authProfileNonce: number,
authRequest: AuthorizationRequestMessage,
zkpRequestsWithCreds?: ZKPRequestWithCredential[]
): Promise<{
token: string;
authRequest: AuthorizationRequestMessage;
authResponse: AuthorizationResponseMessage;
}> {
const guid = uuid.v4();
const authResponse: AuthorizationResponseMessage = {
id: guid,
typ: MediaType.ZKPMessage,
type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_RESPONSE_MESSAGE_TYPE,
thid: authRequest.thid ?? guid,
body: {
message: authRequest?.body?.message,
scope: []
},
from: userGenesisDID.string(),
to: authRequest.from
};
const profiles = await this._identityWallet.getProfilesByDID(did);

for (const r of zkpRequestsWithCreds || []) {
const zkpRes: ZeroKnowledgeProofResponse = await this._proofService.generateProof(
r.req,
userGenesisDID,
r.credential,
{
authProfileNonce: authProfileNonce,
credentialSubjectProfileNonce: r.credentialSubjectProfileNonce,
skipRevocation: false
}
const ownedCreds = creds.filter((cred) => {
const credentialSubjectId = cred.credentialSubject['id'] as string; // credential subject
return (
credentialSubjectId == did.string() ||
profiles.some((p) => {
return p.id === credentialSubjectId;
})
);
});

authResponse.body.scope.push(zkpRes);
if (!ownedCreds.length) {
throw new Error(`no credentials belong to did ot its profiles`);
}
const msgBytes = byteEncoder.encode(JSON.stringify(authResponse));
const token = byteDecoder.decode(
await this._packerMgr.pack(MediaType.ZKPMessage, msgBytes, {
senderDID: userGenesisDID,
profileNonce: authProfileNonce,
provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg
})
);
return { authRequest, authResponse, token };

// For EQ / IN / NIN / LT / GT operations selective if credential satisfies query - we can get any.
// TODO: choose credential for selective credentials
const cred = query.skipClaimRevocationCheck
? ownedCreds[0]
: (await this._credentialWallet.findNonRevokedCredential(ownedCreds)).cred;

// get profile nonce that was used as a part of subject in the credential

let subjectDID = cred.credentialSubject['id'];
if (!subjectDID) {
subjectDID = cred.issuer; // self credential
}
const credentialSubjectProfileNonce =
subjectDID === did.string()
? 0
: profiles.find((p) => {
return p.id === subjectDID;
})!.nonce;

return { credential: cred, nonce: credentialSubjectProfileNonce };
}
}
Loading

0 comments on commit 3fbc333

Please sign in to comment.