Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#206 - Introduced support for custom signer #502

Merged
merged 11 commits into from
Sep 18, 2023
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Decentralized Web Node (DWN) SDK <!-- omit in toc -->

Code Coverage
![Statements](https://img.shields.io/badge/statements-97.69%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-94.87%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.16%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.69%25-brightgreen.svg?style=flat)
![Statements](https://img.shields.io/badge/statements-97.74%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.2%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.74%25-brightgreen.svg?style=flat)

- [Introduction](#introduction)
- [Installation](#installation)
Expand Down Expand Up @@ -92,7 +92,7 @@ DWN SDK includes a polyfilled distribution that can imported in a `module` scrip
dataFormat: 'application/json',
published: true,
schema: 'yeeter/post',
authorizationSignatureInput: Jws.createSignatureInput(didKey)
authorizationSigner: Jws.createSigner(didKey)
});

// get the DWN to process the RecordsWrite
Expand Down Expand Up @@ -191,7 +191,7 @@ const recordsWrite = await RecordsWrite.create({
dataFormat: 'application/json',
published: true,
schema: 'yeeter/post',
authorizationSignatureInput: Jws.createSignatureInput(didKey)
authorizationSigner: Jws.createSigner(didKey)
});

// get the DWN to process the RecordsWrite
Expand Down Expand Up @@ -234,6 +234,30 @@ const tenantGate = new CustomTenantGate();
const dwn = await Dwn.create({ messageStore, dataStore, eventLog, tenantGate });
```

### Custom Signature Signer
If you have the private key readily available, it is recommended to use the built-in `PrivateKeySigner`. Otherwise, you can implement a customer signer to interface with external signing service, API, HSM, TPM etc and use it for signing your DWN messages:

```ts
// create a custom signer
class CustomSigner implements Signer {
public keyId = 'did:example:alice#key1';
public algorithm = 'EdDSA'; // use valid `alg` value published in https://www.iana.org/assignments/jose/jose.xhtml
public async sign (content: Uint8Array): Promise<Uint8Array> {
... // custom signing logic
}
}

const authorizationSigner = new CustomSigner();

const options: RecordsWriteOptions = {
...
authorizationSigner
};

const recordsWrite = await RecordsWrite.create(options);
```


## Release/Build Process

The DWN JS SDK releases builds to [npmjs.com](https://www.npmjs.com/package/@tbd54566975/dwn-sdk-js). There are two build types: stable build and unstable build.
Expand Down
5 changes: 4 additions & 1 deletion src/core/dwn-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export enum DwnErrorCode {
PermissionsGrantUnauthorizedGrant = 'PermissionsGrantUnauthorizedGrant',
PermissionsRevokeMissingPermissionsGrant = 'PermissionsRevokeMissingPermissionsGrant',
PermissionsRevokeUnauthorizedRevoke = 'PermissionsRevokeUnauthorizedRevoke',
PrivateKeySignerUnableToDeduceAlgorithm = 'PrivateKeySignerUnableToDeduceAlgorithm',
PrivateKeySignerUnableToDeduceKeyId = 'PrivateKeySignerUnableToDeduceKeyId',
PrivateKeySignerUnsupportedCurve = 'PrivateKeySignerUnsupportedCurve',
ProtocolAuthorizationActionNotAllowed = 'ProtocolAuthorizationActionNotAllowed',
ProtocolAuthorizationIncorrectDataFormat = 'ProtocolAuthorizationIncorrectDataFormat',
ProtocolAuthorizationIncorrectProtocolPath = 'ProtocolAuthorizationIncorrectProtocolPath',
Expand All @@ -56,7 +59,7 @@ export enum DwnErrorCode {
RecordsWriteGetEntryIdUndefinedAuthor = 'RecordsWriteGetEntryIdUndefinedAuthor',
RecordsWriteDataCidMismatch = 'RecordsWriteDataCidMismatch',
RecordsWriteDataSizeMismatch = 'RecordsWriteDataSizeMismatch',
RecordsWriteMissingAuthorizationSignatureInput = 'RecordsWriteMissingAuthorizationSignatureInput',
RecordsWriteMissingauthorizationSigner = 'RecordsWriteMissingauthorizationSigner',
RecordsWriteMissingDataInPrevious = 'RecordsWriteMissingDataInPrevious',
RecordsWriteMissingDataAssociation = 'RecordsWriteMissingDataAssociation',
RecordsWriteMissingDataStream = 'RecordsWriteMissingDataStream',
Expand Down
10 changes: 5 additions & 5 deletions src/core/message.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { GeneralJws } from '../types/jws-types.js';
import type { SignatureInput } from '../types/jws-types.js';
import type { Signer } from '../types/signer.js';
import type { BaseAuthorizationPayload, Descriptor, GenericMessage } from '../types/message-types.js';

import { Cid } from '../utils/cid.js';
import { GeneralJwsSigner } from '../jose/jws/general/signer.js';
import { GeneralJwsBuilder } from '../jose/jws/general/builder.js';
import { Jws } from '../utils/jws.js';
import { lexicographicalCompare } from '../utils/string.js';
import { removeUndefinedProperties } from '../utils/object.js';
Expand Down Expand Up @@ -138,7 +138,7 @@ export abstract class Message<M extends GenericMessage> {
*/
public static async signAsAuthorization(
descriptor: Descriptor,
signatureInput: SignatureInput,
signatureInput: Signer,
permissionsGrantId?: string,
): Promise<GeneralJws> {
const descriptorCid = await Cid.computeCid(descriptor);
Expand All @@ -148,9 +148,9 @@ export abstract class Message<M extends GenericMessage> {
const authPayloadStr = JSON.stringify(authPayload);
const authPayloadBytes = new TextEncoder().encode(authPayloadStr);

const signer = await GeneralJwsSigner.create(authPayloadBytes, [signatureInput]);
const builder = await GeneralJwsBuilder.create(authPayloadBytes, [signatureInput]);

return signer.getJws();
return builder.getJws();
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export { MessageStore, MessageStoreOptions } from './types/message-store.js';
export { PermissionsGrant, PermissionsGrantOptions } from './interfaces/permissions-grant.js';
export { PermissionsRequest, PermissionsRequestOptions } from './interfaces/permissions-request.js';
export { PermissionsRevoke, PermissionsRevokeOptions } from './interfaces/permissions-revoke.js';
export { PrivateKeySigner } from './utils/private-key-signer.js';
export { Protocols } from './utils/protocols.js';
export { ProtocolsConfigure, ProtocolsConfigureOptions } from './interfaces/protocols-configure.js';
export { ProtocolsQuery, ProtocolsQueryOptions } from './interfaces/protocols-query.js';
Expand All @@ -46,7 +47,7 @@ export { RecordsDelete, RecordsDeleteOptions } from './interfaces/records-delete
export { RecordsRead, RecordsReadOptions } from './interfaces/records-read.js';
export { SnapshotsCreate, SnapshotsCreateOptions } from './interfaces/snapshots-create.js';
export { Secp256k1 } from './utils/secp256k1.js';
export { SignatureInput } from './types/jws-types.js';
export { Signer } from './types/signer.js';
export { DataStoreLevel } from './store/data-store-level.js';
export { EventLogLevel } from './event-log/event-log-level.js';
export { MessageStoreLevel } from './store/message-store-level.js';
6 changes: 3 additions & 3 deletions src/interfaces/events-get.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SignatureInput } from '../types/jws-types.js';
import type { Signer } from '../types/signer.js';
import type { EventsGetDescriptor, EventsGetMessage } from '../types/event-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
Expand All @@ -7,7 +7,7 @@ import { DwnInterfaceName, DwnMethodName, Message } from '../core/message.js';

export type EventsGetOptions = {
watermark?: string;
authorizationSignatureInput: SignatureInput;
authorizationSigner: Signer;
messageTimestamp?: string;
};

Expand All @@ -31,7 +31,7 @@ export class EventsGet extends Message<EventsGetMessage> {
descriptor.watermark = options.watermark;
}

const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSignatureInput);
const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSigner);
const message = { descriptor, authorization };

Message.validateJsonSchema(message);
Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/hooks-write.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SignatureInput } from '../types/jws-types.js';
import type { Signer } from '../types/signer.js';
import type { HooksWriteDescriptor, HooksWriteMessage } from '../types/hooks-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
Expand All @@ -19,7 +19,7 @@ export type HooksWriteOptions = {
filter: {
method: string,
},
authorizationSignatureInput: SignatureInput;
authorizationSigner: Signer;
};

/**
Expand All @@ -43,7 +43,7 @@ export class HooksWrite extends Message<HooksWriteMessage> {
// Error: `undefined` is not supported by the IPLD Data Model and cannot be encoded
removeUndefinedProperties(descriptor);

const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSignatureInput);
const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSigner);
const message = { descriptor, authorization };

Message.validateJsonSchema(message);
Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/messages-get.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SignatureInput } from '../types/jws-types.js';
import type { Signer } from '../types/signer.js';
import type { MessagesGetDescriptor, MessagesGetMessage } from '../types/messages-types.js';

import { Cid } from '../utils/cid.js';
Expand All @@ -8,7 +8,7 @@ import { DwnInterfaceName, DwnMethodName, Message } from '../core/message.js';

export type MessagesGetOptions = {
messageCids: string[];
authorizationSignatureInput: SignatureInput;
authorizationSigner: Signer;
messageTimestamp?: string;
};

Expand All @@ -30,7 +30,7 @@ export class MessagesGet extends Message<MessagesGetMessage> {
messageTimestamp : options?.messageTimestamp ?? getCurrentTimeInHighPrecision(),
};

const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSignatureInput);
const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSigner);
const message = { descriptor, authorization };

Message.validateJsonSchema(message);
Expand Down
12 changes: 6 additions & 6 deletions src/interfaces/permissions-grant.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { PermissionsRequest } from './permissions-request.js';
import type { SignatureInput } from '../types/jws-types.js';
import type { Signer } from '../types/signer.js';
import type { PermissionConditions, PermissionScope, RecordsPermissionScope } from '../types/permissions-types.js';
import type { PermissionsGrantDescriptor, PermissionsGrantMessage } from '../types/permissions-types.js';

Expand All @@ -19,7 +19,7 @@ export type PermissionsGrantOptions = {
permissionsRequestId?: string;
scope: PermissionScope;
conditions?: PermissionConditions;
authorizationSignatureInput: SignatureInput;
authorizationSigner: Signer;
};

export type CreateFromPermissionsRequestOverrides = {
Expand Down Expand Up @@ -60,7 +60,7 @@ export class PermissionsGrant extends Message<PermissionsGrantMessage> {
// Error: `undefined` is not supported by the IPLD Data Model and cannot be encoded
removeUndefinedProperties(descriptor);

const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSignatureInput);
const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSigner);
const message: PermissionsGrantMessage = { descriptor, authorization };

Message.validateJsonSchema(message);
Expand All @@ -72,12 +72,12 @@ export class PermissionsGrant extends Message<PermissionsGrantMessage> {
/**
* generates a PermissionsGrant using the provided PermissionsRequest
* @param permissionsRequest
* @param authorizationSignatureInput - the private key and additional signature material of the grantor
* @param authorizationSigner - the private key and additional signature material of the grantor
* @param overrides - overrides that will be used instead of the properties in `permissionsRequest`
*/
public static async createFromPermissionsRequest(
permissionsRequest: PermissionsRequest,
authorizationSignatureInput: SignatureInput,
authorizationSigner: Signer,
overrides: CreateFromPermissionsRequestOverrides,
): Promise<PermissionsGrant> {
const descriptor = permissionsRequest.message.descriptor;
Expand All @@ -90,7 +90,7 @@ export class PermissionsGrant extends Message<PermissionsGrantMessage> {
permissionsRequestId : await Message.getCid(permissionsRequest.message),
scope : overrides.scope ?? descriptor.scope,
conditions : overrides.conditions ?? descriptor.conditions,
authorizationSignatureInput,
authorizationSigner,
});
}

Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/permissions-request.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SignatureInput } from '../types/jws-types.js';
import type { Signer } from '../types/signer.js';
import type { PermissionConditions, PermissionScope } from '../types/permissions-types.js';
import type { PermissionsRequestDescriptor, PermissionsRequestMessage } from '../types/permissions-types.js';

Expand All @@ -15,7 +15,7 @@ export type PermissionsRequestOptions = {
grantedFor: string;
scope: PermissionScope;
conditions?: PermissionConditions;
authorizationSignatureInput: SignatureInput;
authorizationSigner: Signer;
};

export class PermissionsRequest extends Message<PermissionsRequestMessage> {
Expand Down Expand Up @@ -43,7 +43,7 @@ export class PermissionsRequest extends Message<PermissionsRequestMessage> {
// Error: `undefined` is not supported by the IPLD Data Model and cannot be encoded
removeUndefinedProperties(descriptor);

const auth = await Message.signAsAuthorization(descriptor, options.authorizationSignatureInput);
const auth = await Message.signAsAuthorization(descriptor, options.authorizationSigner);
const message: PermissionsRequestMessage = { descriptor, authorization: auth };

Message.validateJsonSchema(message);
Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/permissions-revoke.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SignatureInput } from '../types/jws-types.js';
import type { Signer } from '../types/signer.js';
import type { PermissionsGrantMessage, PermissionsRevokeDescriptor, PermissionsRevokeMessage } from '../types/permissions-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
Expand All @@ -9,7 +9,7 @@ import { DwnInterfaceName, DwnMethodName, Message } from '../core/message.js';
export type PermissionsRevokeOptions = {
messageTimestamp?: string;
permissionsGrantId: string;
authorizationSignatureInput: SignatureInput;
authorizationSigner: Signer;
};

export class PermissionsRevoke extends Message<PermissionsRevokeMessage> {
Expand All @@ -27,7 +27,7 @@ export class PermissionsRevoke extends Message<PermissionsRevokeMessage> {
permissionsGrantId : options.permissionsGrantId,
};

const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSignatureInput);
const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSigner);
const message: PermissionsRevokeMessage = { descriptor, authorization };

Message.validateJsonSchema(message);
Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/protocols-configure.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SignatureInput } from '../types/jws-types.js';
import type { Signer } from '../types/signer.js';
import type { ProtocolDefinition, ProtocolsConfigureDescriptor, ProtocolsConfigureMessage } from '../types/protocols-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
Expand All @@ -9,7 +9,7 @@ import { normalizeProtocolUrl, normalizeSchemaUrl, validateProtocolUrlNormalized
export type ProtocolsConfigureOptions = {
messageTimestamp? : string;
definition : ProtocolDefinition;
authorizationSignatureInput: SignatureInput;
authorizationSigner: Signer;
permissionsGrantId?: string;
};

Expand All @@ -32,7 +32,7 @@ export class ProtocolsConfigure extends Message<ProtocolsConfigureMessage> {
definition : ProtocolsConfigure.normalizeDefinition(options.definition)
};

const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSignatureInput, options.permissionsGrantId);
const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSigner, options.permissionsGrantId);
const message = { descriptor, authorization };

Message.validateJsonSchema(message);
Expand Down
9 changes: 5 additions & 4 deletions src/interfaces/protocols-query.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { GeneralJws } from '../types/jws-types.js';
import type { MessageStore } from '../types/message-store.js';
import type { GeneralJws, SignatureInput } from '../types/jws-types.js';
import type { Signer } from '../types/signer.js';
import type { ProtocolsQueryDescriptor, ProtocolsQueryFilter, ProtocolsQueryMessage } from '../types/protocols-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
Expand All @@ -14,7 +15,7 @@ import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
export type ProtocolsQueryOptions = {
messageTimestamp?: string;
filter?: ProtocolsQueryFilter,
authorizationSignatureInput?: SignatureInput;
authorizationSigner?: Signer;
permissionsGrantId?: string;
};

Expand Down Expand Up @@ -46,8 +47,8 @@ export class ProtocolsQuery extends Message<ProtocolsQueryMessage> {

// only generate the `authorization` property if signature input is given
let authorization: GeneralJws | undefined;
if (options.authorizationSignatureInput !== undefined) {
authorization = await Message.signAsAuthorization(descriptor, options.authorizationSignatureInput, options.permissionsGrantId);
if (options.authorizationSigner !== undefined) {
authorization = await Message.signAsAuthorization(descriptor, options.authorizationSigner, options.permissionsGrantId);
}

const message = { descriptor, authorization };
Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/records-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import type { RecordsDeleteDescriptor, RecordsDeleteMessage } from '../types/rec

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
import { Message } from '../core/message.js';
import type { SignatureInput } from '../types/jws-types.js';
import type { Signer } from '../types/signer.js';

import { authorize, validateAuthorizationIntegrity } from '../core/auth.js';
import { DwnInterfaceName, DwnMethodName } from '../core/message.js';

export type RecordsDeleteOptions = {
recordId: string;
messageTimestamp?: string;
authorizationSignatureInput: SignatureInput;
authorizationSigner: Signer;
};

export class RecordsDelete extends Message<RecordsDeleteMessage> {
Expand Down Expand Up @@ -38,7 +38,7 @@ export class RecordsDelete extends Message<RecordsDeleteMessage> {
messageTimestamp : options.messageTimestamp ?? currentTime
};

const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSignatureInput);
const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSigner);
const message: RecordsDeleteMessage = { descriptor, authorization };

Message.validateJsonSchema(message);
Expand Down
Loading