Skip to content

Commit

Permalink
l3pkt: store pending signing operation
Browse files Browse the repository at this point in the history
  • Loading branch information
yoursunny committed Sep 23, 2019
1 parent 2c5e1d3 commit 2237d7c
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 50 deletions.
22 changes: 16 additions & 6 deletions packages/l3pkt/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Component, Name, NameLike } from "@ndn/name";
import { Decoder, Encodable, Encoder, EvDecoder, NNI } from "@ndn/tlv";

import { SigType, TT } from "./an";
import { LLSign } from "./llsign";
import { LLSign, LLVerify } from "./llsign";
import { DSigInfo } from "./sig-info";

const FAKE_SIGINFO = (() => {
Expand All @@ -22,8 +22,10 @@ const EVD = new EvDecoder<Data>("Data", TT.Data)
)
.add(TT.Content, (self, { value }) => self.content = value)
.add(TT.DSigInfo, (self, { decoder }) => self.sigInfo = decoder.decode(DSigInfo))
.add(TT.DSigValue, (self, { value, before }) =>
[self.sigValue, self[LLSign.SIGNED]] = [value, before]);
.add(TT.DSigValue, (self, { value, before }) => {
self.sigValue = value;
LLVerify.saveSignedPortion(self, before);
});

/** Data packet. */
export class Data {
Expand Down Expand Up @@ -59,7 +61,8 @@ export class Data {
public content: Uint8Array = new Uint8Array();
public sigInfo: DSigInfo = FAKE_SIGINFO;
public sigValue: Uint8Array = FAKE_SIGVALUE;
public [LLSign.SIGNED]?: Uint8Array;
public [LLSign.PENDING]?: LLSign;
public [LLVerify.SIGNED]?: Uint8Array;

private contentType_: number = 0;
private freshnessPeriod_: number = 0;
Expand Down Expand Up @@ -96,14 +99,21 @@ export class Data {
}

public encodeTo(encoder: Encoder) {
LLSign.encodeErrorIfPending(this);
encoder.prependTlv(TT.Data,
...this.getSignedPortion(),
[TT.DSigValue, this.sigValue],
);
}

public [LLSign.GetSignedPortion]() {
return this.getSignedPortion();
public [LLSign.PROCESS](): Promise<void> {
return LLSign.processImpl(this,
() => Encoder.encode(this.getSignedPortion()),
(sig) => this.sigValue = sig);
}

public [LLVerify.VERIFY](verify: LLVerify): Promise<void> {
return LLVerify.verifyImpl(this, this.sigValue, verify);
}

private getSignedPortion(): Encodable[] {
Expand Down
86 changes: 55 additions & 31 deletions packages/l3pkt/src/llsign.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
import { Encodable, Encoder } from "@ndn/tlv";

interface StoredSig {
/** Signed portion, saved during decoding or signing. */
[LLSign.SIGNED]?: Uint8Array;
/** TLV-VALUE of SignatureValue. */
sigValue: Uint8Array;
}

/**
* Low-level signing function.
* This function only concerns about crypto, not naming or policy.
Expand All @@ -15,19 +6,42 @@ interface StoredSig {
export type LLSign = (input: Uint8Array) => Promise<Uint8Array>;

export namespace LLSign {
export const SIGNED = Symbol("LLSign.SIGNED");
export const GetSignedPortion = Symbol("LLSign.GetSignedPortion");

export type Signable = StoredSig & {
/** Retrieve portion of packet to be covered by signature. */
[GetSignedPortion]: () => Encodable|Encodable[];
};

/** Call signing function and store signature. */
export async function call(sign: LLSign, obj: Signable): Promise<void> {
const input = Encoder.encode(obj[GetSignedPortion]());
obj.sigValue = await sign(input);
obj[SIGNED] = input;
export const PENDING = Symbol("LLSign.PENDING");
export const PROCESS = Symbol("LLSign.PROCESS");

export interface Signable {
/** Pending signing operation to be processed prior to encoding. */
[PENDING]?: LLSign;
/** Process pending signing operation. */
[PROCESS](): Promise<void>;
}

/**
* Throw an error if there is a pending signing operation.
* This should be invoked in full packet encoding function.
*/
export function encodeErrorIfPending(obj: Signable) {
if (typeof obj[PENDING] !== "undefined") {
throw new Error("cannot encode due to pending signing operation");
}
}

/**
* Process pending signing operation, if any.
* @param obj packet object that implements signable interface.
* @param getSignedPortion callback to obtain signed portion.
* @param setSigValue callback to store signature; if returning Promise, it will be await-ed.
*/
export async function processImpl(obj: Signable, getSignedPortion: () => Uint8Array,
setSigValue: (sig: Uint8Array) => any): Promise<void> {
const sign = obj[PENDING];
if (typeof sign === "undefined") {
return;
}
const input = getSignedPortion();
const sig = await sign(input);
obj[PENDING] = undefined;
await Promise.resolve(setSigValue(sig));
}
}

Expand All @@ -40,17 +54,27 @@ export namespace LLSign {
export type LLVerify = (input: Uint8Array, sig: Uint8Array) => Promise<void>;

export namespace LLVerify {
export type Verifiable = Readonly<StoredSig>;
export const SIGNED = Symbol("LLVerify.SIGNED");
export const VERIFY = Symbol("LLVerify.VERIFY");

export interface Verifiable {
/** Signed portion stored during decoding. */
[SIGNED]?: Uint8Array;
/** Verify packet using given verification function. */
[VERIFY](verify: LLVerify): Promise<void>;
}

/** Call verification function on existing signed portion and signature. */
export async function call(verify: LLVerify, obj: Verifiable): Promise<void> {
const input = obj[LLSign.SIGNED];
if (typeof input === "undefined") {
/** Store signed portion during decoding. */
export function saveSignedPortion(obj: Verifiable, signed: Uint8Array) {
obj[SIGNED] = signed;
}

/** Perform verification. */
export function verifyImpl(obj: Verifiable, sig: Uint8Array, verify: LLVerify): Promise<void> {
const signed = obj[LLVerify.SIGNED];
if (typeof signed === "undefined") {
return Promise.reject(new Error("signed portion is empty"));
}
return verify(input, obj.sigValue);
return verify(signed, sig);
}

/** An error to indicate signature is incorrect. */
export const BAD_SIG = new Error("incorrect signature value");
}
36 changes: 23 additions & 13 deletions packages/l3pkt/tests/llsign.t.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ class TestAlgo {
}

public sign = async (input: Uint8Array): Promise<Uint8Array> => {
await delay(10);
await delay(5);
if (this.wantSignError) {
throw new Error("mock-signing-error");
}
return this.computeSignature(input);
}

public verify = async (input: Uint8Array, sig: Uint8Array): Promise<void> => {
await delay(10);
await delay(5);
// warning: this is insecure comparison, for test case only
if (Buffer.compare(sig, this.computeSignature(input)) !== 0) {
throw LLVerify.BAD_SIG;
throw new Error("incorrect signature value");
}
}

Expand All @@ -48,19 +48,29 @@ const TABLE = [

test.each(TABLE)("sign %#", async ({ cls }) => {
const obj = new cls();
await expect(LLVerify.call(ALGO0.verify, obj)).rejects.toThrow(/empty/);
await expect(obj[LLSign.PROCESS]()).resolves.toBeUndefined(); // noop

await expect(LLSign.call(ALGO1.sign, obj)).rejects.toThrow(/mock-signing-error/);
obj[LLSign.PENDING] = ALGO1.sign;
expect(() => Encoder.encode(obj)).toThrow(/pending/);
await expect(obj[LLSign.PROCESS]()).rejects.toThrow(/mock-signing-error/);
expect(obj[LLSign.PENDING]).not.toBeUndefined();

await expect(LLSign.call(ALGO0.sign, obj)).resolves.toBeUndefined();
await expect(LLVerify.call(ALGO0.verify, obj)).resolves.toBeUndefined();

await expect(LLVerify.call(ALGO1.verify, obj)).rejects.toThrow(/incorrect/);
obj[LLSign.PENDING] = ALGO0.sign;
await expect(obj[LLSign.PROCESS]()).resolves.toBeUndefined();
expect(obj[LLSign.PENDING]).toBeUndefined();
expect(Encoder.encode(obj)).not.toBeUndefined();
});

test.each(TABLE)("verify %#", async ({ cls }) => {
const obj = new cls(new Name("/A"));
await LLSign.call(ALGO0.sign, obj);
const decoded = new Decoder(Encoder.encode(obj)).decode(cls);
await expect(LLVerify.call(ALGO0.verify, decoded)).resolves.toBeUndefined();
const src = new cls(new Name("/A"));
src[LLSign.PENDING] = ALGO0.sign;
await src[LLSign.PROCESS]();

const obj = new Decoder(Encoder.encode(src)).decode(cls);
expect(obj[LLVerify.SIGNED]).not.toBeUndefined();
await expect(obj[LLVerify.VERIFY](ALGO0.verify)).resolves.toBeUndefined();
await expect(obj[LLVerify.VERIFY](ALGO1.verify)).rejects.toThrow(/incorrect/);

const empty = new cls(); // no signed portion
await expect(empty[LLVerify.VERIFY](ALGO0.verify)).rejects.toThrow(/empty/);
});

0 comments on commit 2237d7c

Please sign in to comment.