Skip to content

Commit

Permalink
l3pkt: Interest ParamsDigest computation
Browse files Browse the repository at this point in the history
  • Loading branch information
yoursunny committed Sep 24, 2019
1 parent ebaf067 commit 19eed49
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 61 deletions.
4 changes: 4 additions & 0 deletions packages/l3pkt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
"files": [
"lib"
],
"browser": {
"./lib/sha256.js": "./lib/sha256-browser.js",
"./src/sha256.ts": "./src/sha256-browser.ts"
},
"sideEffects": [],
"homepage": "https://yoursunny.com/p/NDNts/",
"repository": {
Expand Down
1 change: 1 addition & 0 deletions packages/l3pkt/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from "./an";
export * from "./data";
export * from "./interest";
export * from "./llsign";
export * from "./sha256";
export * from "./sig-info";
115 changes: 74 additions & 41 deletions packages/l3pkt/src/interest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ import { Decoder, Encoder, EvDecoder, NNI } from "@ndn/tlv";

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

const LIFETIME_DEFAULT = 4000;
const HOPLIMIT_MAX = 255;
const FAKE_PARAMS_DIGEST = new Uint8Array((function*() {
for (let i = 0; i < 32; i += 2) {
yield 0xBE;
yield 0xEF;
}
})());
const DecodeParams = Symbol("Interest.DecodeParams");
const DigestValidated = Symbol("Interest.DigestValidated");

const EVD = new EvDecoder<Interest>("Interest", TT.Interest)
.add(TT.Name, (self, { decoder }) => self.name = decoder.decode(Name))
Expand All @@ -22,31 +19,33 @@ const EVD = new EvDecoder<Interest>("Interest", TT.Interest)
.add(TT.Nonce, (self, { value }) => self.nonce = NNI.decode(value, 4))
.add(TT.InterestLifetime, (self, { value }) => self.lifetime = NNI.decode(value))
.add(TT.HopLimit, (self, { value }) => self.hopLimit = NNI.decode(value, 1))
.add(TT.AppParameters, (self, { value, tlv }) => {
if (ParamsDigest.findIn(self.name) < 0) {
.add(TT.AppParameters, (self, { value, tlv, after }) => {
if (ParamsDigest.findIn(self.name, false) < 0) {
throw new Error("ParamsDigest missing in parameterized Interest");
}
self.appParameters = value;
self[LLVerify.SIGNED] = tlv;
self[DecodeParams] = new Uint8Array(tlv.buffer, tlv.byteOffset,
tlv.byteLength + after.byteLength);
})
.add(TT.ISigInfo, (self, { decoder }) => self.sigInfo = decoder.decode(ISigInfo))
.add(TT.ISigValue, (self, { value, tlv }) => {
if (!ParamsDigest.match(self.name.at(-1))) {
throw new Error("ParamsDigest missing or out of place in signed Interest");
}
const appParametersTlv = self[LLVerify.SIGNED];
if (typeof appParametersTlv === "undefined") {

const params = self[DecodeParams];
if (!(params instanceof Uint8Array)) {
throw new Error("AppParameters missing in signed Interest");
}

if (typeof self.sigInfo === "undefined") {
throw new Error("ISigInfo missing in signed Interest");
}

self.sigValue = value;
self[LLVerify.SIGNED] = Encoder.encode([
self.name.getPrefix(-1).valueOnly,
new Uint8Array(appParametersTlv.buffer, appParametersTlv.byteOffset,
tlv.byteOffset - appParametersTlv.byteOffset),
new Uint8Array(tlv.buffer, params.byteOffset, tlv.byteOffset - params.byteOffset),
]);
});

Expand Down Expand Up @@ -74,6 +73,13 @@ export class Interest {
public [LLSign.PENDING]?: LLSign;
public [LLVerify.SIGNED]?: Uint8Array;

/**
* Portion covered by ParamsDigest.
* This is set to Uint8Array during decoding, when AppParameters is present.
* 'DigestValidated' indicates ParamsDigest has been validated.
*/
public [DecodeParams]?: Uint8Array|typeof DigestValidated;

private nonce_: number|undefined;
private lifetime_: number = LIFETIME_DEFAULT;
private hopLimit_: number = HOPLIMIT_MAX;
Expand Down Expand Up @@ -139,6 +145,21 @@ export class Interest {
);
}

public async updateParamsDigest(): Promise<void> {
let pdIndex = ParamsDigest.findIn(this.name);
if (pdIndex < 0) {
pdIndex = this.appendParamsDigestPlaceholder();
}

const params = Encoder.encode([
[TT.AppParameters, this.appParameters],
this.sigInfo,
[TT.ISigValue, Encoder.OmitEmpty, this.sigValue],
]);
const d = await sha256(params);
this.name = this.name.replaceAt(pdIndex, ParamsDigest.create(d));
}

public [LLSign.PROCESS](): Promise<void> {
this.insertParamsDigest();
return LLSign.processImpl(this,
Expand All @@ -153,54 +174,66 @@ export class Interest {
});
}

public [LLVerify.VERIFY](verify: LLVerify): Promise<void> {
public async validateParamsDigest(): Promise<void> {
if (typeof this.appParameters === "undefined") {
return;
}

const params = this[DecodeParams];
if (params === DigestValidated) {
return;
}
if (typeof params === "undefined") {
throw new Error("parameters portion is empty");
}

const pdComp = this.name.at(ParamsDigest.findIn(this.name, false));
const d = await sha256(params);
// This is not a constant-time comparison. It's for integrity purpose only.
if (!pdComp.equals(ParamsDigest.create(d))) {
throw new Error("incorrect ParamsDigest");
}
this[DecodeParams] = DigestValidated;
}

public async [LLVerify.VERIFY](verify: LLVerify): Promise<void> {
await this.validateParamsDigest();
if (!this.sigValue) {
return Promise.resolve();
return;
}
return LLVerify.verifyImpl(this, this.sigValue, verify);
await LLVerify.verifyImpl(this, this.sigValue, verify);
}

private insertParamsDigest() {
if (this.name.size < 1) {
throw new Error("Interest name is empty");
}

let pdIndex = ParamsDigest.findIn(this.name);
let pdAppendPlaceholder = false;
const pdIndex = ParamsDigest.findIn(this.name);
if (this.sigInfo) {
if (pdIndex < 0) {
pdAppendPlaceholder = true;
} else if (pdIndex !== this.name.size - 1) {
if (pdIndex >= 0 && pdIndex !== this.name.size - 1) {
throw new Error("ParamsDigest out of place for signed Interest");
}

if (!this.appParameters) {
this.appParameters = new Uint8Array();
}
} else if (this.appParameters) {
if (pdIndex < 0) {
pdAppendPlaceholder = true;
}
} else if (pdIndex >= 0) {
this.appParameters = new Uint8Array();
} else {
}
if (this.sigInfo || pdIndex >= 0) {
this.appParameters = this.appParameters || new Uint8Array();
}
if (!this.appParameters) {
return; // not a parameterized or signed Interest
}

if (pdAppendPlaceholder) {
pdIndex = this.name.size;
this.name = this.name.append(ParamsDigest.PLACEHOLDER);
if (pdIndex < 0) {
this.appendParamsDigestPlaceholder();
}

if (ParamsDigest.isPlaceholder(this.name.at(pdIndex)) && !this[LLSign.PENDING]) {
if ((pdIndex < 0 || ParamsDigest.isPlaceholder(this.name.at(pdIndex))) && !this[LLSign.PENDING]) {
// insert noop signing operation to trigger digest update
this[LLSign.PENDING] = () => Promise.resolve(new Uint8Array());
}
}

private async updateParamsDigest(): Promise<void> {
const pdIndex = ParamsDigest.findIn(this.name);
const newDigest = FAKE_PARAMS_DIGEST; // TODO compute digest
this.name = this.name.replaceAt(pdIndex, ParamsDigest.create(newDigest));
private appendParamsDigestPlaceholder(): number {
this.name = this.name.append(ParamsDigest.PLACEHOLDER);
return this.name.size - 1;
}
}

Expand Down
4 changes: 4 additions & 0 deletions packages/l3pkt/src/sha256-browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export async function sha256(input: Uint8Array): Promise<Uint8Array> {
const digest = await self.crypto.subtle.digest("sha256", input);
return new Uint8Array(digest);
}
8 changes: 8 additions & 0 deletions packages/l3pkt/src/sha256.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createHash } from "crypto";

export async function sha256(input: Uint8Array): Promise<Uint8Array> {
const hash = createHash("sha256");
hash.update(input);
const d = hash.digest();
return new Uint8Array(d.buffer, d.byteOffset, d.byteLength);
}
43 changes: 23 additions & 20 deletions packages/l3pkt/tests/interest.t.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ test("encode", () => {
]);
});

test("decode", () => {
test("decode", async () => {
let decoder = new Decoder(new Uint8Array([
0x05, 0x05,
0x07, 0x03, 0x08, 0x01, 0x41,
Expand Down Expand Up @@ -87,6 +87,9 @@ test("decode", () => {
expect(interest.appParameters).toBeUndefined();
expect(interest.sigInfo).toBeUndefined();
expect(interest.sigValue).toBeUndefined();

// noop for non parameterized Interest
await expect(interest.validateParamsDigest()).resolves.toBeUndefined();
});

async function encodeWithLLSign(interest: Interest): Promise<Uint8Array> {
Expand Down Expand Up @@ -114,20 +117,13 @@ test("encode parameterized", async () => {

// append ParamsDigest
interest = new Interest(new Name("/A"), new Uint8Array([0xC0, 0xC1]));
await expect(encodeWithLLSign(interest)).resolves.toEncodeAs(({ value }) => {
expect(value).toMatchTlv(
({ decoder }) => {
const name = decoder.decode(Name);
expect(name.size).toBe(2);
expect(name.at(1).is(ParamsDigest)).toBeTruthy();
},
({ type }) => expect(type).toBe(TT.Nonce),
({ type, value }) => {
expect(type).toBe(TT.AppParameters);
expect(value).toEqualUint8Array([0xC0, 0xC1]);
},
);
});
await interest.updateParamsDigest();
expect(interest.name.size).toBe(2);
expect(interest.name.at(1).is(ParamsDigest)).toBeTruthy();
expect(Encoder.encode(interest)).toBeInstanceOf(Uint8Array);

// cannot validate unless Interest comes from decoding
await expect(interest.validateParamsDigest()).rejects.toThrow(/empty/);
});

test("decode parameterized", async () => {
Expand All @@ -147,18 +143,25 @@ test("decode parameterized", async () => {
expect(interest.name.size).toBe(2);
expect(interest.appParameters).not.toBeUndefined();

decoder = new Decoder(Encoder.encode([
TT.Interest,
new Name("/A").append(ParamsDigest, new Uint8Array(32)).append("C"),
[TT.AppParameters, new Uint8Array([0xC0, 0xC1])],
]));
const wire = await encodeWithLLSign(new Interest(
new Name("/A").append(ParamsDigest.PLACEHOLDER).append("C"),
new Uint8Array([0xC0, 0xC1]),
));
decoder = new Decoder(wire);
interest = decoder.decode(Interest);
expect(interest.name.size).toBe(3);
expect(interest.appParameters).not.toBeUndefined();

const verify = jest.fn();
await expect(interest[LLVerify.VERIFY](verify)).resolves.toBeUndefined();
expect(verify).not.toHaveBeenCalled();

await expect(interest.validateParamsDigest()).resolves.toBeUndefined();

decoder = new Decoder(wire);
interest = decoder.decode(Interest);
interest.name = interest.name.replaceAt(1, ParamsDigest.create(new Uint8Array(32)));
await expect(interest.validateParamsDigest()).rejects.toThrow(/incorrect/);
});

test("encode signed", async () => {
Expand Down

0 comments on commit 19eed49

Please sign in to comment.