Skip to content

Commit e5a053f

Browse files
committed
security: introduce new syntax in cross schema
1 parent 4ecd8b5 commit e5a053f

File tree

6 files changed

+468
-2
lines changed

6 files changed

+468
-2
lines changed

docs/cross-schema.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CrossSchema = CROSS-SCHEMA-TYPE TLV-LENGTH Data
1111
; Content of the Data in the CrossSchema field
1212
CrossSchemaContent = *SimpleSchemaRule
1313
*PrefixSchemaRule
14+
*ComponentSchemaRule
1415
1516
SimpleSchemaRule = SIMPLE-SCHEMA-RULE-TYPE TLV-LENGTH
1617
NamePrefix
@@ -19,11 +20,22 @@ SimpleSchemaRule = SIMPLE-SCHEMA-RULE-TYPE TLV-LENGTH
1920
PrefixSchemaRule = PREFIX-SCHEMA-RULE-TYPE TLV-LENGTH
2021
NamePrefix
2122
23+
ComponentSchemaRule = COMPONENT-SCHEMA-RULE-TYPE TLV-LENGTH
24+
NamePrefix
25+
KeyLocator
26+
NameComponentIndex
27+
KeyComponentIndex
28+
29+
; component indices are absolute (relative to the full Data/KeyLocator names)
30+
NameComponentIndex = NON-NEGATIVE-INTEGER
31+
KeyComponentIndex = NON-NEGATIVE-INTEGER
32+
2233
NamePrefix = Name
2334
2435
CROSS-SCHEMA-TYPE = 600
2536
SIMPLE-SCHEMA-RULE-TYPE = 620
2637
PREFIX-SCHEMA-RULE-TYPE = 622
38+
COMPONENT-SCHEMA-RULE-TYPE = 624
2739
```
2840

2941
## Usage of `CrossSchema`
@@ -129,3 +141,22 @@ During verification, the verifier:
129141

130142
1. Checks if `NamePrefix` is a prefix of the Data name. If yes, continue; otherwise, reject.
131143
1. Checks if the `KeyName` appears after `NamePrefix` in the Data name. If yes, accept; otherwise, reject.
144+
145+
### ComponentSchemaRule
146+
147+
A ComponentSchemaRule captures a component in the full Data name (not just the suffix after the prefix) and requires the same component to appear at a specific position in the KeyLocator name. This supports invitations where the producer identifier is not the entire suffix of the Data name.
148+
149+
For example:
150+
151+
```ini
152+
Content = ComponentSchemaRule {
153+
NamePrefix = /ucla.edu/wksp/collab
154+
KeyLocator = /arizona.edu
155+
NameComponentIndex = 3
156+
KeyComponentIndex = 1
157+
}
158+
```
159+
160+
1. The rule matches Data names that start with `/ucla.edu/wksp/collab/`, then captures the 4th component in the full name (index 3) as the producer identifier.
161+
1. The captured component must equal the second component (index 1) in the certificate name after `/arizona.edu`, e.g., `/arizona.edu/alice/KEY/...`.
162+
1. If either prefix check fails or the indexed components differ, the rule rejects the signer.

std/security/trust_config_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,53 @@ func testTrustConfigIntra(t *testing.T, schema ndn.TrustSchema) {
712712
crossSchema: apInvite,
713713
}))
714714

715+
// Component-based rule: capture producer id between prefix and key name
716+
acCollabInvite, err := trust_schema.SignCrossSchema(trust_schema.SignCrossSchemaArgs{
717+
Name: sname("/test/alice/32=INVITE/test/component/v=1"),
718+
Signer: aliceSigner,
719+
Content: trust_schema.CrossSchemaContent{
720+
ComponentSchemaRules: []*trust_schema.ComponentSchemaRule{{
721+
NamePrefix: sname("/test/alice/app/test"),
722+
KeyLocator: &spec.KeyLocator{Name: sname("/test")},
723+
NameComponentIndex: 4, // /test alice app test bob -> capture "bob"
724+
KeyComponentIndex: 1, // /test bob KEY...
725+
}},
726+
},
727+
NotBefore: time.Now(),
728+
NotAfter: time.Now().Add(time.Hour),
729+
})
730+
require.NoError(t, err)
731+
732+
// Bob cannot sign in alice namespace without the cross schema
733+
require.False(t, validateSync(ValidateSyncOptions{
734+
name: "/test/alice/app/test/bob/component1",
735+
signer: bobSigner,
736+
}))
737+
738+
// Bob and Cathy can publish if their id appears in the captured position
739+
require.True(t, validateSync(ValidateSyncOptions{
740+
name: "/test/alice/app/test/bob/component1",
741+
signer: bobSigner,
742+
crossSchema: acCollabInvite,
743+
}))
744+
require.True(t, validateSync(ValidateSyncOptions{
745+
name: "/test/alice/app/test/cathy/component2",
746+
signer: cathySigner,
747+
crossSchema: acCollabInvite,
748+
}))
749+
750+
// Mismatched captured component is rejected
751+
require.False(t, validateSync(ValidateSyncOptions{
752+
name: "/test/alice/app/test/cathy/component2",
753+
signer: bobSigner,
754+
crossSchema: acCollabInvite,
755+
}))
756+
require.False(t, validateSync(ValidateSyncOptions{
757+
name: "/test/alice/app/test",
758+
signer: cathySigner,
759+
crossSchema: acCollabInvite,
760+
}))
761+
715762
// Malicious cross schema created by bob for bob
716763
bobMCross, err := trust_schema.SignCrossSchema(trust_schema.SignCrossSchemaArgs{
717764
Name: sname("/test/alice/32=INVITE/test/bob/v=1"),

std/security/trust_schema/cross_schema.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,28 @@ func (cross *CrossSchemaContent) Match(dataName enc.Name, certName enc.Name) boo
103103
}
104104
}
105105

106+
for _, rule := range cross.ComponentSchemaRules {
107+
if rule == nil || rule.NamePrefix == nil || rule.KeyLocator == nil || rule.KeyLocator.Name == nil {
108+
continue
109+
}
110+
111+
if !rule.NamePrefix.IsPrefix(dataName) || !rule.KeyLocator.Name.IsPrefix(certName) {
112+
continue
113+
}
114+
115+
if uint64(len(dataName)) <= rule.NameComponentIndex {
116+
continue
117+
}
118+
if uint64(len(certName)) <= rule.KeyComponentIndex {
119+
continue
120+
}
121+
122+
dataComp := dataName[int(rule.NameComponentIndex)]
123+
certComp := certName[int(rule.KeyComponentIndex)]
124+
if dataComp.Equal(certComp) {
125+
return true
126+
}
127+
}
128+
106129
return false
107130
}

std/security/trust_schema/cross_schema_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,27 @@ func TestSignCrossSchema(t *testing.T) {
5555
require.Equal(t, T1.Unix(), nb.Unwrap().Unix())
5656
require.Equal(t, T2.Unix(), na.Unwrap().Unix())
5757
}
58+
59+
func TestCrossSchemaComponentRuleMatch(t *testing.T) {
60+
tu.SetT(t)
61+
62+
dataName := tu.NoErr(enc.NameFromStr("/app/invite/team/alice/data"))
63+
certName := tu.NoErr(enc.NameFromStr("/users/alice/KEY/kid/iss/ver"))
64+
65+
cross := trust_schema.CrossSchemaContent{
66+
ComponentSchemaRules: []*trust_schema.ComponentSchemaRule{{
67+
NamePrefix: tu.NoErr(enc.NameFromStr("/app/invite")),
68+
KeyLocator: &spec_2022.KeyLocator{Name: tu.NoErr(enc.NameFromStr("/users"))},
69+
NameComponentIndex: 3, // capture "alice" in full data name
70+
KeyComponentIndex: 1, // expect it immediately after "/users" in cert name
71+
}},
72+
}
73+
74+
require.True(t, cross.Match(dataName, certName))
75+
76+
mismatchCert := tu.NoErr(enc.NameFromStr("/users/bob/KEY/kid/iss/ver"))
77+
require.False(t, cross.Match(dataName, mismatchCert))
78+
79+
shortName := tu.NoErr(enc.NameFromStr("/app/invite/team"))
80+
require.False(t, cross.Match(shortName, certName))
81+
}

std/security/trust_schema/definitions_crossschema.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ type CrossSchemaContent struct {
1111
SimpleSchemaRules []*SimpleSchemaRule `tlv:"0x26C"`
1212
//+field:sequence:*PrefixSchemaRule:struct:PrefixSchemaRule
1313
PrefixSchemaRules []*PrefixSchemaRule `tlv:"0x26E"`
14+
//+field:sequence:*ComponentSchemaRule:struct:ComponentSchemaRule
15+
ComponentSchemaRules []*ComponentSchemaRule `tlv:"0x270"`
1416
}
1517

1618
type SimpleSchemaRule struct {
@@ -24,3 +26,14 @@ type PrefixSchemaRule struct {
2426
//+field:name
2527
NamePrefix enc.Name `tlv:"0x07"`
2628
}
29+
30+
type ComponentSchemaRule struct {
31+
//+field:name
32+
NamePrefix enc.Name `tlv:"0x07"`
33+
//+field:struct:spec_2022.KeyLocator
34+
KeyLocator *spec_2022.KeyLocator `tlv:"0x1c"`
35+
//+field:natural
36+
NameComponentIndex uint64 `tlv:"0x271"`
37+
//+field:natural
38+
KeyComponentIndex uint64 `tlv:"0x272"`
39+
}

0 commit comments

Comments
 (0)