Skip to content

Commit

Permalink
Bump agent to dwn-sdk-js v0.2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
diehuxx committed Sep 25, 2023
1 parent 6d08ed1 commit 66c1d8d
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 258 deletions.
311 changes: 59 additions & 252 deletions packages/agent/src/dwn-manager.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
import type {
import {
GenericMessage,
SignatureInput,
MessagesGetReply,
RecordsReadReply,
UnionMessageReply,
EncryptionProperty,
RecordsWriteMessage,
RecordsWriteOptions,
PrivateJwk as DwnPrivateKeyJwk,
Signer,
} from '@tbd54566975/dwn-sdk-js';

import { Jose } from '@web5/crypto';
import { DidResolver } from '@web5/dids';
import { Readable } from 'readable-stream';
import * as didUtils from '@web5/dids/utils';
import { Convert, removeUndefinedProperties } from '@web5/common';
import { Convert } from '@web5/common';

import {
EventLogLevel,
DataStoreLevel,
MessageStoreLevel,
} from '@tbd54566975/dwn-sdk-js/stores';
import {
Cid,
Dwn,
Expand All @@ -36,12 +29,16 @@ import {
ProtocolsQuery,
DwnInterfaceName,
ProtocolsConfigure,
EventLogLevel,
DataStoreLevel,
MessageStoreLevel,
} from '@tbd54566975/dwn-sdk-js';

import type { DwnRpcRequest, DwnResponse,ProcessDwnRequest, SendDwnRequest, Web5ManagedAgent } from './types/agent.js';

import { isManagedKeyPair } from './utils.js';
import { blobToIsomorphicNodeReadable, webReadableToIsomorphicNodeReadable } from './utils.js';
import { ManagedKey } from './index.js';

Check failure on line 41 in packages/agent/src/dwn-manager.ts

View workflow job for this annotation

GitHub Actions / test-with-node

'ManagedKey' is defined but never used. Allowed unused vars must match /^_/u

export type GeneralJws = {
payload: string
Expand Down Expand Up @@ -276,42 +273,13 @@ export class DwnManager {
}
}

const signingKeyId = await this.getAuthorSigningKeyId({ did: request.author });

// ! TODO: Remove this once DWN SDK supports external signers.
const dwnSignatureInput = this.getTempSignatureInput({ signingKeyId });

// TODO: Figure out how to narrow this type.
const messageCreateInput = {
...<any>request.messageOptions,
authorizationSignatureInput: dwnSignatureInput
};
const dwnAuthorizationSigner = await this.constructDwnAuthorizationSigner(request.author);

const messageCreator = dwnMessageCreators[request.messageType];

// ! TODO: START Remove this monkey patch (MP) as soon as the DWN SDK supports external signers.
// MP Step 1: Store the original methods.
const originalCreateAuthorization = RecordsWrite.createAuthorization;
const originalSignAsAuthorization = Message.signAsAuthorization;
// MP Step 2: Replace the methods.
RecordsWrite.createAuthorization = (
recordId: string,
contextId: string | undefined,
descriptorCid: string,
attestation: GeneralJws | undefined,
encryption: EncryptionProperty | undefined,
) => this.createAuthorization(recordId, contextId, descriptorCid, attestation, encryption, signingKeyId);
Message.signAsAuthorization = (
descriptor: GenericMessage['descriptor'],
signatureInput: SignatureInput,
permissionsGrantId?: string,
) => this.signAsAuthorization(descriptor, signingKeyId, permissionsGrantId);
// MP Step 3: Call the method that required monkey patching.
const dwnMessage = await messageCreator.create(messageCreateInput as any);
// MP Step 4: Restore the original methods.
RecordsWrite.createAuthorization = originalCreateAuthorization;
Message.signAsAuthorization = originalSignAsAuthorization;
// ! TODO: END Remove this monkey patch (MP) as soon as the DWN SDK supports external signers.
const dwnMessage = await messageCreator.create({
...<any>request.messageOptions,
authorizationSigner: dwnAuthorizationSigner
});

return { message: dwnMessage.toJSON(), dataStream: readableStream };
}
Expand All @@ -321,11 +289,8 @@ export class DwnManager {
}): Promise<string> {
const { did } = options;

// Get the agent instance.
const agent = this.agent;

// Get the method-specific default signing key.
const signingKeyId = await agent.didManager.getDefaultSigningKey({ did });
const signingKeyId = await this.agent.didManager.getDefaultSigningKey({ did });

if (!signingKeyId) {
throw new Error (`DwnManager: Unable to determine signing key for author: '${did}'`);
Expand All @@ -334,35 +299,54 @@ export class DwnManager {
return signingKeyId;
}

private async constructDwnAuthorizationSigner(author: string): Promise<Signer> {
const signingKeyId = await this.getAuthorSigningKeyId({ did: author });

/** DID keys stored in KeyManager use the canonicalId as an alias, so
* normalize the signing key ID before attempting to retrieve the key. **/
const parsedDid = didUtils.parseDid({ didUrl: signingKeyId });
if (!parsedDid) throw new Error(`DidIonMethod: Unable to parse DID: ${signingKeyId}`);
const normalizedDid = parsedDid.did.split(':', 3).join(':');
const normalizedSigningKeyId = `${normalizedDid}#${parsedDid.fragment}`;

const signingKey = await this.agent.keyManager.getKey({ keyRef: normalizedSigningKeyId });
if (!isManagedKeyPair(signingKey)) {
throw new Error(`DwnManager: Signing key not found for author: '${author}'`);
}

const { alg } = Jose.webCryptoToJose(signingKey.privateKey.algorithm);
if (alg === undefined) {
throw Error(`No algorithm provided to sign with key ID ${signingKeyId}`);
}

return {
keyId: signingKeyId,

Check failure on line 323 in packages/agent/src/dwn-manager.ts

View workflow job for this annotation

GitHub Actions / test-with-node

Missing space after key 'keyId'
algorithm: alg,

Check failure on line 324 in packages/agent/src/dwn-manager.ts

View workflow job for this annotation

GitHub Actions / test-with-node

Missing space after key 'algorithm'
sign: async (content: Uint8Array): Promise<Uint8Array> => {

Check failure on line 325 in packages/agent/src/dwn-manager.ts

View workflow job for this annotation

GitHub Actions / test-with-node

Missing space after key 'sign'
return await this.agent.keyManager.sign({
algorithm : signingKey.privateKey.algorithm,
data : content,
keyRef : normalizedSigningKeyId
});
}
};
}

private async getDwnMessage(options: {
author: string,
messageType: string,
messageCid: string
}): Promise<DwnMessage> {
const { author, messageType, messageCid } = options;

const signingKeyId = await this.getAuthorSigningKeyId({ did: author });
const dwnAuthorizationSigner = await this.constructDwnAuthorizationSigner(author);

// ! TODO: START Remove this monkey patch (MP) as soon as the DWN SDK supports external signers.
const dwnSignatureInput = this.getTempSignatureInput({ signingKeyId });
// MP Step 1: Store the original methods.
const originalSignAsAuthorization = Message.signAsAuthorization;
// MP Step 2: Replace the methods.
Message.signAsAuthorization = (
descriptor: GenericMessage['descriptor'],
signatureInput: SignatureInput,
permissionsGrantId?: string,
) => this.signAsAuthorization(descriptor, signingKeyId, permissionsGrantId);
// MP Step 3: Call the method that required monkey patching.
const messagesGet = await MessagesGet.create({
authorizationSignatureInput : dwnSignatureInput,
messageCids : [messageCid]
authorizationSigner : dwnAuthorizationSigner,
messageCids : [messageCid]
});
// MP Step 4: Restore the original methods.
Message.signAsAuthorization = originalSignAsAuthorization;
// ! TODO: END Remove this monkey patch (MP) as soon as the DWN SDK supports external signers.

const result: MessagesGetReply = await this._dwn.processMessage(author, messagesGet.toJSON());
const result: MessagesGetReply = await this._dwn.processMessage(author, messagesGet.message);

if (!(result.messages && result.messages.length === 1)) {
throw new Error('TODO: figure out error message');
Expand All @@ -388,8 +372,10 @@ export class DwnManager {
dwnMessage.data = new Blob([dataBytes]);
} else {
const recordsRead = await RecordsRead.create({
authorizationSignatureInput : dwnSignatureInput,
recordId : writeMessage.recordId
authorizationSigner : dwnAuthorizationSigner,
filter : {
recordId: writeMessage.recordId
}
});

const reply = await this._dwn.processMessage(author, recordsRead.toJSON()) as RecordsReadReply;
Expand All @@ -406,157 +392,6 @@ export class DwnManager {

return dwnMessage;
}

/**
* The following methods are a temporary workaround that should be removed
* once DWN SDK implements support for an external signer.
*
* - createAuthorization()
* - createJws()
* - getTempSignatureInput()
* - signAsAuthorization()
*/

private async createAuthorization(
recordId: string,
contextId: string | undefined,
descriptorCid: string,
attestation: GeneralJws | undefined,
encryption: EncryptionProperty | undefined,
signingKeyId: string,
): Promise<GeneralJws> {
const authorizationPayload: RecordsWriteAuthorizationPayload = {
recordId,
descriptorCid
};

const attestationCid = attestation ? await Cid.computeCid(attestation) : undefined;
const encryptionCid = encryption ? await Cid.computeCid(encryption) : undefined;

if (contextId !== undefined) { authorizationPayload.contextId = contextId; } // assign `contextId` only if it is defined
if (attestationCid !== undefined) { authorizationPayload.attestationCid = attestationCid; } // assign `attestationCid` only if it is defined
if (encryptionCid !== undefined) { authorizationPayload.encryptionCid = encryptionCid; } // assign `encryptionCid` only if it is defined

const authorizationPayloadU8A = Convert.object(authorizationPayload).toUint8Array();

const jws = await this.createJws(authorizationPayloadU8A, [signingKeyId]);

return jws;
}

private async createJws(payload: Uint8Array, signingKeyIds: string[] = []): Promise<GeneralJws> {
// Get the agent instance.
const agent = this.agent;

// Begin constructing a JWS.
const jws: GeneralJws = {
payload : Convert.uint8Array(payload).toBase64Url(),
signatures : []
};

for (const signingKeyId of signingKeyIds) {

/** DID keys stored in KeyManager use the canonicalId as an alias, so
* normalize the signing key ID before attempting to retrieve the key. **/
const parsedDid = didUtils.parseDid({ didUrl: signingKeyId });
if (!parsedDid) throw new Error(`DidIonMethod: Unable to parse DID: ${signingKeyId}`);
const normalizedDid = parsedDid.did.split(':', 3).join(':');
const normalizedSigningKeyId = `${normalizedDid}#${parsedDid.fragment}`;

// Attempt to retrieve the signing key.
const signingKey = await agent.keyManager.getKey({ keyRef: normalizedSigningKeyId });

if (!isManagedKeyPair(signingKey)) {
throw new Error (`DwnManager: Signing key not found for author: '${normalizedDid}'`);
}

// Get the JWS alg parameter given the key pair.
const { alg } = Jose.webCryptoToJose(signingKey.privateKey.algorithm);

// Construct the JWS protected header.
const protectedHeader = Convert.object(
{ alg, kid: signingKeyId }
).toBase64Url();

// Concatenate the dot-separated header and payload and convert to bytes.
const headerAndPayload = Convert.string(
`${protectedHeader}.${jws.payload}`
).toUint8Array();

// Sign the JWS.
const signatureBytes = await agent.keyManager.sign({
algorithm : signingKey.privateKey.algorithm,
data : headerAndPayload,
keyRef : signingKey.privateKey.id
});
const signature = Convert.uint8Array(signatureBytes).toBase64Url();

jws.signatures.push({ protected: protectedHeader, signature });
}

return jws;
}

private getTempSignatureInput({ signingKeyId }: { signingKeyId: string }): SignatureInput {
const privateJwk: DwnPrivateKeyJwk = {
alg : 'placeholder',
d : 'placeholder',
crv : 'Ed25519',
kty : 'placeholder',
x : 'placeholder'
};

const protectedHeader = {
alg : 'placeholder',
kid : signingKeyId
};

const dwnSignatureInput: SignatureInput = { privateJwk, protectedHeader };

return dwnSignatureInput;
}

private async signAsAuthorization(
descriptor: GenericMessage['descriptor'],
signingKeyId: string,
permissionsGrantId?: string
): Promise<GeneralJws> {
const descriptorCid = await Cid.computeCid(descriptor);

const authorizationPayload = { descriptorCid, permissionsGrantId };
removeUndefinedProperties(authorizationPayload);

const authorizationPayloadU8A = Convert.object(authorizationPayload).toUint8Array();

const jws = await this.createJws(authorizationPayloadU8A, [signingKeyId]);

return jws;
}


























/**
* ADDED TO GET SYNC WORKING
Expand All @@ -572,42 +407,14 @@ export class DwnManager {
}): Promise<EventsGet | MessagesGet | RecordsRead | RecordsQuery | RecordsWrite | RecordsDelete | ProtocolsQuery | ProtocolsConfigure> {
const { author, messageOptions, messageType } = options;

const signingKeyId = await this.getAuthorSigningKeyId({ did: author });

// ! TODO: Remove this once DWN SDK supports external signers.
const dwnSignatureInput = this.getTempSignatureInput({ signingKeyId });

// TODO: Figure out how to narrow this type.
const messageCreateInput = {
...<any>messageOptions,
authorizationSignatureInput: dwnSignatureInput
};
const dwnAuthorizationSigner = await this.constructDwnAuthorizationSigner(author);

const messageCreator = dwnMessageCreators[messageType];

// ! TODO: START Remove this monkey patch (MP) as soon as the DWN SDK supports external signers.
// MP Step 1: Store the original methods.
const originalCreateAuthorization = RecordsWrite.createAuthorization;
const originalSignAsAuthorization = Message.signAsAuthorization;
// MP Step 2: Replace the methods.
RecordsWrite.createAuthorization = (
recordId: string,
contextId: string | undefined,
descriptorCid: string,
attestation: GeneralJws | undefined,
encryption: EncryptionProperty | undefined,
) => this.createAuthorization(recordId, contextId, descriptorCid, attestation, encryption, signingKeyId);
Message.signAsAuthorization = (
descriptor: GenericMessage['descriptor'],
signatureInput: SignatureInput,
permissionsGrantId?: string,
) => this.signAsAuthorization(descriptor, signingKeyId, permissionsGrantId);
// MP Step 3: Call the method that required monkey patching.
const dwnMessage = await messageCreator.create(messageCreateInput as any);
// MP Step 4: Restore the original methods.
RecordsWrite.createAuthorization = originalCreateAuthorization;
Message.signAsAuthorization = originalSignAsAuthorization;
// ! TODO: END Remove this monkey patch (MP) as soon as the DWN SDK supports external signers.
const dwnMessage = await messageCreator.create({
...<any>messageOptions,
authorizationSigner: dwnAuthorizationSigner
});

return dwnMessage;
}
Expand Down
Loading

0 comments on commit 66c1d8d

Please sign in to comment.