Skip to content

Commit

Permalink
trust-schema: component pattern match correction
Browse files Browse the repository at this point in the history
A component pattern that appears twice must match the same component in
both places.
  • Loading branch information
yoursunny committed Mar 23, 2024
1 parent c833852 commit 23e7ee9
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 33 deletions.
34 changes: 25 additions & 9 deletions pkg/trust-schema/src/schema/pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,33 @@ class MatchState {
return this.tailLength === 0;
}

/**
* Clone the state while consuming part of the name.
* @param incrementPos - How many components are consumed.
* @returns Updated state.
*/
public extend(incrementPos: number): MatchState;

/**
* Clone the state while consuming part of the name.
* @param incrementPos - How many components are consumed.
* @param varsL - Updated variables.
* @returns Updated state, or `false` if variables are inconsistent.
*/
public extend(incrementPos: number, ...varsL: Array<Iterable<readonly [string, Name]>>): MatchState {
const { vars } = this;
return new MatchState(this.name, this.pos + incrementPos,
new Map<string, Name>((function*() {
yield* vars;
for (const vars of varsL) {
yield* vars;
public extend(incrementPos: number, ...varsL: Array<Iterable<readonly [string, Name]>>): MatchState | false;

public extend(incrementPos: number, ...varsL: Array<Iterable<readonly [string, Name]>>): any {
const result = new Map(this.vars);
for (const vars of varsL) {
for (const [k, rv] of vars) {
const lv = result.get(k);
if (lv?.equals(rv) === false) {
return false;
}
})()));
result.set(k, rv);
}
}
return new MatchState(this.name, this.pos + incrementPos, result);
}
}

Expand Down Expand Up @@ -218,7 +231,10 @@ export class VariablePattern extends Pattern {
const value = state.tail(i);
for (const innerVars of this.innerMatch(value)) {
if (this.filtersAccept(value, innerVars)) {
yield state.extend(i, innerVars, [[this.id, value]]);
const s = state.extend(i, innerVars, [[this.id, value]]);
if (s) {
yield s;
}
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions pkg/trust-schema/tests/schema/lvs.t.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Name, type NameLike } from "@ndn/packet";
import { expect, test } from "vitest";

import { TrustSchemaPolicy, versec } from "../..";

test("pattern", () => {
const policy = versec.load(`
#p0: /"ndn"/user/"KEY"/key_id
#ndn: "ndn"
#key: /"KEY"/key_id
#p1: #ndn/user/#key
#p2: /a/"b"/a/d
#p3: /a/"b"/a/d & {c: a}
`);

const expectMatches = (id: string, name: NameLike, matchCount: number) => {
const p = policy.getPattern(id);
expect(Array.from(p.match(Name.from(name)))).toHaveLength(matchCount);
};

expectMatches("#p0", "/ndn/xinyu/KEY/1", 1);
expectMatches("#p0", "/ndn/admin/KEY/65c66a2a", 1);
expectMatches("#p0", "/ndn/xinyu/key/1", 0);
expectMatches("#p0", "/ndn/xinyu/KEY/1/self/1", 0);

expectMatches("#p1", "/ndn/xinyu/KEY/1", 1);
expectMatches("#p1", "/ndn/admin/KEY/65c66a2a", 1);
expectMatches("#p1", "/ndn/xinyu/key/1", 0);
expectMatches("#p1", "/ndn/xinyu/KEY/1/self/1", 0);

expectMatches("#p2", "/x/b/x/ddd", 1);
expectMatches("#p2", "/x/b/y/ddd", 0);
expectMatches("#p3", "/x/b/x/ddd", 1);
expectMatches("#p3", "/x/b/y/ddd", 0);
});

test("example", () => {
const policy = versec.load(`
// taken from python-ndn LVS example
// Site prefix is "/a/blog"
#site: "a"/"blog"
// The trust anchor name is of pattern /a/blog/KEY/<key-id>/<issuer>/<cert-id>
#root: #site/#KEY
// Posts are signed by some author's key
#article: #site/"article"/category/year/month <= #author
// An author's key is signed by an admin's key
#author: #site/role/author/#KEY & { role: "author" } <= #admin
// An admin's key is signed by the root key
#admin: #site/"admin"/admin/#KEY <= #root
#KEY: "KEY"/_/_/_
`);
expect(versec.load(versec.print(policy))).toBeInstanceOf(TrustSchemaPolicy);

expect(policy.canSign(new Name("/a/blog/article/math/2022/03"), new Name("/a/blog/author/xinyu/KEY/1/admin/1"))).toBeTruthy();
expect(policy.canSign(new Name("/a/blog/author/xinyu/KEY/1/admin/1"), new Name("/a/blog/admin/admin/KEY/1/root/1"))).toBeTruthy();
expect(policy.canSign(new Name("/a/blog/author/xinyu/KEY/1/admin/1"), new Name("/a/blog/KEY/1/self/1"))).toBeFalsy();
});
24 changes: 0 additions & 24 deletions pkg/trust-schema/tests/schema/versec.t.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,27 +139,3 @@ test("compile", () => {
expect(() => versec.load("s: a/sysid(\"b\")")).toThrow(/sysid\(.*arguments/);
expect(() => versec.load("s: foo()")).toThrow(/unknown function/);
});

test("lvs", () => {
const policy = versec.load(`
// taken from python-ndn LVS example
// Site prefix is "/a/blog"
#site: "a"/"blog"
// The trust anchor name is of pattern /a/blog/KEY/<key-id>/<issuer>/<cert-id>
#root: #site/#KEY
// Posts are signed by some author's key
#article: #site/"article"/category/year/month <= #author
// An author's key is signed by an admin's key
#author: #site/role/author/#KEY & { role: "author" } <= #admin
// An admin's key is signed by the root key
#admin: #site/"admin"/admin/#KEY <= #root
#KEY: "KEY"/_/_/_
`);
expect(versec.load(versec.print(policy))).toBeInstanceOf(TrustSchemaPolicy);

expect(policy.canSign(new Name("/a/blog/article/math/2022/03"), new Name("/a/blog/author/xinyu/KEY/1/admin/1"))).toBeTruthy();
expect(policy.canSign(new Name("/a/blog/author/xinyu/KEY/1/admin/1"), new Name("/a/blog/admin/admin/KEY/1/root/1"))).toBeTruthy();
expect(policy.canSign(new Name("/a/blog/author/xinyu/KEY/1/admin/1"), new Name("/a/blog/KEY/1/self/1"))).toBeFalsy();
});

0 comments on commit 23e7ee9

Please sign in to comment.