From 81d90d8dec37b1ffccefe16a7b14069edac820d2 Mon Sep 17 00:00:00 2001 From: Victor Chinnappan Date: Tue, 24 Feb 2026 11:10:51 -0800 Subject: [PATCH 01/23] WIP First Iteration of alternate flow --- std/ndn/spec.go | 3 + std/ndn/spec_2022/spec.go | 12 +- std/security/certificate.go | 25 +++ std/security/certificate_test.go | 19 ++ std/security/trust_config.go | 312 +++++++++++++++++++++++++++++- std/security/trust_config_test.go | 239 +++++++++++++++++++++++ 6 files changed, 603 insertions(+), 7 deletions(-) diff --git a/std/ndn/spec.go b/std/ndn/spec.go index 59e32a46..1f6bffae 100644 --- a/std/ndn/spec.go +++ b/std/ndn/spec.go @@ -97,6 +97,9 @@ type DataConfig struct { SigNotBefore optional.Optional[time.Time] SigNotAfter optional.Optional[time.Time] + // Signature time, used to determine time the packet was signed + SigTime optional.Optional[time.Duration] + // Cross Schema attachment CrossSchema enc.Wire } diff --git a/std/ndn/spec_2022/spec.go b/std/ndn/spec_2022/spec.go index b245705f..58cc6b92 100644 --- a/std/ndn/spec_2022/spec.go +++ b/std/ndn/spec_2022/spec.go @@ -53,7 +53,11 @@ func (d *Data) SigNonce() []byte { // (AI GENERATED DESCRIPTION): Returns the signature timestamp of the Data packet (currently unimplemented and returns nil). func (d *Data) SigTime() *time.Time { - return nil + if d.SignatureInfo != nil && d.SignatureInfo.SignatureTime.IsSet() { + return utils.IdPtr(time.UnixMilli(d.SignatureInfo.SignatureTime.Unwrap().Milliseconds())) + } else { + return nil + } } // (AI GENERATED DESCRIPTION): Sets the Data packet’s SignatureInfo.SignatureTime to the given time (converted to a millisecond duration) or clears the field if the argument is nil. @@ -262,6 +266,11 @@ func (Spec) MakeData(name enc.Name, config *ndn.DataConfig, content enc.Wire, si if config == nil { return nil, ndn.ErrInvalidValue{Item: "Data.DataConfig", Value: nil} } + + if !config.SigTime.IsSet() { + config.SigTime = optional.Some(time.Duration(time.Now().UnixMilli()) * time.Millisecond) + } + finalBlock := []byte(nil) if fbid, ok := config.FinalBlockID.Get(); ok { finalBlock = fbid.Bytes() @@ -289,6 +298,7 @@ func (Spec) MakeData(name enc.Name, config *ndn.DataConfig, content enc.Wire, si data.SignatureInfo = &SignatureInfo{ SignatureType: uint64(signer.Type()), + SignatureTime: config.SigTime, } if key := signer.KeyLocator(); key != nil { diff --git a/std/security/certificate.go b/std/security/certificate.go index 99060019..8bebd1dc 100644 --- a/std/security/certificate.go +++ b/std/security/certificate.go @@ -65,6 +65,7 @@ func SignCert(args SignCertArgs) (enc.Wire, error) { Freshness: optional.Some(time.Hour), SigNotBefore: optional.Some(args.NotBefore), SigNotAfter: optional.Some(args.NotAfter), + SigTime: optional.Some(time.Duration(time.Now().UnixMilli()) * time.Millisecond), CrossSchema: args.CrossSchema, } signer := sig.AsContextSigner(args.Signer) @@ -199,3 +200,27 @@ func DecodeCertList(content enc.Wire) ([]enc.Name, error) { } return names, nil } + +// AppendCertList appends a certificate name list to a certificate list by creating a new certificate list +func AppendCertList(content enc.Wire, names []enc.Name) (enc.Wire, error) { + certList, err := DecodeCertList(content) + if err != nil { + return nil, err + } + + newList := append(certList, names...) + + return EncodeCertList(newList) +} + +// AppendCertList appends a certificate name list to a certificate list by creating a new certificate list +func FetchAndAppendCertList(content enc.Wire, names []enc.Name) (enc.Wire, error) { + certList, err := DecodeCertList(content) + if err != nil { + return nil, err + } + + newList := append(certList, names...) + + return EncodeCertList(newList) +} diff --git a/std/security/certificate_test.go b/std/security/certificate_test.go index f3419c3b..457e8833 100644 --- a/std/security/certificate_test.go +++ b/std/security/certificate_test.go @@ -191,3 +191,22 @@ func TestEncodeDecodeCertList(t *testing.T) { _, err = sec.DecodeCertList(enc.Wire{[]byte{0x01, 0x02}}) require.Error(t, err) } + +func TestApendCertList(t *testing.T) { + tu.SetT(t) + n1 := tu.NoErr(enc.NameFromStr("/ndn/alice/KEY/aa/self/v=1")) + n2 := tu.NoErr(enc.NameFromStr("/ndn/alice/KEY/bb/ndn/v=2")) + + wire, err := sec.EncodeCertList([]enc.Name{n1}) + require.NoError(t, err) + decoded, err := sec.DecodeCertList(wire) + require.NoError(t, err) + require.Equal(t, []enc.Name{n1}, decoded) + + wire2, err2 := sec.AppendCertList(wire, []enc.Name{n2}) + require.NoError(t, err2) + + decoded, err = sec.DecodeCertList(wire2) + require.NoError(t, err) + require.Equal(t, []enc.Name{n1, n2}, decoded) +} diff --git a/std/security/trust_config.go b/std/security/trust_config.go index b8e4ba9a..0f48ff4a 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -61,7 +61,8 @@ func NewTrustConfig(keyChain ndn.KeyChain, schema ndn.TrustSchema, roots []enc.N // Check if all roots are present in the keychain for _, root := range roots { if certBytes, _ := keyChain.Store().Get(root, false); len(certBytes) == 0 { - return nil, fmt.Errorf("trust anchor not found in keychain: %s", root) + continue // TODO: Make this more robust + // return nil, fmt.Errorf("trust anchor not found in keychain: %s", root) } else { certData, _, err := spec.Spec{}.ReadData(enc.NewBufferView(certBytes)) if err != nil { @@ -140,10 +141,13 @@ type TrustConfigValidateArgs struct { // depth is the maximum depth of the validation chain. depth int + + // Use alternate verification flow. + UseSignatureTime optional.Optional[bool] } // Validate validates a Data packet using a fetch API. -func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { +func (tc *TrustConfig) ValidateWithExpiry(args TrustConfigValidateArgs) { if args.Data == nil { args.Callback(false, fmt.Errorf("data is nil")) return @@ -220,7 +224,7 @@ func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { if valid && err == nil { // Continue validation with cross schema args.crossSchemaIsValid = true - tc.Validate(args) + tc.ValidateWithExpiry(args) } else { args.Callback(valid, fmt.Errorf("cross schema: %w", err)) } @@ -285,7 +289,7 @@ func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { } // Recursively validate the certificate - tc.Validate(TrustConfigValidateArgs{ + tc.ValidateWithExpiry(TrustConfigValidateArgs{ Data: args.cert, DataSigCov: args.certSigCov, @@ -328,7 +332,7 @@ func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { args.certIsValid = true // Continue validation with cached cert - tc.Validate(args) + tc.ValidateWithExpiry(args) return } @@ -386,11 +390,303 @@ func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { args.certIsValid = false // Continue validation with fetched cert - tc.Validate(args) + tc.ValidateWithExpiry(args) + } + args.Fetch(keyLocator, fetchCfg, cb) +} + +// TODO: See if we can create a helper function to handle the overlapping logic from both flows +// Validate validates a Data packet using a fetch API. +func (tc *TrustConfig) ValidateWithSignatureTime(args TrustConfigValidateArgs) { + if args.Data == nil { + args.Callback(false, fmt.Errorf("data is nil")) + return + } + + if len(args.DataSigCov) == 0 { + args.Callback(false, fmt.Errorf("data sig covered is nil")) + return + } + + if args.origDataName == nil { + // Always use original name here, not the override name + args.origDataName = args.Data.Name() + } + + // Prevent infinite recursion for signer loops + if args.depth == 0 { + args.depth = 32 + } else if args.depth <= 1 { + args.Callback(false, fmt.Errorf("max depth reached")) + return + } else { + args.depth-- + } + + // Make sure the data is signed + signature := args.Data.Signature() + if signature == nil { + args.Callback(false, fmt.Errorf("signature is nil")) + return + } + + // Get the key locator + keyLocator := signature.KeyName() + if len(keyLocator) == 0 { + args.Callback(false, fmt.Errorf("key locator is nil")) + return + } + + // Bail if the data is a cert and is not fresh + if t, ok := args.Data.ContentType().Get(); ok && t == ndn.ContentTypeKey { + if !args.IgnoreValidity.GetOr(false) && CertIsExpired(args.Data) { + fmt.Println("CERT IS EXPIRED: " + args.Data.Name().String()) + if keyLocator.IsPrefix(args.Data.Name()) || tc.isTrustedAnchorKey(keyLocator) { + fmt.Println("EXPLORE CERT LIST: " + keyLocator.String() + " " + args.Data.Name().String()) + anchorKeyName, err := KeyNameFromLocator(keyLocator) + if err != nil { + args.Callback(false, fmt.Errorf("invalid anchor key locator: %w", err)) + return + } + + tc.exploreCertList(certListArgs{ + args: args, + anchorCert: args.Data, + anchorRaw: args.certRaw, + anchorKey: anchorKeyName, + visitedLists: map[string]struct{}{}, + visitedCerts: map[string]struct{}{}, + }, anchorKeyName.Append(enc.NewKeywordComponent("auth"))) + + return + } + } + } + + // If a certificate is provided, go directly to validation + if args.cert != nil { + certName := args.cert.Name() + dataName := args.Data.Name() + if len(args.OverrideName) > 0 { + dataName = args.OverrideName + } + + // Disallow empty names + if len(dataName) == 0 { + args.Callback(false, fmt.Errorf("data name is empty")) + return + } + + // Verify that the data was signed within certificate validity period + sigTime := args.Data.Signature().SigTime() + + if sigTime != nil { + + if args.cert.Signature() == nil { + args.Callback(false, fmt.Errorf("data was signed during an invalid period of certificate [no signature]: %s", args.cert.Name())) + return + } + + cert_notBefore, cert_notAfter := args.cert.Signature().Validity() + if val, ok := cert_notBefore.Get(); !ok || sigTime.Before(val) { + args.Callback(false, fmt.Errorf("data was signed during an invalid period of certificate [signed before]: %s", args.cert.Name())) + return + } + if val, ok := cert_notAfter.Get(); !ok || sigTime.After(val) { + args.Callback(false, fmt.Errorf("data was signed during an invalid period of certificate [signed after]: %s", args.cert.Name())) + + return + } + } + + // Check schema if the key is allowed + if args.crossSchemaIsValid { + // continue + } else if tc.schema.Check(dataName, certName) { + // continue + } else if args.Data.CrossSchema() != nil { + tc.validateCrossSchema(TrustConfigValidateArgs{ + Data: args.Data, + DataSigCov: args.DataSigCov, + + Fetch: args.Fetch, + Callback: func(valid bool, err error) { + if valid && err == nil { + // Continue validation with cross schema + args.crossSchemaIsValid = true + tc.ValidateWithSignatureTime(args) + } else { + args.Callback(valid, fmt.Errorf("cross schema: %w", err)) + } + }, + OverrideName: args.OverrideName, + IgnoreValidity: args.IgnoreValidity, + cert: args.cert, + depth: args.depth, + }) + return + } else { + args.Callback(false, fmt.Errorf("trust schema mismatch: %s signed by %s", dataName, certName)) + return + } + + // Validate signature on data + valid, err := signer.ValidateData(args.Data, args.DataSigCov, args.cert) + if !valid { + args.Callback(false, fmt.Errorf("signature is invalid")) + return + } + if err != nil { + args.Callback(false, fmt.Errorf("signature validate error: %w", err)) + return + } + + // Check if the certificate was already validated. + // Since all roots are in cache, this breaks the recursion. + if args.certIsValid { + args.Callback(true, nil) + return + } + + // This should never happen, but just in case + if len(args.certSigCov) == 0 { + args.Callback(false, fmt.Errorf("cert sig covered is nil: %s", certName)) + return + } + + // Monkey patch the callback to store the cert in + // keychain and cache if the validation passes. + origCallback := args.Callback + args.Callback = func(valid bool, err error) { + if valid && err == nil { + // Cache is thread safe + tc.certCache.Put(args.cert) + + // Keychain is not thread safe for inserts + if len(args.certRaw) > 0 { + tc.mutex.Lock() + err := tc.keychain.InsertCert(args.certRaw.Join()) + tc.mutex.Unlock() + if err != nil { // broken keychain + log.Error(tc, "Failed to insert certificate to keychain", "name", args.cert.Name(), "err", err) + } + } + } else { + log.Warn(tc, "Received invalid certificate", "name", args.cert.Name(), "err", err) + } + + origCallback(valid, err) // continue bubbling up result + } + + // Recursively validate the certificate + tc.ValidateWithSignatureTime(TrustConfigValidateArgs{ + Data: args.cert, + DataSigCov: args.certSigCov, + + Fetch: args.Fetch, + Callback: args.Callback, + OverrideName: nil, + IgnoreValidity: args.IgnoreValidity, + origDataName: args.origDataName, + + cert: nil, + certSigCov: nil, + certRaw: nil, + certIsValid: false, + + crossSchemaIsValid: false, + + depth: args.depth, + }) + return + } + + // Handle self-signed certificate (potential trust anchor). + if keyLocator.IsPrefix(args.Data.Name()) { + tc.handleSelfSignedCert(args, keyLocator) + return + } + + // Reset all cert fields, this is just for extra safety + // The code below might seem to have a lot of redundancy - this is intentional. + args.cert = nil + args.certSigCov = nil + args.certRaw = nil + args.certIsValid = false + args.crossSchemaIsValid = false + + // Check the validated memcache for the certificate + if cachedCert, ok := tc.certCache.Get(keyLocator); ok { + // The cache always checks the expiry of the cert + args.cert = cachedCert + args.certIsValid = true + + // Continue validation with cached cert + tc.ValidateWithSignatureTime(args) + return + } + + // Attach forwarding hint if needed + var fwHint []enc.Name = nil + if args.UseDataNameFwHint.GetOr(tc.UseDataNameFwHint) { + fwHint = []enc.Name{args.origDataName} + } + + // Cert not found, attempt to fetch from network + fetchCfg := &ndn.InterestConfig{ + CanBePrefix: true, + MustBeFresh: true, + ForwardingHint: fwHint, + } + triedLocal := false + var cb ndn.ExpressCallbackFunc + cb = func(res ndn.ExpressCallbackArgs) { + if res.Error == nil && res.Result != ndn.InterestResultData { + res.Error = fmt.Errorf("failed to fetch certificate (%s) with result: %s", keyLocator, res.Result) + } + + if res.Error != nil { + args.Callback(false, res.Error) + return // failed to fetch cert + } + + // Bail if not a certificate + if t, ok := res.Data.ContentType().Get(); !ok || t != ndn.ContentTypeKey { + if res.IsLocal && !triedLocal { + triedLocal = true + if res.Data != nil { + _ = tc.keychain.Store().Remove(res.Data.Name()) + } + args.Fetch(keyLocator, fetchCfg, cb) + return + } + args.Callback(false, fmt.Errorf("non-certificate in chain: %s", res.Data.Name())) + return + } + + // Fetched cert is fresh + log.Debug(tc, "Fetched certificate from network", "cert", res.Data.Name()) + + // Call again with the fetched cert + args.cert = res.Data + args.certSigCov = res.SigCovered + args.certRaw = utils.If(!res.IsLocal, res.RawData, nil) // prevent double insert + args.certIsValid = false + + // Continue validation with fetched cert + tc.ValidateWithSignatureTime(args) } args.Fetch(keyLocator, fetchCfg, cb) } +func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { + if args.UseSignatureTime.GetOr(false) { + tc.ValidateWithSignatureTime(args) + } else { + tc.ValidateWithExpiry(args) + } +} + // (AI GENERATED DESCRIPTION): Validates the cross‑schema signed Data packet by parsing its embedded schema, checking its validity period, ensuring it authorizes the original certificate, and recursively validating the cross‑schema’s signature against the trust configuration. func (tc *TrustConfig) validateCrossSchema(args TrustConfigValidateArgs) { crossWire := args.Data.CrossSchema() @@ -440,6 +736,8 @@ func (tc *TrustConfig) validateCrossSchema(args TrustConfigValidateArgs) { IgnoreValidity: args.IgnoreValidity, depth: args.depth, + + UseSignatureTime: args.UseSignatureTime, }) } @@ -669,6 +967,8 @@ func (tc *TrustConfig) tryListedCerts(args certListArgs, names []enc.Name, idx i IgnoreValidity: args.args.IgnoreValidity, origDataName: args.args.origDataName, depth: args.args.depth, + + UseSignatureTime: args.args.UseSignatureTime, }) }) } diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index 8ca26328..11840484 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -1044,3 +1044,242 @@ func TestTrustConfigLvsInter(t *testing.T) { testTrustConfigInter(t, schemaInter) } + +func TestExpiredRootWithCertList(t *testing.T) { + tu.SetT(t) + + // TODO: TEST CASE EXPECTED TO FAIL! Adjust testcase such that signing happens during valid period. + + store := storage.NewMemoryStore() + tcTestKeyChain = keychain.NewKeyChainMem(store) + // Use intra-domain schema with root-to-root signing support + // This schema is the same as TRUST_CONFIG_INTRA_LVS but allows roots to sign each other + schema, err := trust_schema.NewLvsSchema(TRUST_CONFIG_INTER_LVS) + require.NoError(t, err) + + clear(tcTestNetwork) + tcTestT = t + network := tcTestNetwork + keychain2 := tcTestKeyChain + + now := time.Now() + nb := now + na := now.Add(time.Hour) + n := func(s string) enc.Name { return tu.NoErr(enc.NameFromStr(s)) } + + // Expired root (old root) + expiredRootSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/root")))) + expiredRootKeyData := tu.NoErr(signer.MarshalSecretToData(expiredRootSigner)) + expiredRootCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: expiredRootSigner, + Data: expiredRootKeyData, + IssuerId: enc.NewGenericComponent("self"), + NotBefore: now.Add(-3 * time.Hour), // Expired 3 hours ago + NotAfter: now.Add(-1 * time.Hour), // Expired 1 hour ago + })) + expiredRootCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(expiredRootCertWire)) + network[expiredRootCertData.Name().String()] = expiredRootCertWire + keychain2.InsertCert(expiredRootCertWire.Join()) + + // New root (replacement root) + newRootSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/root")))) + newRootKeyData := tu.NoErr(signer.MarshalSecretToData(newRootSigner)) + newRootCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: newRootSigner, + Data: newRootKeyData, + IssuerId: enc.NewGenericComponent("self"), + NotBefore: nb, + NotAfter: na, + })) + newRootCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(newRootCertWire)) + network[newRootCertData.Name().String()] = newRootCertWire + keychain2.InsertCert(newRootCertWire.Join()) + + // PreAnchor: replacement certificate for expired root's key, signed by new root + // This certificate verifies/authorizes the expired root's key + preAnchorWire2 := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: newRootSigner, // Signed by new root + Data: expiredRootKeyData, // Certificate for expired root's key + IssuerId: enc.NewGenericComponent("root"), + NotBefore: nb, + NotAfter: na, + })) + preAnchorData2, _, _ := spec.Spec{}.ReadData(enc.NewWireView(preAnchorWire2)) + // network[preAnchorData2.Name().String()] = preAnchorWire2 + // keychain2.InsertCert(preAnchorWire2.Join()) + + fmt.Println("EXPIRED ROOT KEY: " + expiredRootKeyData.Name().String()) + fmt.Println("EXPIRED ROOT CERT: " + expiredRootCertData.Name().String()) + fmt.Println("NEW ROOT CERT: " + newRootCertData.Name().String()) + fmt.Println("PRE ANCHOR DATA 2: " + preAnchorData2.Name().String()) + + // CertList for expired root pointing to preAnchor + // This CertList is signed by the new root itself (even though expired) + listContent2 := tu.NoErr(sec.EncodeCertList([]enc.Name{preAnchorData2.Name()})) + listPrefix2 := tu.NoErr(sec.CertListPrefix(expiredRootSigner.KeyName())) + listName2 := listPrefix2.Append(enc.NewVersionComponent(uint64(now.UnixMicro()))) + listWireEnc2 := tu.NoErr(spec.Spec{}.MakeData(listName2, &ndn.DataConfig{ + Freshness: optional.Some(time.Minute), + SigNotBefore: optional.Some(nb), + SigNotAfter: optional.Some(na), + }, listContent2, expiredRootSigner)) // Signed by expired root + listData2, _, _ := spec.Spec{}.ReadData(enc.NewWireView(listWireEnc2.Wire)) + // network[listData2.Name().String()] = listWireEnc2.Wire + fmt.Println("LIST DATA 2: " + listData2.Name().String()) + + // Owner <= testbed + ownerSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/root/owner")))) + ownerKeyData := tu.NoErr(signer.MarshalSecretToData(ownerSigner)) + ownerCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: expiredRootSigner, + Data: ownerKeyData, + IssuerId: enc.NewGenericComponent("root"), + NotBefore: nb, + NotAfter: na, + })) + ownerCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(ownerCertWire)) + + // Workspace anchor (cert) + anchorSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/app")))) + anchorKeyData := tu.NoErr(signer.MarshalSecretToData(anchorSigner)) + anchorCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: anchorSigner, + Data: anchorKeyData, + IssuerId: enc.NewGenericComponent("self"), + NotBefore: nb, + NotAfter: na, + })) + anchorCertData, anchorSigCov, _ := spec.Spec{}.ReadData(enc.NewWireView(anchorCertWire)) + + // Workspace preanchor (precert) + preAnchorWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: ownerSigner, + Data: anchorKeyData, + IssuerId: enc.NewGenericComponent("owner"), + NotBefore: nb, + NotAfter: na, + })) + preAnchorData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(preAnchorWire)) + + listContent := tu.NoErr(sec.EncodeCertList([]enc.Name{preAnchorData.Name()})) + listPrefix := tu.NoErr(sec.CertListPrefix(anchorSigner.KeyName())) + listName := listPrefix.Append(enc.NewVersionComponent(uint64(time.Now().UnixMicro()))) + listWireEnc := tu.NoErr(spec.Spec{}.MakeData(listName, &ndn.DataConfig{ + Freshness: optional.Some(time.Minute), + SigNotBefore: optional.Some(nb), + SigNotAfter: optional.Some(na), + }, listContent, anchorSigner)) + listData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(listWireEnc.Wire)) + + userSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/app/user/alice")))) + userKeyData := tu.NoErr(signer.MarshalSecretToData(userSigner)) + userCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: anchorSigner, + Data: userKeyData, + IssuerId: enc.NewGenericComponent("app"), + NotBefore: nb, + NotAfter: na, + })) + userCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(userCertWire)) + + payload := enc.Wire{[]byte{0x01}} + dataWire := tu.NoErr(spec.Spec{}.MakeData(n("/app/user/alice/data"), &ndn.DataConfig{ + Freshness: optional.Some(time.Minute), + }, payload, userSigner)) + dataPkt, dataSigCov, _ := spec.Spec{}.ReadData(enc.NewWireView(dataWire.Wire)) + + require.True(t, schema.Check(anchorCertData.Name(), anchorCertData.Name())) + require.True(t, schema.Check(preAnchorData.Name(), ownerCertData.Name())) + require.True(t, schema.Check(listData.Name(), anchorCertData.Name())) + require.True(t, schema.Check(userCertData.Name(), anchorCertData.Name())) + require.True(t, schema.Check(dataPkt.Name(), userCertData.Name())) + + network[anchorCertData.Name().String()] = anchorCertWire + network[userCertData.Name().String()] = userCertWire + network[ownerCertData.Name().String()] = ownerCertWire + + type stage struct { + name string + add map[string]enc.Wire + expectAnchor bool + expectData bool + expectErrPart string + } + + stages := []stage{ + { + name: "certlist ok", + add: map[string]enc.Wire{ + listData2.Name().String(): listWireEnc2.Wire, + preAnchorData2.Name().String(): preAnchorWire2, + listData.Name().String(): listWireEnc.Wire, + preAnchorData.Name().String(): preAnchorWire, + }, + expectAnchor: true, + expectData: true, + }, + } + + validateOnce := func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) (bool, error) { + tcTestFetchCount = 0 + done := make(chan struct { + v bool + err error + }, 1) + trust.Validate(sec.TrustConfigValidateArgs{ + Data: data, + DataSigCov: sigCov, + Fetch: fetchFun, + Callback: func(valid bool, err error) { + done <- struct { + v bool + err error + }{v: valid, err: err} + }, + + UseSignatureTime: optional.Some(true), + }) + res := <-done + return res.v, res.err + } + + for _, st := range stages { + t.Run(st.name, func(t *testing.T) { + store := storage.NewMemoryStore() + tcTestKeyChain = keychain.NewKeyChainMem(store) + kc := tcTestKeyChain + + for k, v := range st.add { + network[k] = v + } + require.NoError(t, kc.InsertCert(newRootCertWire.Join())) + trust, err := sec.NewTrustConfig(kc, schema, []enc.Name{newRootCertData.Name()}) + require.NoError(t, err) + + // Validate anchor cert first, then user data. + anchorValid, anchorErr := validateOnce(trust, anchorCertData, anchorSigCov) + dataValid, dataErr := validateOnce(trust, dataPkt, dataSigCov) + if st.expectData { + require.True(t, dataValid) + require.NoError(t, dataErr) + } else { + require.False(t, dataValid) + require.Error(t, dataErr) + if st.expectErrPart != "" { + require.Contains(t, dataErr.Error(), st.expectErrPart) + } + } + + if st.expectAnchor { + require.True(t, anchorValid) + require.NoError(t, anchorErr) + } else { + require.False(t, anchorValid) + require.Error(t, anchorErr) + if st.expectErrPart != "" { + require.Contains(t, anchorErr.Error(), st.expectErrPart) + } + } + }) + } +} From 038460e20e8ca1ae4e0be94ebe406fada8fcd458 Mon Sep 17 00:00:00 2001 From: Victor Chinnappan Date: Mon, 9 Mar 2026 03:19:24 -0700 Subject: [PATCH 02/23] Adjusted existing test cases and tested for both valid scenarios for new flows --- std/engine/basic/engine_test.go | 17 +++++-- std/ndn/spec_2022/spec_test.go | 44 ++++++++++------- std/security/trust_config.go | 4 +- std/security/trust_config_test.go | 82 +++++++++++++++++++------------ 4 files changed, 92 insertions(+), 55 deletions(-) diff --git a/std/engine/basic/engine_test.go b/std/engine/basic/engine_test.go index 169bc10f..42454d12 100644 --- a/std/engine/basic/engine_test.go +++ b/std/engine/basic/engine_test.go @@ -268,6 +268,9 @@ func TestRoute(t *testing.T) { hitCnt := 0 spec := engine.Spec() + // Fixed SigTime for deterministic test vectors: 2024-01-01 00:00:00 UTC + fixedSigTime := optional.Some(time.Duration(1704067200000) * time.Millisecond) + handler := func(args ndn.InterestHandlerArgs) { hitCnt += 1 require.Equal(t, []byte( @@ -278,6 +281,7 @@ func TestRoute(t *testing.T) { args.Interest.Name(), &ndn.DataConfig{ ContentType: optional.Some(ndn.ContentTypeBlob), + SigTime: fixedSigTime, }, enc.Wire{[]byte("test")}, sig.NewTestSigner(enc.Name{}, 0)) @@ -291,8 +295,8 @@ func TestRoute(t *testing.T) { require.Equal(t, 1, hitCnt) buf := tu.NoErr(face.Consume()) require.Equal(t, enc.Buffer( - "\x06\x22\x07\x10\x08\x03not\x08\timportant\x14\x03\x18\x01\x00\x15\x04test"+ - "\x16\x03\x1b\x01\xc8", + "\x06\x2c\x07\x10\x08\x03not\x08\timportant\x14\x03\x18\x01\x00\x15\x04test"+ + "\x16\x0d\x1b\x01\xc8(\x08\x00\x00\x01\x8c\xc2Q\xf4\x00", ), buf) }) } @@ -302,6 +306,8 @@ func TestPitToken(t *testing.T) { executeTest(t, func(face *face.DummyFace, engine *basic_engine.Engine, timer *basic_engine.DummyTimer) { hitCnt := 0 spec := engine.Spec() + // Fixed SigTime for deterministic test vectors: 2024-01-01 00:00:00 UTC + fixedSigTime := optional.Some(time.Duration(1704067200000) * time.Millisecond) handler := func(args ndn.InterestHandlerArgs) { hitCnt += 1 @@ -309,6 +315,7 @@ func TestPitToken(t *testing.T) { args.Interest.Name(), &ndn.DataConfig{ ContentType: optional.Some(ndn.ContentTypeBlob), + SigTime: fixedSigTime, }, enc.Wire{[]byte("test")}, sig.NewTestSigner(enc.Name{}, 0)) @@ -324,9 +331,9 @@ func TestPitToken(t *testing.T) { )) buf := tu.NoErr(face.Consume()) require.Equal(t, enc.Buffer( - "\x64\x2c\x62\x04\x01\x02\x03\x04\x50\x24"+ - "\x06\x22\x07\x10\x08\x03not\x08\timportant\x14\x03\x18\x01\x00\x15\x04test"+ - "\x16\x03\x1b\x01\xc8", + "\x64\x36\x62\x04\x01\x02\x03\x04\x50\x2e"+ + "\x06\x2c\x07\x10\x08\x03not\x08\timportant\x14\x03\x18\x01\x00\x15\x04test"+ + "\x16\x0d\x1b\x01\xc8(\x08\x00\x00\x01\x8c\xc2Q\xf4\x00", ), buf) }) } diff --git a/std/ndn/spec_2022/spec_test.go b/std/ndn/spec_2022/spec_test.go index e8cd9950..a8f36a5d 100644 --- a/std/ndn/spec_2022/spec_test.go +++ b/std/ndn/spec_2022/spec_test.go @@ -21,40 +21,42 @@ func TestMakeDataBasic(t *testing.T) { tu.SetT(t) spec := spec_2022.Spec{} + // Fixed SigTime for deterministic test vectors: 2024-01-01 00:00:00 UTC + fixedSigTime := optional.Some(time.Duration(1704067200000) * time.Millisecond) data, err := spec.MakeData( tu.NoErr(enc.NameFromStr("/local/ndn/prefix")), &ndn.DataConfig{ ContentType: optional.Some(ndn.ContentTypeBlob), + SigTime: fixedSigTime, }, nil, sig.NewSha256Signer(), ) require.NoError(t, err) require.Equal(t, []byte( - "\x06\x42\x07\x14\x08\x05local\x08\x03ndn\x08\x06prefix"+ + "\x06L\x07\x14\x08\x05local\x08\x03ndn\x08\x06prefix"+ "\x14\x03\x18\x01\x00"+ - "\x16\x03\x1b\x01\x00"+ - "\x17 \x7f1\xe4\t\xc5z/\x1d\r\xdaVh8\xfd\xd9\x94"+ - "\xd8'S\x13[\xd7\x15\xa5\x9d%^\x80\xf2\xab\xf0\xb5"), + "\x16\x0d\x1b\x01\x00(\x08\x00\x00\x01\x8c\xc2Q\xf4\x00"+ + "\x17 *\x17?\xd6i\xdcB\xd0\xf6G\x1d\x0b\xb6\x93\xd4{\xddw\xc6$\x04\x00\x0eN\xb7\x07\x5c\x80K+O\x86"), data.Wire.Join()) data, err = spec.MakeData( tu.NoErr(enc.NameFromStr("/local/ndn/prefix")), &ndn.DataConfig{ ContentType: optional.Some(ndn.ContentTypeBlob), + SigTime: fixedSigTime, }, enc.Wire{[]byte("01020304")}, sig.NewSha256Signer(), ) require.NoError(t, err) require.Equal(t, []byte( - "\x06L\x07\x14\x08\x05local\x08\x03ndn\x08\x06prefix"+ + "\x06V\x07\x14\x08\x05local\x08\x03ndn\x08\x06prefix"+ "\x14\x03\x18\x01\x00"+ "\x15\x0801020304"+ - "\x16\x03\x1b\x01\x00"+ - "\x17 \x94\xe9\xda\x91\x1a\x11\xfft\x02i:G\x0cO\xdd!"+ - "\xe0\xc7\xb6\xfd\x8f\x9cn\xc5\x93{\x93\x04\xe0\xdf\xa6S"), + "\x16\x0d\x1b\x01\x00(\x08\x00\x00\x01\x8c\xc2Q\xf4\x00"+ + "\x17 \xaf\x02\x9d\xf3\x9a\x05s\x83\xefv\x05\xef\x81=\xdb.\xc72$v\x13\xb3\xae\x80\x83+\xe8W\xfeP\x1f}"), data.Wire.Join()) data, err = spec.MakeData( @@ -75,15 +77,16 @@ func TestMakeDataBasic(t *testing.T) { tu.NoErr(enc.NameFromStr("/E")), &ndn.DataConfig{ ContentType: optional.None[ndn.ContentType](), + SigTime: fixedSigTime, }, enc.Wire{}, sig.NewSha256Signer(), ) require.NoError(t, err) require.Equal(t, tu.NoErr(hex.DecodeString( - "06300703080145"+ - "1400150016031b0100"+ - "1720f965ee682c6973c3cbaa7b69e4c7063680f83be93a46be2ccc98686134354b66")), + "063a070308014514001500"+ + "160d1b010028080000018cc251f400"+ + "1720f7b2d57151cb8d6fe2c616adee9195c9d00f3c422754709f750375bae2e91e30")), data.Wire.Join()) } @@ -91,6 +94,8 @@ func TestMakeDataBasic(t *testing.T) { func TestMakeDataMetaInfo(t *testing.T) { tu.SetT(t) spec := spec_2022.Spec{} + // Fixed SigTime for deterministic test vectors: 2024-01-01 00:00:00 UTC + fixedSigTime := optional.Some(time.Duration(1704067200000) * time.Millisecond) data, err := spec.MakeData( tu.NoErr(enc.NameFromStr("/local/ndn/prefix/37=%00")), @@ -98,16 +103,17 @@ func TestMakeDataMetaInfo(t *testing.T) { ContentType: optional.Some(ndn.ContentTypeBlob), Freshness: optional.Some(1000 * time.Millisecond), FinalBlockID: optional.Some(enc.NewSequenceNumComponent(2)), + SigTime: fixedSigTime, }, nil, sig.NewSha256Signer(), ) require.NoError(t, err) require.Equal(t, []byte( - "\x06\x4e\x07\x17\x08\x05local\x08\x03ndn\x08\x06prefix\x25\x01\x00"+ - "\x14\x0c\x18\x01\x00\x19\x02\x03\xe8\x1a\x03\x3a\x01\x02"+ - "\x16\x03\x1b\x01\x00"+ - "\x17 \x0f^\xa1\x0c\xa7\xf5Fb\xf0\x9cOT\xe0FeC\x8f92\x04\x9d\xabP\x80o'\x94\xaa={hQ"), + "\x06X\x07\x17\x08\x05local\x08\x03ndn\x08\x06prefix\x25\x01\x00"+ + "\x14\x0c\x18\x01\x00\x19\x02\x03\xe8\x1a\x03:\x01\x02"+ + "\x16\x0d\x1b\x01\x00(\x08\x00\x00\x01\x8c\xc2Q\xf4\x00"+ + "\x17 \xa9v\x8a(\xb3^\xf8\x1c\xce*\xa8\xf2\x14\x81\x8cQ\xcc\xb8I\xc6\xd6\xa7\x99z\x88JKu\x81\xc4[N"), data.Wire.Join()) } @@ -148,19 +154,23 @@ func (testSigner) Public() ([]byte, error) { func TestMakeDataShrink(t *testing.T) { tu.SetT(t) spec := spec_2022.Spec{} + // Fixed SigTime for deterministic test vectors: 2024-01-01 00:00:00 UTC + fixedSigTime := optional.Some(time.Duration(1704067200000) * time.Millisecond) data, err := spec.MakeData( tu.NoErr(enc.NameFromStr("/test")), &ndn.DataConfig{ ContentType: optional.Some(ndn.ContentTypeBlob), + SigTime: fixedSigTime, }, nil, testSigner{}, ) require.NoError(t, err) require.Equal(t, []byte{ - 0x6, 0x22, 0x7, 0x6, 0x8, 0x4, 0x74, 0x65, 0x73, 0x74, 0x14, 0x3, 0x18, 0x1, 0x0, - 0x16, 0xc, 0x1b, 0x1, 0xc8, 0x1c, 0x7, 0x7, 0x5, 0x8, 0x3, 0x4b, 0x45, 0x59, + 0x6, 0x2c, 0x7, 0x6, 0x8, 0x4, 0x74, 0x65, 0x73, 0x74, 0x14, 0x3, 0x18, 0x1, 0x0, + 0x16, 0x16, 0x1b, 0x1, 0xc8, 0x1c, 0x7, 0x7, 0x5, 0x8, 0x3, 0x4b, 0x45, 0x59, + 0x28, 0x8, 0x0, 0x0, 0x1, 0x8c, 0xc2, 0x51, 0xf4, 0x0, 0x17, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0}, data.Wire.Join()) } diff --git a/std/security/trust_config.go b/std/security/trust_config.go index 0f48ff4a..b09d8c5f 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -440,9 +440,9 @@ func (tc *TrustConfig) ValidateWithSignatureTime(args TrustConfigValidateArgs) { // Bail if the data is a cert and is not fresh if t, ok := args.Data.ContentType().Get(); ok && t == ndn.ContentTypeKey { if !args.IgnoreValidity.GetOr(false) && CertIsExpired(args.Data) { - fmt.Println("CERT IS EXPIRED: " + args.Data.Name().String()) + // fmt.Println("CERT IS EXPIRED: " + args.Data.Name().String()) if keyLocator.IsPrefix(args.Data.Name()) || tc.isTrustedAnchorKey(keyLocator) { - fmt.Println("EXPLORE CERT LIST: " + keyLocator.String() + " " + args.Data.Name().String()) + fmt.Println("EXPLORE CERT LIST: " + args.Data.Name().String()) anchorKeyName, err := KeyNameFromLocator(keyLocator) if err != nil { args.Callback(false, fmt.Errorf("invalid anchor key locator: %w", err)) diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index 11840484..8cea2f12 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -1048,8 +1048,6 @@ func TestTrustConfigLvsInter(t *testing.T) { func TestExpiredRootWithCertList(t *testing.T) { tu.SetT(t) - // TODO: TEST CASE EXPECTED TO FAIL! Adjust testcase such that signing happens during valid period. - store := storage.NewMemoryStore() tcTestKeyChain = keychain.NewKeyChainMem(store) // Use intra-domain schema with root-to-root signing support @@ -1064,7 +1062,7 @@ func TestExpiredRootWithCertList(t *testing.T) { now := time.Now() nb := now - na := now.Add(time.Hour) + na := now.Add(time.Second * 5) n := func(s string) enc.Name { return tu.NoErr(enc.NameFromStr(s)) } // Expired root (old root) @@ -1074,8 +1072,8 @@ func TestExpiredRootWithCertList(t *testing.T) { Signer: expiredRootSigner, Data: expiredRootKeyData, IssuerId: enc.NewGenericComponent("self"), - NotBefore: now.Add(-3 * time.Hour), // Expired 3 hours ago - NotAfter: now.Add(-1 * time.Hour), // Expired 1 hour ago + NotBefore: now, // Expired 3 hours ago + NotAfter: now.Add(time.Second * 7), // Expired 1 hour ago })) expiredRootCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(expiredRootCertWire)) network[expiredRootCertData.Name().String()] = expiredRootCertWire @@ -1089,7 +1087,7 @@ func TestExpiredRootWithCertList(t *testing.T) { Data: newRootKeyData, IssuerId: enc.NewGenericComponent("self"), NotBefore: nb, - NotAfter: na, + NotAfter: now.Add(time.Second * 17), })) newRootCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(newRootCertWire)) network[newRootCertData.Name().String()] = newRootCertWire @@ -1102,16 +1100,9 @@ func TestExpiredRootWithCertList(t *testing.T) { Data: expiredRootKeyData, // Certificate for expired root's key IssuerId: enc.NewGenericComponent("root"), NotBefore: nb, - NotAfter: na, + NotAfter: now.Add(time.Second * 15), })) preAnchorData2, _, _ := spec.Spec{}.ReadData(enc.NewWireView(preAnchorWire2)) - // network[preAnchorData2.Name().String()] = preAnchorWire2 - // keychain2.InsertCert(preAnchorWire2.Join()) - - fmt.Println("EXPIRED ROOT KEY: " + expiredRootKeyData.Name().String()) - fmt.Println("EXPIRED ROOT CERT: " + expiredRootCertData.Name().String()) - fmt.Println("NEW ROOT CERT: " + newRootCertData.Name().String()) - fmt.Println("PRE ANCHOR DATA 2: " + preAnchorData2.Name().String()) // CertList for expired root pointing to preAnchor // This CertList is signed by the new root itself (even though expired) @@ -1119,13 +1110,11 @@ func TestExpiredRootWithCertList(t *testing.T) { listPrefix2 := tu.NoErr(sec.CertListPrefix(expiredRootSigner.KeyName())) listName2 := listPrefix2.Append(enc.NewVersionComponent(uint64(now.UnixMicro()))) listWireEnc2 := tu.NoErr(spec.Spec{}.MakeData(listName2, &ndn.DataConfig{ - Freshness: optional.Some(time.Minute), + Freshness: optional.Some(time.Second * 19), SigNotBefore: optional.Some(nb), SigNotAfter: optional.Some(na), }, listContent2, expiredRootSigner)) // Signed by expired root listData2, _, _ := spec.Spec{}.ReadData(enc.NewWireView(listWireEnc2.Wire)) - // network[listData2.Name().String()] = listWireEnc2.Wire - fmt.Println("LIST DATA 2: " + listData2.Name().String()) // Owner <= testbed ownerSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/root/owner")))) @@ -1199,6 +1188,8 @@ func TestExpiredRootWithCertList(t *testing.T) { network[ownerCertData.Name().String()] = ownerCertWire type stage struct { + preprocess func() + postprocess func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) name string add map[string]enc.Wire expectAnchor bool @@ -1206,20 +1197,6 @@ func TestExpiredRootWithCertList(t *testing.T) { expectErrPart string } - stages := []stage{ - { - name: "certlist ok", - add: map[string]enc.Wire{ - listData2.Name().String(): listWireEnc2.Wire, - preAnchorData2.Name().String(): preAnchorWire2, - listData.Name().String(): listWireEnc.Wire, - preAnchorData.Name().String(): preAnchorWire, - }, - expectAnchor: true, - expectData: true, - }, - } - validateOnce := func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) (bool, error) { tcTestFetchCount = 0 done := make(chan struct { @@ -1243,7 +1220,47 @@ func TestExpiredRootWithCertList(t *testing.T) { return res.v, res.err } + stages := []stage{ + { + preprocess: func() { + return + }, + name: "normal chaincertlist ok", + add: map[string]enc.Wire{ + listData2.Name().String(): listWireEnc2.Wire, + preAnchorData2.Name().String(): preAnchorWire2, + listData.Name().String(): listWireEnc.Wire, + preAnchorData.Name().String(): preAnchorWire, + }, + expectAnchor: true, + expectData: true, + postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) { + return + }, + }, + { + preprocess: func() { + time.Sleep(time.Second * 9) + }, + name: "epxired chain certlist ok", + add: map[string]enc.Wire{ + listData2.Name().String(): listWireEnc2.Wire, + preAnchorData2.Name().String(): preAnchorWire2, + listData.Name().String(): listWireEnc.Wire, + preAnchorData.Name().String(): preAnchorWire, + }, + expectAnchor: true, + expectData: true, + postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) { + + return + }, + }, + } + for _, st := range stages { + st.preprocess() + t.Run(st.name, func(t *testing.T) { store := storage.NewMemoryStore() tcTestKeyChain = keychain.NewKeyChainMem(store) @@ -1280,6 +1297,9 @@ func TestExpiredRootWithCertList(t *testing.T) { require.Contains(t, anchorErr.Error(), st.expectErrPart) } } + + st.postprocess(trust, dataPkt, dataSigCov) }) + } } From d144a32a12edb1c7c43ba3547d8a65c31b5e82a1 Mon Sep 17 00:00:00 2001 From: Victor Chinnappan Date: Tue, 10 Mar 2026 21:24:05 -0700 Subject: [PATCH 03/23] Added Signature Flow Verification Tests --- std/security/trust_config_test.go | 275 +++++++++++++++++++++++++++++- 1 file changed, 272 insertions(+), 3 deletions(-) diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index 8cea2f12..750dee47 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -1045,7 +1045,7 @@ func TestTrustConfigLvsInter(t *testing.T) { testTrustConfigInter(t, schemaInter) } -func TestExpiredRootWithCertList(t *testing.T) { +func TestSignatureTimeValidationValidFlows(t *testing.T) { tu.SetT(t) store := storage.NewMemoryStore() @@ -1192,6 +1192,7 @@ func TestExpiredRootWithCertList(t *testing.T) { postprocess func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) name string add map[string]enc.Wire + trustAnchors []enc.Name expectAnchor bool expectData bool expectErrPart string @@ -1225,13 +1226,30 @@ func TestExpiredRootWithCertList(t *testing.T) { preprocess: func() { return }, - name: "normal chaincertlist ok", + name: "normal no certlist ok", + add: map[string]enc.Wire{ + listData.Name().String(): listWireEnc.Wire, + preAnchorData.Name().String(): preAnchorWire, + }, + trustAnchors: []enc.Name{expiredRootCertData.Name(), newRootCertData.Name()}, + expectAnchor: true, + expectData: true, + postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) { + return + }, + }, + { + preprocess: func() { + return + }, + name: "normal chain certlist ok", add: map[string]enc.Wire{ listData2.Name().String(): listWireEnc2.Wire, preAnchorData2.Name().String(): preAnchorWire2, listData.Name().String(): listWireEnc.Wire, preAnchorData.Name().String(): preAnchorWire, }, + trustAnchors: []enc.Name{newRootCertData.Name()}, expectAnchor: true, expectData: true, postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) { @@ -1249,6 +1267,7 @@ func TestExpiredRootWithCertList(t *testing.T) { listData.Name().String(): listWireEnc.Wire, preAnchorData.Name().String(): preAnchorWire, }, + trustAnchors: []enc.Name{newRootCertData.Name()}, expectAnchor: true, expectData: true, postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) { @@ -1270,7 +1289,7 @@ func TestExpiredRootWithCertList(t *testing.T) { network[k] = v } require.NoError(t, kc.InsertCert(newRootCertWire.Join())) - trust, err := sec.NewTrustConfig(kc, schema, []enc.Name{newRootCertData.Name()}) + trust, err := sec.NewTrustConfig(kc, schema, st.trustAnchors) require.NoError(t, err) // Validate anchor cert first, then user data. @@ -1303,3 +1322,253 @@ func TestExpiredRootWithCertList(t *testing.T) { } } + +func TestSignatureTimeValidationInvalidFlows(t *testing.T) { + tu.SetT(t) + + store := storage.NewMemoryStore() + tcTestKeyChain = keychain.NewKeyChainMem(store) + // Use intra-domain schema with root-to-root signing support + // This schema is the same as TRUST_CONFIG_INTRA_LVS but allows roots to sign each other + schema, err := trust_schema.NewLvsSchema(TRUST_CONFIG_INTER_LVS) + require.NoError(t, err) + + clear(tcTestNetwork) + tcTestT = t + network := tcTestNetwork + keychain2 := tcTestKeyChain + + now := time.Now() + nb := now + na := now.Add(time.Second * 5) + n := func(s string) enc.Name { return tu.NoErr(enc.NameFromStr(s)) } + + // Expired root (old root) + expiredRootSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/root")))) + expiredRootKeyData := tu.NoErr(signer.MarshalSecretToData(expiredRootSigner)) + expiredRootCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: expiredRootSigner, + Data: expiredRootKeyData, + IssuerId: enc.NewGenericComponent("self"), + NotBefore: now, // Expired 3 hours ago + NotAfter: now.Add(time.Second * 7), // Expired 1 hour ago + })) + expiredRootCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(expiredRootCertWire)) + network[expiredRootCertData.Name().String()] = expiredRootCertWire + keychain2.InsertCert(expiredRootCertWire.Join()) + + // New root (replacement root) + newRootSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/root")))) + newRootKeyData := tu.NoErr(signer.MarshalSecretToData(newRootSigner)) + newRootCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: newRootSigner, + Data: newRootKeyData, + IssuerId: enc.NewGenericComponent("self"), + NotBefore: nb, + NotAfter: now.Add(time.Second * 17), + })) + newRootCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(newRootCertWire)) + network[newRootCertData.Name().String()] = newRootCertWire + keychain2.InsertCert(newRootCertWire.Join()) + + // Owner <= testbed + ownerSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/root/owner")))) + ownerKeyData := tu.NoErr(signer.MarshalSecretToData(ownerSigner)) + ownerCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: expiredRootSigner, + Data: ownerKeyData, + IssuerId: enc.NewGenericComponent("root"), + NotBefore: nb, + NotAfter: na, + })) + ownerCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(ownerCertWire)) + + // Workspace anchor (cert) + anchorSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/app")))) + anchorKeyData := tu.NoErr(signer.MarshalSecretToData(anchorSigner)) + anchorCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: anchorSigner, + Data: anchorKeyData, + IssuerId: enc.NewGenericComponent("self"), + NotBefore: nb, + NotAfter: na, + })) + anchorCertData, anchorSigCov, _ := spec.Spec{}.ReadData(enc.NewWireView(anchorCertWire)) + + // Workspace preanchor (precert) + preAnchorWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: ownerSigner, + Data: anchorKeyData, + IssuerId: enc.NewGenericComponent("owner"), + NotBefore: nb, + NotAfter: na, + })) + preAnchorData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(preAnchorWire)) + + listContent := tu.NoErr(sec.EncodeCertList([]enc.Name{preAnchorData.Name()})) + listPrefix := tu.NoErr(sec.CertListPrefix(anchorSigner.KeyName())) + listName := listPrefix.Append(enc.NewVersionComponent(uint64(time.Now().UnixMicro()))) + listWireEnc := tu.NoErr(spec.Spec{}.MakeData(listName, &ndn.DataConfig{ + Freshness: optional.Some(time.Minute), + SigNotBefore: optional.Some(nb), + SigNotAfter: optional.Some(na), + }, listContent, anchorSigner)) + listData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(listWireEnc.Wire)) + + userSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/app/user/alice")))) + userKeyData := tu.NoErr(signer.MarshalSecretToData(userSigner)) + userCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: anchorSigner, + Data: userKeyData, + IssuerId: enc.NewGenericComponent("app"), + NotBefore: nb, + NotAfter: na, + })) + userCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(userCertWire)) + + payload := enc.Wire{[]byte{0x01}} + dataWire := tu.NoErr(spec.Spec{}.MakeData(n("/app/user/alice/data"), &ndn.DataConfig{ + Freshness: optional.Some(time.Minute), + }, payload, userSigner)) + dataPkt, dataSigCov, _ := spec.Spec{}.ReadData(enc.NewWireView(dataWire.Wire)) + + sigTimeBeforeUserCert := optional.Some(time.Duration(now.Add(-time.Hour).UnixMilli()) * time.Millisecond) + dataWireInvalidSigTime := tu.NoErr(spec.Spec{}.MakeData(n("/app/user/alice/data"), &ndn.DataConfig{ + Freshness: optional.Some(time.Minute), + SigTime: sigTimeBeforeUserCert, + }, payload, userSigner)) + dataPktInvalidSigTime, dataSigCovInvalidSigTime, _ := spec.Spec{}.ReadData(enc.NewWireView(dataWireInvalidSigTime.Wire)) + + require.True(t, schema.Check(anchorCertData.Name(), anchorCertData.Name())) + require.True(t, schema.Check(preAnchorData.Name(), ownerCertData.Name())) + require.True(t, schema.Check(listData.Name(), anchorCertData.Name())) + require.True(t, schema.Check(userCertData.Name(), anchorCertData.Name())) + require.True(t, schema.Check(dataPkt.Name(), userCertData.Name())) + + network[anchorCertData.Name().String()] = anchorCertWire + network[userCertData.Name().String()] = userCertWire + network[ownerCertData.Name().String()] = ownerCertWire + + type stage struct { + preprocess func() + postprocess func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) + name string + add map[string]enc.Wire + trustAnchors []enc.Name + data ndn.Data + dataSigCov enc.Wire + expectAnchor bool + expectData bool + expectErrPart string + } + + validateOnce := func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) (bool, error) { + tcTestFetchCount = 0 + done := make(chan struct { + v bool + err error + }, 1) + trust.Validate(sec.TrustConfigValidateArgs{ + Data: data, + DataSigCov: sigCov, + Fetch: fetchFun, + Callback: func(valid bool, err error) { + done <- struct { + v bool + err error + }{v: valid, err: err} + }, + + UseSignatureTime: optional.Some(true), + }) + res := <-done + return res.v, res.err + } + + stages := []stage{ + { + preprocess: func() {}, + name: "normal chain certlist ok", + add: map[string]enc.Wire{listData.Name().String(): listWireEnc.Wire, preAnchorData.Name().String(): preAnchorWire}, + trustAnchors: []enc.Name{expiredRootCertData.Name(), newRootCertData.Name()}, + expectAnchor: true, + expectData: true, + postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) {}, + }, + { + preprocess: func() {}, + name: "data signed when user cert invalid", + add: map[string]enc.Wire{listData.Name().String(): listWireEnc.Wire, preAnchorData.Name().String(): preAnchorWire}, + trustAnchors: []enc.Name{expiredRootCertData.Name(), newRootCertData.Name()}, + data: dataPktInvalidSigTime, + dataSigCov: dataSigCovInvalidSigTime, + expectAnchor: true, + expectData: false, + expectErrPart: "signed during an invalid period", + postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) {}, + }, + { + preprocess: func() { + time.Sleep(time.Second * 9) + }, + name: "expired chain no certlist not ok", + add: map[string]enc.Wire{listData.Name().String(): listWireEnc.Wire, preAnchorData.Name().String(): preAnchorWire}, + trustAnchors: []enc.Name{newRootCertData.Name()}, + expectAnchor: false, + expectData: false, + postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) {}, + }, + } + + tcTestKeyChain = keychain.NewKeyChainMem(store) + kc := tcTestKeyChain + + for _, st := range stages { + st.preprocess() + + t.Run(st.name, func(t *testing.T) { + for k, v := range st.add { + network[k] = v + } + + require.NoError(t, kc.InsertCert(newRootCertWire.Join())) + trust, err := sec.NewTrustConfig(kc, schema, st.trustAnchors) + require.NoError(t, err) + + validateData := dataPkt + validateSigCov := dataSigCov + if st.data != nil { + validateData = st.data + validateSigCov = st.dataSigCov + } + + // Validate anchor cert first, then user data. + anchorValid, anchorErr := validateOnce(trust, anchorCertData, anchorSigCov) + dataValid, dataErr := validateOnce(trust, validateData, validateSigCov) + if st.expectData { + require.True(t, dataValid) + require.NoError(t, dataErr) + } else { + require.False(t, dataValid) + require.Error(t, dataErr) + if st.expectErrPart != "" { + require.Contains(t, dataErr.Error(), st.expectErrPart) + } + } + + if st.expectAnchor { + require.True(t, anchorValid) + require.NoError(t, anchorErr) + } else { + require.False(t, anchorValid) + require.Error(t, anchorErr) + if st.expectErrPart != "" { + require.Contains(t, anchorErr.Error(), st.expectErrPart) + } + } + + st.postprocess(trust, dataPkt, dataSigCov) + }) + + } +} From cc7742b22f900ed431e6fede6969b81a2e319fa6 Mon Sep 17 00:00:00 2001 From: Victor Chinnappan Date: Tue, 10 Mar 2026 22:04:11 -0700 Subject: [PATCH 04/23] KC verif revert --- std/security/trust_config.go | 3 +-- std/security/trust_config_test.go | 11 +++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/std/security/trust_config.go b/std/security/trust_config.go index b09d8c5f..f0737e14 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -61,8 +61,7 @@ func NewTrustConfig(keyChain ndn.KeyChain, schema ndn.TrustSchema, roots []enc.N // Check if all roots are present in the keychain for _, root := range roots { if certBytes, _ := keyChain.Store().Get(root, false); len(certBytes) == 0 { - continue // TODO: Make this more robust - // return nil, fmt.Errorf("trust anchor not found in keychain: %s", root) + return nil, fmt.Errorf("trust anchor not found in keychain: %s", root) } else { certData, _, err := spec.Spec{}.ReadData(enc.NewBufferView(certBytes)) if err != nil { diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index 750dee47..30aeb7ee 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -1072,7 +1072,7 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { Signer: expiredRootSigner, Data: expiredRootKeyData, IssuerId: enc.NewGenericComponent("self"), - NotBefore: now, // Expired 3 hours ago + NotBefore: nb, // Expired 3 hours ago NotAfter: now.Add(time.Second * 7), // Expired 1 hour ago })) expiredRootCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(expiredRootCertWire)) @@ -1192,6 +1192,7 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { postprocess func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) name string add map[string]enc.Wire + kcInsert []enc.Wire trustAnchors []enc.Name expectAnchor bool expectData bool @@ -1231,6 +1232,7 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { listData.Name().String(): listWireEnc.Wire, preAnchorData.Name().String(): preAnchorWire, }, + kcInsert: []enc.Wire{expiredRootCertWire, newRootCertWire}, trustAnchors: []enc.Name{expiredRootCertData.Name(), newRootCertData.Name()}, expectAnchor: true, expectData: true, @@ -1249,6 +1251,7 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { listData.Name().String(): listWireEnc.Wire, preAnchorData.Name().String(): preAnchorWire, }, + kcInsert: []enc.Wire{newRootCertWire}, trustAnchors: []enc.Name{newRootCertData.Name()}, expectAnchor: true, expectData: true, @@ -1267,6 +1270,7 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { listData.Name().String(): listWireEnc.Wire, preAnchorData.Name().String(): preAnchorWire, }, + kcInsert: []enc.Wire{newRootCertWire}, trustAnchors: []enc.Name{newRootCertData.Name()}, expectAnchor: true, expectData: true, @@ -1288,7 +1292,10 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { for k, v := range st.add { network[k] = v } - require.NoError(t, kc.InsertCert(newRootCertWire.Join())) + + for _, w := range st.kcInsert { + require.NoError(t, kc.InsertCert(w.Join())) + } trust, err := sec.NewTrustConfig(kc, schema, st.trustAnchors) require.NoError(t, err) From 8f0703c75d542cb37b9576bd81dc01bd14df3015 Mon Sep 17 00:00:00 2001 From: Victor Chinnappan Date: Sat, 14 Mar 2026 22:22:43 -0700 Subject: [PATCH 05/23] Combine new flow tests into one and clean it up --- std/security/trust_config_test.go | 347 ++++++------------------------ 1 file changed, 68 insertions(+), 279 deletions(-) diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index 30aeb7ee..88c4f603 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -1045,11 +1045,13 @@ func TestTrustConfigLvsInter(t *testing.T) { testTrustConfigInter(t, schemaInter) } -func TestSignatureTimeValidationValidFlows(t *testing.T) { +func testSignatureTimeValidationHelper(t *testing.T, schema *trust_schema.LvsSchema) { + return +} + +func TestSignatureTimeValidationFlows(t *testing.T) { tu.SetT(t) - store := storage.NewMemoryStore() - tcTestKeyChain = keychain.NewKeyChainMem(store) // Use intra-domain schema with root-to-root signing support // This schema is the same as TRUST_CONFIG_INTRA_LVS but allows roots to sign each other schema, err := trust_schema.NewLvsSchema(TRUST_CONFIG_INTER_LVS) @@ -1058,7 +1060,6 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { clear(tcTestNetwork) tcTestT = t network := tcTestNetwork - keychain2 := tcTestKeyChain now := time.Now() nb := now @@ -1072,12 +1073,11 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { Signer: expiredRootSigner, Data: expiredRootKeyData, IssuerId: enc.NewGenericComponent("self"), - NotBefore: nb, // Expired 3 hours ago - NotAfter: now.Add(time.Second * 7), // Expired 1 hour ago + NotBefore: nb, + NotAfter: now.Add(time.Second * 7), // Expires in 7 seconds })) expiredRootCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(expiredRootCertWire)) network[expiredRootCertData.Name().String()] = expiredRootCertWire - keychain2.InsertCert(expiredRootCertWire.Join()) // New root (replacement root) newRootSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/root")))) @@ -1091,30 +1091,29 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { })) newRootCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(newRootCertWire)) network[newRootCertData.Name().String()] = newRootCertWire - keychain2.InsertCert(newRootCertWire.Join()) // PreAnchor: replacement certificate for expired root's key, signed by new root // This certificate verifies/authorizes the expired root's key - preAnchorWire2 := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + expiredRootVerifCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ Signer: newRootSigner, // Signed by new root Data: expiredRootKeyData, // Certificate for expired root's key IssuerId: enc.NewGenericComponent("root"), NotBefore: nb, NotAfter: now.Add(time.Second * 15), })) - preAnchorData2, _, _ := spec.Spec{}.ReadData(enc.NewWireView(preAnchorWire2)) + expiredRootVerifCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(expiredRootVerifCertWire)) // CertList for expired root pointing to preAnchor - // This CertList is signed by the new root itself (even though expired) - listContent2 := tu.NoErr(sec.EncodeCertList([]enc.Name{preAnchorData2.Name()})) - listPrefix2 := tu.NoErr(sec.CertListPrefix(expiredRootSigner.KeyName())) - listName2 := listPrefix2.Append(enc.NewVersionComponent(uint64(now.UnixMicro()))) - listWireEnc2 := tu.NoErr(spec.Spec{}.MakeData(listName2, &ndn.DataConfig{ + // This CertList is signed by the expired root itself + expiredRootCertListContent := tu.NoErr(sec.EncodeCertList([]enc.Name{expiredRootVerifCertData.Name()})) + expiredRootCertListPrefix := tu.NoErr(sec.CertListPrefix(expiredRootSigner.KeyName())) + expiredRootCertListName := expiredRootCertListPrefix.Append(enc.NewVersionComponent(uint64(now.UnixMicro()))) + expiredRootCertListWire := tu.NoErr(spec.Spec{}.MakeData(expiredRootCertListName, &ndn.DataConfig{ Freshness: optional.Some(time.Second * 19), SigNotBefore: optional.Some(nb), SigNotAfter: optional.Some(na), - }, listContent2, expiredRootSigner)) // Signed by expired root - listData2, _, _ := spec.Spec{}.ReadData(enc.NewWireView(listWireEnc2.Wire)) + }, expiredRootCertListContent, expiredRootSigner)) // Signed by expired root + expiredRootCertListData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(expiredRootCertListWire.Wire)) // Owner <= testbed ownerSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/root/owner")))) @@ -1171,17 +1170,27 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { })) userCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(userCertWire)) + // User data payload := enc.Wire{[]byte{0x01}} dataWire := tu.NoErr(spec.Spec{}.MakeData(n("/app/user/alice/data"), &ndn.DataConfig{ Freshness: optional.Some(time.Minute), }, payload, userSigner)) dataPkt, dataSigCov, _ := spec.Spec{}.ReadData(enc.NewWireView(dataWire.Wire)) + // User data with invalid signature time + sigTimeBeforeUserCert := optional.Some(time.Duration(now.Add(-time.Hour).UnixMilli()) * time.Millisecond) + dataWireInvalidSigTime := tu.NoErr(spec.Spec{}.MakeData(n("/app/user/alice/data"), &ndn.DataConfig{ + Freshness: optional.Some(time.Minute), + SigTime: sigTimeBeforeUserCert, + }, payload, userSigner)) + dataPktInvalidSigTime, dataSigCovInvalidSigTime, _ := spec.Spec{}.ReadData(enc.NewWireView(dataWireInvalidSigTime.Wire)) + require.True(t, schema.Check(anchorCertData.Name(), anchorCertData.Name())) require.True(t, schema.Check(preAnchorData.Name(), ownerCertData.Name())) require.True(t, schema.Check(listData.Name(), anchorCertData.Name())) require.True(t, schema.Check(userCertData.Name(), anchorCertData.Name())) require.True(t, schema.Check(dataPkt.Name(), userCertData.Name())) + require.True(t, schema.Check(dataPktInvalidSigTime.Name(), userCertData.Name())) network[anchorCertData.Name().String()] = anchorCertWire network[userCertData.Name().String()] = userCertWire @@ -1189,11 +1198,12 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { type stage struct { preprocess func() - postprocess func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) name string add map[string]enc.Wire kcInsert []enc.Wire trustAnchors []enc.Name + data ndn.Data + dataSigCov enc.Wire expectAnchor bool expectData bool expectErrPart string @@ -1223,6 +1233,7 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { } stages := []stage{ + // Validate flows before the expired root is replaced { preprocess: func() { return @@ -1236,9 +1247,6 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { trustAnchors: []enc.Name{expiredRootCertData.Name(), newRootCertData.Name()}, expectAnchor: true, expectData: true, - postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) { - return - }, }, { preprocess: func() { @@ -1246,299 +1254,83 @@ func TestSignatureTimeValidationValidFlows(t *testing.T) { }, name: "normal chain certlist ok", add: map[string]enc.Wire{ - listData2.Name().String(): listWireEnc2.Wire, - preAnchorData2.Name().String(): preAnchorWire2, - listData.Name().String(): listWireEnc.Wire, - preAnchorData.Name().String(): preAnchorWire, + expiredRootCertListData.Name().String(): expiredRootCertListWire.Wire, + expiredRootVerifCertData.Name().String(): expiredRootVerifCertWire, + listData.Name().String(): listWireEnc.Wire, + preAnchorData.Name().String(): preAnchorWire, }, kcInsert: []enc.Wire{newRootCertWire}, trustAnchors: []enc.Name{newRootCertData.Name()}, expectAnchor: true, expectData: true, - postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) { + }, + { + preprocess: func() { return }, + name: "data signed when user cert invalid", + add: map[string]enc.Wire{ + expiredRootCertListData.Name().String(): expiredRootCertListWire.Wire, + expiredRootVerifCertData.Name().String(): expiredRootVerifCertWire, + listData.Name().String(): listWireEnc.Wire, + preAnchorData.Name().String(): preAnchorWire, + }, + kcInsert: []enc.Wire{expiredRootCertWire, newRootCertWire}, + trustAnchors: []enc.Name{expiredRootCertData.Name(), newRootCertData.Name()}, + data: dataPktInvalidSigTime, + dataSigCov: dataSigCovInvalidSigTime, + expectAnchor: true, + expectData: false, + expectErrPart: "signed during an invalid period", }, + + // Validate flows after the expired root is replaced { preprocess: func() { - time.Sleep(time.Second * 9) + time.Sleep(time.Second * 9) // Wait for expiration }, name: "epxired chain certlist ok", add: map[string]enc.Wire{ - listData2.Name().String(): listWireEnc2.Wire, - preAnchorData2.Name().String(): preAnchorWire2, - listData.Name().String(): listWireEnc.Wire, - preAnchorData.Name().String(): preAnchorWire, + expiredRootCertListData.Name().String(): expiredRootCertListWire.Wire, + expiredRootVerifCertData.Name().String(): expiredRootVerifCertWire, + listData.Name().String(): listWireEnc.Wire, + preAnchorData.Name().String(): preAnchorWire, }, kcInsert: []enc.Wire{newRootCertWire}, trustAnchors: []enc.Name{newRootCertData.Name()}, expectAnchor: true, expectData: true, - postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) { - - return - }, - }, - } - - for _, st := range stages { - st.preprocess() - - t.Run(st.name, func(t *testing.T) { - store := storage.NewMemoryStore() - tcTestKeyChain = keychain.NewKeyChainMem(store) - kc := tcTestKeyChain - - for k, v := range st.add { - network[k] = v - } - - for _, w := range st.kcInsert { - require.NoError(t, kc.InsertCert(w.Join())) - } - trust, err := sec.NewTrustConfig(kc, schema, st.trustAnchors) - require.NoError(t, err) - - // Validate anchor cert first, then user data. - anchorValid, anchorErr := validateOnce(trust, anchorCertData, anchorSigCov) - dataValid, dataErr := validateOnce(trust, dataPkt, dataSigCov) - if st.expectData { - require.True(t, dataValid) - require.NoError(t, dataErr) - } else { - require.False(t, dataValid) - require.Error(t, dataErr) - if st.expectErrPart != "" { - require.Contains(t, dataErr.Error(), st.expectErrPart) - } - } - - if st.expectAnchor { - require.True(t, anchorValid) - require.NoError(t, anchorErr) - } else { - require.False(t, anchorValid) - require.Error(t, anchorErr) - if st.expectErrPart != "" { - require.Contains(t, anchorErr.Error(), st.expectErrPart) - } - } - - st.postprocess(trust, dataPkt, dataSigCov) - }) - - } -} - -func TestSignatureTimeValidationInvalidFlows(t *testing.T) { - tu.SetT(t) - - store := storage.NewMemoryStore() - tcTestKeyChain = keychain.NewKeyChainMem(store) - // Use intra-domain schema with root-to-root signing support - // This schema is the same as TRUST_CONFIG_INTRA_LVS but allows roots to sign each other - schema, err := trust_schema.NewLvsSchema(TRUST_CONFIG_INTER_LVS) - require.NoError(t, err) - - clear(tcTestNetwork) - tcTestT = t - network := tcTestNetwork - keychain2 := tcTestKeyChain - - now := time.Now() - nb := now - na := now.Add(time.Second * 5) - n := func(s string) enc.Name { return tu.NoErr(enc.NameFromStr(s)) } - - // Expired root (old root) - expiredRootSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/root")))) - expiredRootKeyData := tu.NoErr(signer.MarshalSecretToData(expiredRootSigner)) - expiredRootCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ - Signer: expiredRootSigner, - Data: expiredRootKeyData, - IssuerId: enc.NewGenericComponent("self"), - NotBefore: now, // Expired 3 hours ago - NotAfter: now.Add(time.Second * 7), // Expired 1 hour ago - })) - expiredRootCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(expiredRootCertWire)) - network[expiredRootCertData.Name().String()] = expiredRootCertWire - keychain2.InsertCert(expiredRootCertWire.Join()) - - // New root (replacement root) - newRootSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/root")))) - newRootKeyData := tu.NoErr(signer.MarshalSecretToData(newRootSigner)) - newRootCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ - Signer: newRootSigner, - Data: newRootKeyData, - IssuerId: enc.NewGenericComponent("self"), - NotBefore: nb, - NotAfter: now.Add(time.Second * 17), - })) - newRootCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(newRootCertWire)) - network[newRootCertData.Name().String()] = newRootCertWire - keychain2.InsertCert(newRootCertWire.Join()) - - // Owner <= testbed - ownerSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/root/owner")))) - ownerKeyData := tu.NoErr(signer.MarshalSecretToData(ownerSigner)) - ownerCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ - Signer: expiredRootSigner, - Data: ownerKeyData, - IssuerId: enc.NewGenericComponent("root"), - NotBefore: nb, - NotAfter: na, - })) - ownerCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(ownerCertWire)) - - // Workspace anchor (cert) - anchorSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/app")))) - anchorKeyData := tu.NoErr(signer.MarshalSecretToData(anchorSigner)) - anchorCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ - Signer: anchorSigner, - Data: anchorKeyData, - IssuerId: enc.NewGenericComponent("self"), - NotBefore: nb, - NotAfter: na, - })) - anchorCertData, anchorSigCov, _ := spec.Spec{}.ReadData(enc.NewWireView(anchorCertWire)) - - // Workspace preanchor (precert) - preAnchorWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ - Signer: ownerSigner, - Data: anchorKeyData, - IssuerId: enc.NewGenericComponent("owner"), - NotBefore: nb, - NotAfter: na, - })) - preAnchorData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(preAnchorWire)) - - listContent := tu.NoErr(sec.EncodeCertList([]enc.Name{preAnchorData.Name()})) - listPrefix := tu.NoErr(sec.CertListPrefix(anchorSigner.KeyName())) - listName := listPrefix.Append(enc.NewVersionComponent(uint64(time.Now().UnixMicro()))) - listWireEnc := tu.NoErr(spec.Spec{}.MakeData(listName, &ndn.DataConfig{ - Freshness: optional.Some(time.Minute), - SigNotBefore: optional.Some(nb), - SigNotAfter: optional.Some(na), - }, listContent, anchorSigner)) - listData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(listWireEnc.Wire)) - - userSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/app/user/alice")))) - userKeyData := tu.NoErr(signer.MarshalSecretToData(userSigner)) - userCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ - Signer: anchorSigner, - Data: userKeyData, - IssuerId: enc.NewGenericComponent("app"), - NotBefore: nb, - NotAfter: na, - })) - userCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(userCertWire)) - - payload := enc.Wire{[]byte{0x01}} - dataWire := tu.NoErr(spec.Spec{}.MakeData(n("/app/user/alice/data"), &ndn.DataConfig{ - Freshness: optional.Some(time.Minute), - }, payload, userSigner)) - dataPkt, dataSigCov, _ := spec.Spec{}.ReadData(enc.NewWireView(dataWire.Wire)) - - sigTimeBeforeUserCert := optional.Some(time.Duration(now.Add(-time.Hour).UnixMilli()) * time.Millisecond) - dataWireInvalidSigTime := tu.NoErr(spec.Spec{}.MakeData(n("/app/user/alice/data"), &ndn.DataConfig{ - Freshness: optional.Some(time.Minute), - SigTime: sigTimeBeforeUserCert, - }, payload, userSigner)) - dataPktInvalidSigTime, dataSigCovInvalidSigTime, _ := spec.Spec{}.ReadData(enc.NewWireView(dataWireInvalidSigTime.Wire)) - - require.True(t, schema.Check(anchorCertData.Name(), anchorCertData.Name())) - require.True(t, schema.Check(preAnchorData.Name(), ownerCertData.Name())) - require.True(t, schema.Check(listData.Name(), anchorCertData.Name())) - require.True(t, schema.Check(userCertData.Name(), anchorCertData.Name())) - require.True(t, schema.Check(dataPkt.Name(), userCertData.Name())) - - network[anchorCertData.Name().String()] = anchorCertWire - network[userCertData.Name().String()] = userCertWire - network[ownerCertData.Name().String()] = ownerCertWire - - type stage struct { - preprocess func() - postprocess func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) - name string - add map[string]enc.Wire - trustAnchors []enc.Name - data ndn.Data - dataSigCov enc.Wire - expectAnchor bool - expectData bool - expectErrPart string - } - - validateOnce := func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) (bool, error) { - tcTestFetchCount = 0 - done := make(chan struct { - v bool - err error - }, 1) - trust.Validate(sec.TrustConfigValidateArgs{ - Data: data, - DataSigCov: sigCov, - Fetch: fetchFun, - Callback: func(valid bool, err error) { - done <- struct { - v bool - err error - }{v: valid, err: err} - }, - - UseSignatureTime: optional.Some(true), - }) - res := <-done - return res.v, res.err - } - - stages := []stage{ - { - preprocess: func() {}, - name: "normal chain certlist ok", - add: map[string]enc.Wire{listData.Name().String(): listWireEnc.Wire, preAnchorData.Name().String(): preAnchorWire}, - trustAnchors: []enc.Name{expiredRootCertData.Name(), newRootCertData.Name()}, - expectAnchor: true, - expectData: true, - postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) {}, - }, - { - preprocess: func() {}, - name: "data signed when user cert invalid", - add: map[string]enc.Wire{listData.Name().String(): listWireEnc.Wire, preAnchorData.Name().String(): preAnchorWire}, - trustAnchors: []enc.Name{expiredRootCertData.Name(), newRootCertData.Name()}, - data: dataPktInvalidSigTime, - dataSigCov: dataSigCovInvalidSigTime, - expectAnchor: true, - expectData: false, - expectErrPart: "signed during an invalid period", - postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) {}, }, { preprocess: func() { - time.Sleep(time.Second * 9) + // Remove the certlist and certificate from the network to simulate a fully expired chainz + network[expiredRootCertListData.Name().String()] = nil + network[expiredRootVerifCertData.Name().String()] = nil }, name: "expired chain no certlist not ok", add: map[string]enc.Wire{listData.Name().String(): listWireEnc.Wire, preAnchorData.Name().String(): preAnchorWire}, + kcInsert: []enc.Wire{newRootCertWire}, trustAnchors: []enc.Name{newRootCertData.Name()}, expectAnchor: false, expectData: false, - postprocess: func(trust *sec.TrustConfig, data ndn.Data, sigCov enc.Wire) {}, }, } - tcTestKeyChain = keychain.NewKeyChainMem(store) - kc := tcTestKeyChain - for _, st := range stages { st.preprocess() t.Run(st.name, func(t *testing.T) { + store := storage.NewMemoryStore() + tcTestKeyChain = keychain.NewKeyChainMem(store) + kc := tcTestKeyChain + for k, v := range st.add { network[k] = v } - require.NoError(t, kc.InsertCert(newRootCertWire.Join())) + for _, w := range st.kcInsert { + require.NoError(t, kc.InsertCert(w.Join())) + } trust, err := sec.NewTrustConfig(kc, schema, st.trustAnchors) require.NoError(t, err) @@ -1573,9 +1365,6 @@ func TestSignatureTimeValidationInvalidFlows(t *testing.T) { require.Contains(t, anchorErr.Error(), st.expectErrPart) } } - - st.postprocess(trust, dataPkt, dataSigCov) }) - } } From 7f5dd8b84376f2f8c88c8b5a4118d5683a6cb9c5 Mon Sep 17 00:00:00 2001 From: Victor Chinnappan Date: Sun, 15 Mar 2026 21:35:21 -0700 Subject: [PATCH 06/23] Move common existing cert validation into a helper function --- std/security/trust_config.go | 312 ++++++++++++----------------------- 1 file changed, 108 insertions(+), 204 deletions(-) diff --git a/std/security/trust_config.go b/std/security/trust_config.go index f0737e14..41760c40 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -145,6 +145,111 @@ type TrustConfigValidateArgs struct { UseSignatureTime optional.Optional[bool] } +func (tc *TrustConfig) ValidateProvidedCertHelper(args TrustConfigValidateArgs, dataName enc.Name, certName enc.Name) { + // Check schema if the key is allowed + if args.crossSchemaIsValid { + // continue + } else if tc.schema.Check(dataName, certName) { + // continue + } else if args.Data.CrossSchema() != nil { + tc.validateCrossSchema(TrustConfigValidateArgs{ + Data: args.Data, + DataSigCov: args.DataSigCov, + + Fetch: args.Fetch, + Callback: func(valid bool, err error) { + if valid && err == nil { + // Continue validation with cross schema + args.crossSchemaIsValid = true + tc.Validate(args) + } else { + args.Callback(valid, fmt.Errorf("cross schema: %w", err)) + } + }, + OverrideName: args.OverrideName, + IgnoreValidity: args.IgnoreValidity, + cert: args.cert, + depth: args.depth, + }) + return + } else { + args.Callback(false, fmt.Errorf("trust schema mismatch: %s signed by %s", dataName, certName)) + return + } + + // Validate signature on data + valid, err := signer.ValidateData(args.Data, args.DataSigCov, args.cert) + if !valid { + args.Callback(false, fmt.Errorf("signature is invalid")) + return + } + if err != nil { + args.Callback(false, fmt.Errorf("signature validate error: %w", err)) + return + } + + // Check if the certificate was already validated. + // Since all roots are in cache, this breaks the recursion. + if args.certIsValid { + args.Callback(true, nil) + return + } + + // This should never happen, but just in case + if len(args.certSigCov) == 0 { + args.Callback(false, fmt.Errorf("cert sig covered is nil: %s", certName)) + return + } + + // Monkey patch the callback to store the cert in + // keychain and cache if the validation passes. + origCallback := args.Callback + args.Callback = func(valid bool, err error) { + if valid && err == nil { + // Cache is thread safe + tc.certCache.Put(args.cert) + + // Keychain is not thread safe for inserts + if len(args.certRaw) > 0 { + tc.mutex.Lock() + err := tc.keychain.InsertCert(args.certRaw.Join()) + tc.mutex.Unlock() + if err != nil { // broken keychain + log.Error(tc, "Failed to insert certificate to keychain", "name", args.cert.Name(), "err", err) + } + } + } else { + log.Warn(tc, "Received invalid certificate", "name", args.cert.Name(), "err", err) + } + + origCallback(valid, err) // continue bubbling up result + } + + // Recursively validate the certificate + tc.Validate(TrustConfigValidateArgs{ + Data: args.cert, + DataSigCov: args.certSigCov, + + Fetch: args.Fetch, + Callback: args.Callback, + OverrideName: nil, + IgnoreValidity: args.IgnoreValidity, + origDataName: args.origDataName, + + cert: nil, + certSigCov: nil, + certRaw: nil, + certIsValid: false, + + crossSchemaIsValid: false, + + UseSignatureTime: args.UseSignatureTime, + + depth: args.depth, + }) + return +} + // Validate validates a Data packet using a fetch API. func (tc *TrustConfig) ValidateWithExpiry(args TrustConfigValidateArgs) { if args.Data == nil { @@ -208,105 +313,7 @@ func (tc *TrustConfig) ValidateWithExpiry(args TrustConfigValidateArgs) { return } - // Check schema if the key is allowed - if args.crossSchemaIsValid { - // continue - } else if tc.schema.Check(dataName, certName) { - // continue - } else if args.Data.CrossSchema() != nil { - tc.validateCrossSchema(TrustConfigValidateArgs{ - Data: args.Data, - DataSigCov: args.DataSigCov, - - Fetch: args.Fetch, - Callback: func(valid bool, err error) { - if valid && err == nil { - // Continue validation with cross schema - args.crossSchemaIsValid = true - tc.ValidateWithExpiry(args) - } else { - args.Callback(valid, fmt.Errorf("cross schema: %w", err)) - } - }, - OverrideName: args.OverrideName, - IgnoreValidity: args.IgnoreValidity, - cert: args.cert, - depth: args.depth, - }) - return - } else { - args.Callback(false, fmt.Errorf("trust schema mismatch: %s signed by %s", dataName, certName)) - return - } - - // Validate signature on data - valid, err := signer.ValidateData(args.Data, args.DataSigCov, args.cert) - if !valid { - args.Callback(false, fmt.Errorf("signature is invalid")) - return - } - if err != nil { - args.Callback(false, fmt.Errorf("signature validate error: %w", err)) - return - } - - // Check if the certificate was already validated. - // Since all roots are in cache, this breaks the recursion. - if args.certIsValid { - args.Callback(true, nil) - return - } - - // This should never happen, but just in case - if len(args.certSigCov) == 0 { - args.Callback(false, fmt.Errorf("cert sig covered is nil: %s", certName)) - return - } - - // Monkey patch the callback to store the cert in - // keychain and cache if the validation passes. - origCallback := args.Callback - args.Callback = func(valid bool, err error) { - if valid && err == nil { - // Cache is thread safe - tc.certCache.Put(args.cert) - - // Keychain is not thread safe for inserts - if len(args.certRaw) > 0 { - tc.mutex.Lock() - err := tc.keychain.InsertCert(args.certRaw.Join()) - tc.mutex.Unlock() - if err != nil { // broken keychain - log.Error(tc, "Failed to insert certificate to keychain", "name", args.cert.Name(), "err", err) - } - } - } else { - log.Warn(tc, "Received invalid certificate", "name", args.cert.Name(), "err", err) - } - - origCallback(valid, err) // continue bubbling up result - } - - // Recursively validate the certificate - tc.ValidateWithExpiry(TrustConfigValidateArgs{ - Data: args.cert, - DataSigCov: args.certSigCov, - - Fetch: args.Fetch, - Callback: args.Callback, - OverrideName: nil, - IgnoreValidity: args.IgnoreValidity, - origDataName: args.origDataName, - - cert: nil, - certSigCov: nil, - certRaw: nil, - certIsValid: false, - - crossSchemaIsValid: false, - - depth: args.depth, - }) + tc.ValidateProvidedCertHelper(args, dataName, certName) return } @@ -394,7 +401,6 @@ func (tc *TrustConfig) ValidateWithExpiry(args TrustConfigValidateArgs) { args.Fetch(keyLocator, fetchCfg, cb) } -// TODO: See if we can create a helper function to handle the overlapping logic from both flows // Validate validates a Data packet using a fetch API. func (tc *TrustConfig) ValidateWithSignatureTime(args TrustConfigValidateArgs) { if args.Data == nil { @@ -436,12 +442,10 @@ func (tc *TrustConfig) ValidateWithSignatureTime(args TrustConfigValidateArgs) { return } - // Bail if the data is a cert and is not fresh + // If the certificate is expired and is a trust anchor, explore the cert list if t, ok := args.Data.ContentType().Get(); ok && t == ndn.ContentTypeKey { if !args.IgnoreValidity.GetOr(false) && CertIsExpired(args.Data) { - // fmt.Println("CERT IS EXPIRED: " + args.Data.Name().String()) if keyLocator.IsPrefix(args.Data.Name()) || tc.isTrustedAnchorKey(keyLocator) { - fmt.Println("EXPLORE CERT LIST: " + args.Data.Name().String()) anchorKeyName, err := KeyNameFromLocator(keyLocator) if err != nil { args.Callback(false, fmt.Errorf("invalid anchor key locator: %w", err)) @@ -480,7 +484,6 @@ func (tc *TrustConfig) ValidateWithSignatureTime(args TrustConfigValidateArgs) { sigTime := args.Data.Signature().SigTime() if sigTime != nil { - if args.cert.Signature() == nil { args.Callback(false, fmt.Errorf("data was signed during an invalid period of certificate [no signature]: %s", args.cert.Name())) return @@ -493,110 +496,11 @@ func (tc *TrustConfig) ValidateWithSignatureTime(args TrustConfigValidateArgs) { } if val, ok := cert_notAfter.Get(); !ok || sigTime.After(val) { args.Callback(false, fmt.Errorf("data was signed during an invalid period of certificate [signed after]: %s", args.cert.Name())) - return } } - // Check schema if the key is allowed - if args.crossSchemaIsValid { - // continue - } else if tc.schema.Check(dataName, certName) { - // continue - } else if args.Data.CrossSchema() != nil { - tc.validateCrossSchema(TrustConfigValidateArgs{ - Data: args.Data, - DataSigCov: args.DataSigCov, - - Fetch: args.Fetch, - Callback: func(valid bool, err error) { - if valid && err == nil { - // Continue validation with cross schema - args.crossSchemaIsValid = true - tc.ValidateWithSignatureTime(args) - } else { - args.Callback(valid, fmt.Errorf("cross schema: %w", err)) - } - }, - OverrideName: args.OverrideName, - IgnoreValidity: args.IgnoreValidity, - cert: args.cert, - depth: args.depth, - }) - return - } else { - args.Callback(false, fmt.Errorf("trust schema mismatch: %s signed by %s", dataName, certName)) - return - } - - // Validate signature on data - valid, err := signer.ValidateData(args.Data, args.DataSigCov, args.cert) - if !valid { - args.Callback(false, fmt.Errorf("signature is invalid")) - return - } - if err != nil { - args.Callback(false, fmt.Errorf("signature validate error: %w", err)) - return - } - - // Check if the certificate was already validated. - // Since all roots are in cache, this breaks the recursion. - if args.certIsValid { - args.Callback(true, nil) - return - } - - // This should never happen, but just in case - if len(args.certSigCov) == 0 { - args.Callback(false, fmt.Errorf("cert sig covered is nil: %s", certName)) - return - } - - // Monkey patch the callback to store the cert in - // keychain and cache if the validation passes. - origCallback := args.Callback - args.Callback = func(valid bool, err error) { - if valid && err == nil { - // Cache is thread safe - tc.certCache.Put(args.cert) - - // Keychain is not thread safe for inserts - if len(args.certRaw) > 0 { - tc.mutex.Lock() - err := tc.keychain.InsertCert(args.certRaw.Join()) - tc.mutex.Unlock() - if err != nil { // broken keychain - log.Error(tc, "Failed to insert certificate to keychain", "name", args.cert.Name(), "err", err) - } - } - } else { - log.Warn(tc, "Received invalid certificate", "name", args.cert.Name(), "err", err) - } - - origCallback(valid, err) // continue bubbling up result - } - - // Recursively validate the certificate - tc.ValidateWithSignatureTime(TrustConfigValidateArgs{ - Data: args.cert, - DataSigCov: args.certSigCov, - - Fetch: args.Fetch, - Callback: args.Callback, - OverrideName: nil, - IgnoreValidity: args.IgnoreValidity, - origDataName: args.origDataName, - - cert: nil, - certSigCov: nil, - certRaw: nil, - certIsValid: false, - - crossSchemaIsValid: false, - - depth: args.depth, - }) + tc.ValidateProvidedCertHelper(args, dataName, certName) return } From 01337ee67a23c2502ee232ce4502037035cd2b56 Mon Sep 17 00:00:00 2001 From: Victor Chinnappan Date: Mon, 16 Mar 2026 02:05:02 -0700 Subject: [PATCH 07/23] Move duplicate validation code into a helper function --- std/security/trust_config.go | 227 ++++++++++++----------------------- 1 file changed, 80 insertions(+), 147 deletions(-) diff --git a/std/security/trust_config.go b/std/security/trust_config.go index 41760c40..aaf8faef 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -250,73 +250,7 @@ func (tc *TrustConfig) ValidateProvidedCertHelper(args TrustConfigValidateArgs, return } -// Validate validates a Data packet using a fetch API. -func (tc *TrustConfig) ValidateWithExpiry(args TrustConfigValidateArgs) { - if args.Data == nil { - args.Callback(false, fmt.Errorf("data is nil")) - return - } - - if len(args.DataSigCov) == 0 { - args.Callback(false, fmt.Errorf("data sig covered is nil")) - return - } - - if args.origDataName == nil { - // Always use original name here, not the override name - args.origDataName = args.Data.Name() - } - - // Prevent infinite recursion for signer loops - if args.depth == 0 { - args.depth = 32 - } else if args.depth <= 1 { - args.Callback(false, fmt.Errorf("max depth reached")) - return - } else { - args.depth-- - } - - // Make sure the data is signed - signature := args.Data.Signature() - if signature == nil { - args.Callback(false, fmt.Errorf("signature is nil")) - return - } - - // Bail if the data is a cert and is not fresh - if t, ok := args.Data.ContentType().Get(); ok && t == ndn.ContentTypeKey { - if !args.IgnoreValidity.GetOr(false) && CertIsExpired(args.Data) { - args.Callback(false, fmt.Errorf("certificate is expired: %s", args.Data.Name())) - return - } - } - - // Get the key locator - keyLocator := signature.KeyName() - if len(keyLocator) == 0 { - args.Callback(false, fmt.Errorf("key locator is nil")) - return - } - - // If a certificate is provided, go directly to validation - if args.cert != nil { - certName := args.cert.Name() - dataName := args.Data.Name() - if len(args.OverrideName) > 0 { - dataName = args.OverrideName - } - - // Disallow empty names - if len(dataName) == 0 { - args.Callback(false, fmt.Errorf("data name is empty")) - return - } - - tc.ValidateProvidedCertHelper(args, dataName, certName) - return - } - +func (tc *TrustConfig) ValidateFetchHelper(args TrustConfigValidateArgs, keyLocator enc.Name) { // Handle self-signed certificate (potential trust anchor). if keyLocator.IsPrefix(args.Data.Name()) { tc.handleSelfSignedCert(args, keyLocator) @@ -338,7 +272,7 @@ func (tc *TrustConfig) ValidateWithExpiry(args TrustConfigValidateArgs) { args.certIsValid = true // Continue validation with cached cert - tc.ValidateWithExpiry(args) + tc.Validate(args) return } @@ -380,8 +314,8 @@ func (tc *TrustConfig) ValidateWithExpiry(args TrustConfigValidateArgs) { return } - // Bail if the fetched cert is not fresh - if !args.IgnoreValidity.GetOr(false) && CertIsExpired(res.Data) { + // Bail if the fetched cert is not fresh and not using signature time flow + if !args.UseSignatureTime.GetOr(false) && !args.IgnoreValidity.GetOr(false) && CertIsExpired(res.Data) { args.Callback(false, fmt.Errorf("certificate is expired: %s", res.Data.Name())) return } @@ -396,12 +330,84 @@ func (tc *TrustConfig) ValidateWithExpiry(args TrustConfigValidateArgs) { args.certIsValid = false // Continue validation with fetched cert - tc.ValidateWithExpiry(args) + tc.Validate(args) } args.Fetch(keyLocator, fetchCfg, cb) + return } // Validate validates a Data packet using a fetch API. +func (tc *TrustConfig) ValidateWithExpiry(args TrustConfigValidateArgs) { + if args.Data == nil { + args.Callback(false, fmt.Errorf("data is nil")) + return + } + + if len(args.DataSigCov) == 0 { + args.Callback(false, fmt.Errorf("data sig covered is nil")) + return + } + + if args.origDataName == nil { + // Always use original name here, not the override name + args.origDataName = args.Data.Name() + } + + // Prevent infinite recursion for signer loops + if args.depth == 0 { + args.depth = 32 + } else if args.depth <= 1 { + args.Callback(false, fmt.Errorf("max depth reached")) + return + } else { + args.depth-- + } + + // Make sure the data is signed + signature := args.Data.Signature() + if signature == nil { + args.Callback(false, fmt.Errorf("signature is nil")) + return + } + + // Bail if the data is a cert and is not fresh + if t, ok := args.Data.ContentType().Get(); ok && t == ndn.ContentTypeKey { + if !args.IgnoreValidity.GetOr(false) && CertIsExpired(args.Data) { + args.Callback(false, fmt.Errorf("certificate is expired: %s", args.Data.Name())) + return + } + } + + // Get the key locator + keyLocator := signature.KeyName() + if len(keyLocator) == 0 { + args.Callback(false, fmt.Errorf("key locator is nil")) + return + } + + // If a certificate is provided, go directly to validation + if args.cert != nil { + certName := args.cert.Name() + dataName := args.Data.Name() + if len(args.OverrideName) > 0 { + dataName = args.OverrideName + } + + // Disallow empty names + if len(dataName) == 0 { + args.Callback(false, fmt.Errorf("data name is empty")) + return + } + + tc.ValidateProvidedCertHelper(args, dataName, certName) + return + } + + tc.ValidateFetchHelper(args, keyLocator) + return +} + +// Validate validates a Data packet using signature time flow func (tc *TrustConfig) ValidateWithSignatureTime(args TrustConfigValidateArgs) { if args.Data == nil { args.Callback(false, fmt.Errorf("data is nil")) @@ -504,84 +510,11 @@ func (tc *TrustConfig) ValidateWithSignatureTime(args TrustConfigValidateArgs) { return } - // Handle self-signed certificate (potential trust anchor). - if keyLocator.IsPrefix(args.Data.Name()) { - tc.handleSelfSignedCert(args, keyLocator) - return - } - - // Reset all cert fields, this is just for extra safety - // The code below might seem to have a lot of redundancy - this is intentional. - args.cert = nil - args.certSigCov = nil - args.certRaw = nil - args.certIsValid = false - args.crossSchemaIsValid = false - - // Check the validated memcache for the certificate - if cachedCert, ok := tc.certCache.Get(keyLocator); ok { - // The cache always checks the expiry of the cert - args.cert = cachedCert - args.certIsValid = true - - // Continue validation with cached cert - tc.ValidateWithSignatureTime(args) - return - } - - // Attach forwarding hint if needed - var fwHint []enc.Name = nil - if args.UseDataNameFwHint.GetOr(tc.UseDataNameFwHint) { - fwHint = []enc.Name{args.origDataName} - } - - // Cert not found, attempt to fetch from network - fetchCfg := &ndn.InterestConfig{ - CanBePrefix: true, - MustBeFresh: true, - ForwardingHint: fwHint, - } - triedLocal := false - var cb ndn.ExpressCallbackFunc - cb = func(res ndn.ExpressCallbackArgs) { - if res.Error == nil && res.Result != ndn.InterestResultData { - res.Error = fmt.Errorf("failed to fetch certificate (%s) with result: %s", keyLocator, res.Result) - } - - if res.Error != nil { - args.Callback(false, res.Error) - return // failed to fetch cert - } - - // Bail if not a certificate - if t, ok := res.Data.ContentType().Get(); !ok || t != ndn.ContentTypeKey { - if res.IsLocal && !triedLocal { - triedLocal = true - if res.Data != nil { - _ = tc.keychain.Store().Remove(res.Data.Name()) - } - args.Fetch(keyLocator, fetchCfg, cb) - return - } - args.Callback(false, fmt.Errorf("non-certificate in chain: %s", res.Data.Name())) - return - } - - // Fetched cert is fresh - log.Debug(tc, "Fetched certificate from network", "cert", res.Data.Name()) - - // Call again with the fetched cert - args.cert = res.Data - args.certSigCov = res.SigCovered - args.certRaw = utils.If(!res.IsLocal, res.RawData, nil) // prevent double insert - args.certIsValid = false - - // Continue validation with fetched cert - tc.ValidateWithSignatureTime(args) - } - args.Fetch(keyLocator, fetchCfg, cb) + tc.ValidateFetchHelper(args, keyLocator) + return } +// Validate validates a Data packet using the appropriate flow based on the UseSignatureTime flag func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { if args.UseSignatureTime.GetOr(false) { tc.ValidateWithSignatureTime(args) From 6c25c9aee2a34744755d288d177963d5b32dc083 Mon Sep 17 00:00:00 2001 From: Victor Chinnappan Date: Mon, 23 Mar 2026 13:26:06 -0700 Subject: [PATCH 08/23] Address PR comments --- std/ndn/spec_2022/spec.go | 2 +- std/security/certificate.go | 12 ------------ std/security/certificate_test.go | 2 +- std/security/trust_config.go | 17 +++++++++++------ std/security/trust_config_test.go | 12 ++++-------- 5 files changed, 17 insertions(+), 28 deletions(-) diff --git a/std/ndn/spec_2022/spec.go b/std/ndn/spec_2022/spec.go index 58cc6b92..5c517850 100644 --- a/std/ndn/spec_2022/spec.go +++ b/std/ndn/spec_2022/spec.go @@ -51,7 +51,7 @@ func (d *Data) SigNonce() []byte { return nil } -// (AI GENERATED DESCRIPTION): Returns the signature timestamp of the Data packet (currently unimplemented and returns nil). +// (AI GENERATED DESCRIPTION): Returns the signature timestamp of the Data packet if present, else returns nil. func (d *Data) SigTime() *time.Time { if d.SignatureInfo != nil && d.SignatureInfo.SignatureTime.IsSet() { return utils.IdPtr(time.UnixMilli(d.SignatureInfo.SignatureTime.Unwrap().Milliseconds())) diff --git a/std/security/certificate.go b/std/security/certificate.go index 8bebd1dc..330e33a0 100644 --- a/std/security/certificate.go +++ b/std/security/certificate.go @@ -212,15 +212,3 @@ func AppendCertList(content enc.Wire, names []enc.Name) (enc.Wire, error) { return EncodeCertList(newList) } - -// AppendCertList appends a certificate name list to a certificate list by creating a new certificate list -func FetchAndAppendCertList(content enc.Wire, names []enc.Name) (enc.Wire, error) { - certList, err := DecodeCertList(content) - if err != nil { - return nil, err - } - - newList := append(certList, names...) - - return EncodeCertList(newList) -} diff --git a/std/security/certificate_test.go b/std/security/certificate_test.go index 457e8833..b69ecc29 100644 --- a/std/security/certificate_test.go +++ b/std/security/certificate_test.go @@ -192,7 +192,7 @@ func TestEncodeDecodeCertList(t *testing.T) { require.Error(t, err) } -func TestApendCertList(t *testing.T) { +func TestAppendCertList(t *testing.T) { tu.SetT(t) n1 := tu.NoErr(enc.NameFromStr("/ndn/alice/KEY/aa/self/v=1")) n2 := tu.NoErr(enc.NameFromStr("/ndn/alice/KEY/bb/ndn/v=2")) diff --git a/std/security/trust_config.go b/std/security/trust_config.go index aaf8faef..3415a06f 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -145,7 +145,7 @@ type TrustConfigValidateArgs struct { UseSignatureTime optional.Optional[bool] } -func (tc *TrustConfig) ValidateProvidedCertHelper(args TrustConfigValidateArgs, dataName enc.Name, certName enc.Name) { +func (tc *TrustConfig) validateProvidedCertHelper(args TrustConfigValidateArgs, dataName enc.Name, certName enc.Name) { // Check schema if the key is allowed if args.crossSchemaIsValid { // continue @@ -170,6 +170,8 @@ func (tc *TrustConfig) ValidateProvidedCertHelper(args TrustConfigValidateArgs, IgnoreValidity: args.IgnoreValidity, cert: args.cert, depth: args.depth, + + UseSignatureTime: args.UseSignatureTime, }) return } else { @@ -250,7 +252,7 @@ func (tc *TrustConfig) ValidateProvidedCertHelper(args TrustConfigValidateArgs, return } -func (tc *TrustConfig) ValidateFetchHelper(args TrustConfigValidateArgs, keyLocator enc.Name) { +func (tc *TrustConfig) validateFetchHelper(args TrustConfigValidateArgs, keyLocator enc.Name) { // Handle self-signed certificate (potential trust anchor). if keyLocator.IsPrefix(args.Data.Name()) { tc.handleSelfSignedCert(args, keyLocator) @@ -399,11 +401,11 @@ func (tc *TrustConfig) ValidateWithExpiry(args TrustConfigValidateArgs) { return } - tc.ValidateProvidedCertHelper(args, dataName, certName) + tc.validateProvidedCertHelper(args, dataName, certName) return } - tc.ValidateFetchHelper(args, keyLocator) + tc.validateFetchHelper(args, keyLocator) return } @@ -504,13 +506,16 @@ func (tc *TrustConfig) ValidateWithSignatureTime(args TrustConfigValidateArgs) { args.Callback(false, fmt.Errorf("data was signed during an invalid period of certificate [signed after]: %s", args.cert.Name())) return } + } else { + args.Callback(false, fmt.Errorf("No Signature Time: data was not signed during the certificate validity period: %s", args.cert.Name())) + return } - tc.ValidateProvidedCertHelper(args, dataName, certName) + tc.validateProvidedCertHelper(args, dataName, certName) return } - tc.ValidateFetchHelper(args, keyLocator) + tc.validateFetchHelper(args, keyLocator) return } diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index 88c4f603..b4c7160a 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -1045,15 +1045,11 @@ func TestTrustConfigLvsInter(t *testing.T) { testTrustConfigInter(t, schemaInter) } -func testSignatureTimeValidationHelper(t *testing.T, schema *trust_schema.LvsSchema) { - return -} - func TestSignatureTimeValidationFlows(t *testing.T) { tu.SetT(t) // Use intra-domain schema with root-to-root signing support - // This schema is the same as TRUST_CONFIG_INTRA_LVS but allows roots to sign each other + // This schema is the same as TRUST_CONFIG_INTER_LVS but allows roots to sign each other schema, err := trust_schema.NewLvsSchema(TRUST_CONFIG_INTER_LVS) require.NoError(t, err) @@ -1303,9 +1299,9 @@ func TestSignatureTimeValidationFlows(t *testing.T) { }, { preprocess: func() { - // Remove the certlist and certificate from the network to simulate a fully expired chainz - network[expiredRootCertListData.Name().String()] = nil - network[expiredRootVerifCertData.Name().String()] = nil + // Remove the certlist and certificate from the network to simulate a fully expired chain + delete(network, expiredRootCertListData.Name().String()) + delete(network, expiredRootVerifCertData.Name().String()) }, name: "expired chain no certlist not ok", add: map[string]enc.Wire{listData.Name().String(): listWireEnc.Wire, preAnchorData.Name().String(): preAnchorWire}, From ea64346247a151f7c53de9bedb653fcf77740a80 Mon Sep 17 00:00:00 2001 From: Victor Chinnappan Date: Mon, 23 Mar 2026 13:27:51 -0700 Subject: [PATCH 09/23] Fix test name typo --- std/security/trust_config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index b4c7160a..5f327ff2 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -1285,7 +1285,7 @@ func TestSignatureTimeValidationFlows(t *testing.T) { preprocess: func() { time.Sleep(time.Second * 9) // Wait for expiration }, - name: "epxired chain certlist ok", + name: "expired chain certlist ok", add: map[string]enc.Wire{ expiredRootCertListData.Name().String(): expiredRootCertListWire.Wire, expiredRootVerifCertData.Name().String(): expiredRootVerifCertWire, From d9b5b499ccde2becd9551352a3db711ec355305f Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Tue, 28 Apr 2026 21:22:10 -0700 Subject: [PATCH 10/23] Clean up retired certificate flow --- std/security/trust_config.go | 464 ++++++++++++------------------ std/security/trust_config_test.go | 2 +- 2 files changed, 186 insertions(+), 280 deletions(-) diff --git a/std/security/trust_config.go b/std/security/trust_config.go index 3415a06f..24cb8673 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -145,114 +145,180 @@ type TrustConfigValidateArgs struct { UseSignatureTime optional.Optional[bool] } -func (tc *TrustConfig) validateProvidedCertHelper(args TrustConfigValidateArgs, dataName enc.Name, certName enc.Name) { - // Check schema if the key is allowed - if args.crossSchemaIsValid { - // continue - } else if tc.schema.Check(dataName, certName) { - // continue - } else if args.Data.CrossSchema() != nil { - tc.validateCrossSchema(TrustConfigValidateArgs{ - Data: args.Data, - DataSigCov: args.DataSigCov, - - Fetch: args.Fetch, - Callback: func(valid bool, err error) { - if valid && err == nil { - // Continue validation with cross schema - args.crossSchemaIsValid = true - tc.Validate(args) - } else { - args.Callback(valid, fmt.Errorf("cross schema: %w", err)) - } - }, - OverrideName: args.OverrideName, - IgnoreValidity: args.IgnoreValidity, - cert: args.cert, - depth: args.depth, - - UseSignatureTime: args.UseSignatureTime, - }) - return - } else { - args.Callback(false, fmt.Errorf("trust schema mismatch: %s signed by %s", dataName, certName)) +// Validate validates a Data packet using a fetch API. +func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { + if args.Data == nil { + args.Callback(false, fmt.Errorf("data is nil")) return } - // Validate signature on data - valid, err := signer.ValidateData(args.Data, args.DataSigCov, args.cert) - if !valid { - args.Callback(false, fmt.Errorf("signature is invalid")) + if len(args.DataSigCov) == 0 { + args.Callback(false, fmt.Errorf("data sig covered is nil")) return } - if err != nil { - args.Callback(false, fmt.Errorf("signature validate error: %w", err)) + + if args.origDataName == nil { + // Always use original name here, not the override name + args.origDataName = args.Data.Name() + } + + // Prevent infinite recursion for signer loops + if args.depth == 0 { + args.depth = 32 + } else if args.depth <= 1 { + args.Callback(false, fmt.Errorf("max depth reached")) return + } else { + args.depth-- } - // Check if the certificate was already validated. - // Since all roots are in cache, this breaks the recursion. - if args.certIsValid { - args.Callback(true, nil) + // Make sure the data is signed + signature := args.Data.Signature() + if signature == nil { + args.Callback(false, fmt.Errorf("signature is nil")) return } - // This should never happen, but just in case - if len(args.certSigCov) == 0 { - args.Callback(false, fmt.Errorf("cert sig covered is nil: %s", certName)) + // Bail if the data is a cert and is not fresh + if t, ok := args.Data.ContentType().Get(); ok && t == ndn.ContentTypeKey { + if !args.UseSignatureTime.GetOr(false) && CertIsExpired(args.Data) { + args.Callback(false, fmt.Errorf("certificate is expired: %s", args.Data.Name())) + return + } + } + + // Get the key locator + keyLocator := signature.KeyName() + if len(keyLocator) == 0 { + args.Callback(false, fmt.Errorf("key locator is nil")) return } - // Monkey patch the callback to store the cert in - // keychain and cache if the validation passes. - origCallback := args.Callback - args.Callback = func(valid bool, err error) { - if valid && err == nil { - // Cache is thread safe - tc.certCache.Put(args.cert) - - // Keychain is not thread safe for inserts - if len(args.certRaw) > 0 { - tc.mutex.Lock() - err := tc.keychain.InsertCert(args.certRaw.Join()) - tc.mutex.Unlock() - if err != nil { // broken keychain - log.Error(tc, "Failed to insert certificate to keychain", "name", args.cert.Name(), "err", err) - } - } + // If a certificate is provided, go directly to validation + if args.cert != nil { + certName := args.cert.Name() + dataName := args.Data.Name() + if len(args.OverrideName) > 0 { + dataName = args.OverrideName + } + + // Disallow empty names + if len(dataName) == 0 { + args.Callback(false, fmt.Errorf("data name is empty")) + return + } + + if args.UseSignatureTime.GetOr(false) && ValidateSigTime(args.Data, args.cert) { + args.Callback(false, fmt.Errorf("data not signed during validity period: %s", args.cert.Name())) + return + } + + // Check schema if the key is allowed + if args.crossSchemaIsValid { + // continue + } else if tc.schema.Check(dataName, certName) { + // continue + } else if args.Data.CrossSchema() != nil { + tc.validateCrossSchema(TrustConfigValidateArgs{ + Data: args.Data, + DataSigCov: args.DataSigCov, + + Fetch: args.Fetch, + Callback: func(valid bool, err error) { + if valid && err == nil { + // Continue validation with cross schema + args.crossSchemaIsValid = true + tc.Validate(args) + } else { + args.Callback(valid, fmt.Errorf("cross schema: %w", err)) + } + }, + OverrideName: args.OverrideName, + IgnoreValidity: args.IgnoreValidity, + cert: args.cert, + depth: args.depth, + + UseSignatureTime: args.UseSignatureTime, + }) + return } else { - log.Warn(tc, "Received invalid certificate", "name", args.cert.Name(), "err", err) + args.Callback(false, fmt.Errorf("trust schema mismatch: %s signed by %s", dataName, certName)) + return } - origCallback(valid, err) // continue bubbling up result - } + // Validate signature on data + valid, err := signer.ValidateData(args.Data, args.DataSigCov, args.cert) + if !valid { + args.Callback(false, fmt.Errorf("signature is invalid")) + return + } + if err != nil { + args.Callback(false, fmt.Errorf("signature validate error: %w", err)) + return + } - // Recursively validate the certificate - tc.Validate(TrustConfigValidateArgs{ - Data: args.cert, - DataSigCov: args.certSigCov, + // Check if the certificate was already validated. + // Since all roots are in cache, this breaks the recursion. + if args.certIsValid { + args.Callback(true, nil) + return + } - Fetch: args.Fetch, - Callback: args.Callback, - OverrideName: nil, - IgnoreValidity: args.IgnoreValidity, - origDataName: args.origDataName, + // This should never happen, but just in case + if len(args.certSigCov) == 0 { + args.Callback(false, fmt.Errorf("cert sig covered is nil: %s", certName)) + return + } + + // Monkey patch the callback to store the cert in + // keychain and cache if the validation passes. + origCallback := args.Callback + args.Callback = func(valid bool, err error) { + if valid && err == nil { + // Cache is thread safe + tc.certCache.Put(args.cert) + + // Keychain is not thread safe for inserts + if len(args.certRaw) > 0 { + tc.mutex.Lock() + err := tc.keychain.InsertCert(args.certRaw.Join()) + tc.mutex.Unlock() + if err != nil { // broken keychain + log.Error(tc, "Failed to insert certificate to keychain", "name", args.cert.Name(), "err", err) + } + } + } else { + log.Warn(tc, "Received invalid certificate", "name", args.cert.Name(), "err", err) + } - cert: nil, - certSigCov: nil, - certRaw: nil, - certIsValid: false, + origCallback(valid, err) // continue bubbling up result + } - crossSchemaIsValid: false, + // Recursively validate the certificate + tc.Validate(TrustConfigValidateArgs{ + Data: args.cert, + DataSigCov: args.certSigCov, - UseSignatureTime: args.UseSignatureTime, + Fetch: args.Fetch, + Callback: args.Callback, + OverrideName: nil, + IgnoreValidity: args.IgnoreValidity, + origDataName: args.origDataName, - depth: args.depth, - }) - return -} + cert: nil, + certSigCov: nil, + certRaw: nil, + certIsValid: false, + + crossSchemaIsValid: false, + + UseSignatureTime: args.UseSignatureTime, + + depth: args.depth, + }) + return + } -func (tc *TrustConfig) validateFetchHelper(args TrustConfigValidateArgs, keyLocator enc.Name) { // Handle self-signed certificate (potential trust anchor). if keyLocator.IsPrefix(args.Data.Name()) { tc.handleSelfSignedCert(args, keyLocator) @@ -317,7 +383,7 @@ func (tc *TrustConfig) validateFetchHelper(args TrustConfigValidateArgs, keyLoca } // Bail if the fetched cert is not fresh and not using signature time flow - if !args.UseSignatureTime.GetOr(false) && !args.IgnoreValidity.GetOr(false) && CertIsExpired(res.Data) { + if !args.UseSignatureTime.GetOr(false) && CertIsExpired(res.Data) { args.Callback(false, fmt.Errorf("certificate is expired: %s", res.Data.Name())) return } @@ -335,197 +401,6 @@ func (tc *TrustConfig) validateFetchHelper(args TrustConfigValidateArgs, keyLoca tc.Validate(args) } args.Fetch(keyLocator, fetchCfg, cb) - return -} - -// Validate validates a Data packet using a fetch API. -func (tc *TrustConfig) ValidateWithExpiry(args TrustConfigValidateArgs) { - if args.Data == nil { - args.Callback(false, fmt.Errorf("data is nil")) - return - } - - if len(args.DataSigCov) == 0 { - args.Callback(false, fmt.Errorf("data sig covered is nil")) - return - } - - if args.origDataName == nil { - // Always use original name here, not the override name - args.origDataName = args.Data.Name() - } - - // Prevent infinite recursion for signer loops - if args.depth == 0 { - args.depth = 32 - } else if args.depth <= 1 { - args.Callback(false, fmt.Errorf("max depth reached")) - return - } else { - args.depth-- - } - - // Make sure the data is signed - signature := args.Data.Signature() - if signature == nil { - args.Callback(false, fmt.Errorf("signature is nil")) - return - } - - // Bail if the data is a cert and is not fresh - if t, ok := args.Data.ContentType().Get(); ok && t == ndn.ContentTypeKey { - if !args.IgnoreValidity.GetOr(false) && CertIsExpired(args.Data) { - args.Callback(false, fmt.Errorf("certificate is expired: %s", args.Data.Name())) - return - } - } - - // Get the key locator - keyLocator := signature.KeyName() - if len(keyLocator) == 0 { - args.Callback(false, fmt.Errorf("key locator is nil")) - return - } - - // If a certificate is provided, go directly to validation - if args.cert != nil { - certName := args.cert.Name() - dataName := args.Data.Name() - if len(args.OverrideName) > 0 { - dataName = args.OverrideName - } - - // Disallow empty names - if len(dataName) == 0 { - args.Callback(false, fmt.Errorf("data name is empty")) - return - } - - tc.validateProvidedCertHelper(args, dataName, certName) - return - } - - tc.validateFetchHelper(args, keyLocator) - return -} - -// Validate validates a Data packet using signature time flow -func (tc *TrustConfig) ValidateWithSignatureTime(args TrustConfigValidateArgs) { - if args.Data == nil { - args.Callback(false, fmt.Errorf("data is nil")) - return - } - - if len(args.DataSigCov) == 0 { - args.Callback(false, fmt.Errorf("data sig covered is nil")) - return - } - - if args.origDataName == nil { - // Always use original name here, not the override name - args.origDataName = args.Data.Name() - } - - // Prevent infinite recursion for signer loops - if args.depth == 0 { - args.depth = 32 - } else if args.depth <= 1 { - args.Callback(false, fmt.Errorf("max depth reached")) - return - } else { - args.depth-- - } - - // Make sure the data is signed - signature := args.Data.Signature() - if signature == nil { - args.Callback(false, fmt.Errorf("signature is nil")) - return - } - - // Get the key locator - keyLocator := signature.KeyName() - if len(keyLocator) == 0 { - args.Callback(false, fmt.Errorf("key locator is nil")) - return - } - - // If the certificate is expired and is a trust anchor, explore the cert list - if t, ok := args.Data.ContentType().Get(); ok && t == ndn.ContentTypeKey { - if !args.IgnoreValidity.GetOr(false) && CertIsExpired(args.Data) { - if keyLocator.IsPrefix(args.Data.Name()) || tc.isTrustedAnchorKey(keyLocator) { - anchorKeyName, err := KeyNameFromLocator(keyLocator) - if err != nil { - args.Callback(false, fmt.Errorf("invalid anchor key locator: %w", err)) - return - } - - tc.exploreCertList(certListArgs{ - args: args, - anchorCert: args.Data, - anchorRaw: args.certRaw, - anchorKey: anchorKeyName, - visitedLists: map[string]struct{}{}, - visitedCerts: map[string]struct{}{}, - }, anchorKeyName.Append(enc.NewKeywordComponent("auth"))) - - return - } - } - } - - // If a certificate is provided, go directly to validation - if args.cert != nil { - certName := args.cert.Name() - dataName := args.Data.Name() - if len(args.OverrideName) > 0 { - dataName = args.OverrideName - } - - // Disallow empty names - if len(dataName) == 0 { - args.Callback(false, fmt.Errorf("data name is empty")) - return - } - - // Verify that the data was signed within certificate validity period - sigTime := args.Data.Signature().SigTime() - - if sigTime != nil { - if args.cert.Signature() == nil { - args.Callback(false, fmt.Errorf("data was signed during an invalid period of certificate [no signature]: %s", args.cert.Name())) - return - } - - cert_notBefore, cert_notAfter := args.cert.Signature().Validity() - if val, ok := cert_notBefore.Get(); !ok || sigTime.Before(val) { - args.Callback(false, fmt.Errorf("data was signed during an invalid period of certificate [signed before]: %s", args.cert.Name())) - return - } - if val, ok := cert_notAfter.Get(); !ok || sigTime.After(val) { - args.Callback(false, fmt.Errorf("data was signed during an invalid period of certificate [signed after]: %s", args.cert.Name())) - return - } - } else { - args.Callback(false, fmt.Errorf("No Signature Time: data was not signed during the certificate validity period: %s", args.cert.Name())) - return - } - - tc.validateProvidedCertHelper(args, dataName, certName) - return - } - - tc.validateFetchHelper(args, keyLocator) - return -} - -// Validate validates a Data packet using the appropriate flow based on the UseSignatureTime flag -func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { - if args.UseSignatureTime.GetOr(false) { - tc.ValidateWithSignatureTime(args) - } else { - tc.ValidateWithExpiry(args) - } } // (AI GENERATED DESCRIPTION): Validates the cross‑schema signed Data packet by parsing its embedded schema, checking its validity period, ensuring it authorizes the original certificate, and recursively validating the cross‑schema’s signature against the trust configuration. @@ -543,9 +418,17 @@ func (tc *TrustConfig) validateCrossSchema(args TrustConfigValidateArgs) { } // Check validity period of the cross schema - if !args.IgnoreValidity.GetOr(false) && CertIsExpired(crossData) { - args.Callback(false, fmt.Errorf("cross schema is expired: %s", crossData.Name())) - return + if args.UseSignatureTime.GetOr(false) { + // Cross schema was valid at signature time + if ValidateSigTime(args.Data, crossData) { + args.Callback(false, fmt.Errorf("cross schema signature time invalid: %s", crossData.Name())) + return + } + } else { + if CertIsExpired(crossData) { + args.Callback(false, fmt.Errorf("cross schema is expired: %s", crossData.Name())) + return + } } // Parse the cross schema content @@ -813,3 +696,26 @@ func (tc *TrustConfig) tryListedCerts(args certListArgs, names []enc.Name, idx i }) }) } + +// Returns true if signature time is within certificate validity period +func ValidateSigTime(data ndn.Data, cert ndn.Data) bool { + if cert.Signature() == nil { + return true + } + + sigTime := data.Signature().SigTime() + + if sigTime == nil { + return true + } + + notBefore, notAfter := cert.Signature().Validity() + if val, ok := notBefore.Get(); !ok || sigTime.Before(val) { + return true + } + if val, ok := notAfter.Get(); !ok || sigTime.After(val) { + return true + } + + return false +} diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index 5f327ff2..89c01180 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -1277,7 +1277,7 @@ func TestSignatureTimeValidationFlows(t *testing.T) { dataSigCov: dataSigCovInvalidSigTime, expectAnchor: true, expectData: false, - expectErrPart: "signed during an invalid period", + expectErrPart: "data not signed during validity period", }, // Validate flows after the expired root is replaced From 73fc3dead1e09035921454a00d98423b7fa3fa6e Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Tue, 28 Apr 2026 21:36:56 -0700 Subject: [PATCH 11/23] remove ignoreValidity --- std/object/client_consume.go | 8 ++------ std/object/client_trust.go | 1 - std/security/trust_config.go | 32 +++++++++++++------------------ std/security/trust_config_test.go | 14 ++------------ std/sync/svs_alo_data.go | 2 -- 5 files changed, 17 insertions(+), 40 deletions(-) diff --git a/std/object/client_consume.go b/std/object/client_consume.go index 998dec06..e40dc224 100644 --- a/std/object/client_consume.go +++ b/std/object/client_consume.go @@ -64,7 +64,7 @@ func (c *Client) consumeObject(state *ConsumeState) { // if metadata fetching is disabled, just attempt to fetch one segment // with the prefix, then get the versioned name from the segment. if state.args.NoMetadata { - c.fetchDataByPrefix(name, state.args.TryStore, state.args.IgnoreValidity.GetOr(false), + c.fetchDataByPrefix(name, state.args.TryStore, func(data ndn.Data, err error) { if err != nil { state.finalizeError(err) @@ -81,7 +81,7 @@ func (c *Client) consumeObject(state *ConsumeState) { } // fetch RDR metadata for this object - c.fetchMetadata(name, state.args.TryStore, state.args.IgnoreValidity.GetOr(false), + c.fetchMetadata(name, state.args.TryStore, func(meta *rdr.MetaData, err error) { if err != nil { state.finalizeError(err) @@ -107,7 +107,6 @@ func (c *Client) consumeObjectWithMeta(state *ConsumeState, meta *rdr.MetaData) func (c *Client) fetchMetadata( name enc.Name, tryStore bool, - ignoreValidity bool, callback func(meta *rdr.MetaData, err error), ) { log.Debug(c, "Fetching object metadata", "name", name) @@ -133,7 +132,6 @@ func (c *Client) fetchMetadata( c.ValidateExt(ndn.ValidateExtArgs{ Data: args.Data, SigCovered: args.SigCovered, - IgnoreValidity: optional.Some(ignoreValidity), Callback: func(valid bool, err error) { // validate with trust config if !valid { @@ -162,7 +160,6 @@ func (c *Client) fetchMetadata( func (c *Client) fetchDataByPrefix( name enc.Name, tryStore bool, - ignoreValidity bool, callback func(data ndn.Data, err error), ) { log.Debug(c, "Fetching data with prefix", "name", name) @@ -188,7 +185,6 @@ func (c *Client) fetchDataByPrefix( c.ValidateExt(ndn.ValidateExtArgs{ Data: args.Data, SigCovered: args.SigCovered, - IgnoreValidity: optional.Some(ignoreValidity), Callback: func(valid bool, err error) { if !valid { callback(nil, fmt.Errorf("%w: validate by prefix failed: %w", ndn.ErrSecurity, err)) diff --git a/std/object/client_trust.go b/std/object/client_trust.go index 8654ff48..eb648649 100644 --- a/std/object/client_trust.go +++ b/std/object/client_trust.go @@ -46,7 +46,6 @@ func (c *Client) ValidateExt(args ndn.ValidateExtArgs) { Callback: args.Callback, OverrideName: overrideName, UseDataNameFwHint: args.UseDataNameFwHint, - IgnoreValidity: args.IgnoreValidity, Fetch: func(name enc.Name, config *ndn.InterestConfig, callback ndn.ExpressCallbackFunc) { config.NextHopId = args.CertNextHop c.ExpressR(ndn.ExpressRArgs{ diff --git a/std/security/trust_config.go b/std/security/trust_config.go index 24cb8673..d480cad7 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -120,8 +120,6 @@ type TrustConfigValidateArgs struct { Callback func(bool, error) // OverrideName is an override for the data name (advanced usage). OverrideName enc.Name - // ignore ValidityPeriod in the valication chain - IgnoreValidity optional.Optional[bool] // origDataName is the original data name being verified. origDataName enc.Name @@ -233,10 +231,9 @@ func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { args.Callback(valid, fmt.Errorf("cross schema: %w", err)) } }, - OverrideName: args.OverrideName, - IgnoreValidity: args.IgnoreValidity, - cert: args.cert, - depth: args.depth, + OverrideName: args.OverrideName, + cert: args.cert, + depth: args.depth, UseSignatureTime: args.UseSignatureTime, }) @@ -299,11 +296,10 @@ func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { Data: args.cert, DataSigCov: args.certSigCov, - Fetch: args.Fetch, - Callback: args.Callback, - OverrideName: nil, - IgnoreValidity: args.IgnoreValidity, - origDataName: args.origDataName, + Fetch: args.Fetch, + Callback: args.Callback, + OverrideName: nil, + origDataName: args.origDataName, cert: nil, certSigCov: nil, @@ -454,10 +450,9 @@ func (tc *TrustConfig) validateCrossSchema(args TrustConfigValidateArgs) { Data: crossData, DataSigCov: crossDataSigCov, - Fetch: args.Fetch, - Callback: args.Callback, - OverrideName: dataName, // original data - IgnoreValidity: args.IgnoreValidity, + Fetch: args.Fetch, + Callback: args.Callback, + OverrideName: dataName, // original data depth: args.depth, @@ -688,9 +683,8 @@ func (tc *TrustConfig) tryListedCerts(args certListArgs, names []enc.Name, idx i } tc.tryListedCerts(args, names, idx+1) }, - IgnoreValidity: args.args.IgnoreValidity, - origDataName: args.args.origDataName, - depth: args.args.depth, + origDataName: args.args.origDataName, + depth: args.args.depth, UseSignatureTime: args.args.UseSignatureTime, }) @@ -708,7 +702,7 @@ func ValidateSigTime(data ndn.Data, cert ndn.Data) bool { if sigTime == nil { return true } - + notBefore, notAfter := cert.Signature().Validity() if val, ok := notBefore.Get(); !ok || sigTime.Before(val) { return true diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index 89c01180..3aed5f22 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -92,7 +92,6 @@ type ValidateSyncOptions struct { name string signer ndn.Signer crossSchema enc.Wire - ignoreValidity bool } // Helper to validate a packet synchronously @@ -114,13 +113,12 @@ func validateSync(opts ValidateSyncOptions) bool { ch <- valid close(ch) }, - IgnoreValidity: optional.Some(opts.ignoreValidity), }) return <-ch } // Helper to validate certificates -func validateCerts(certData ndn.Data, certDataSigCov enc.Wire, ignoreValidity bool) bool { +func validateCerts(certData ndn.Data, certDataSigCov enc.Wire) bool { ch := make(chan bool) go tcTestTrustConfig.Validate(sec.TrustConfigValidateArgs{ Data: certData, @@ -131,7 +129,6 @@ func validateCerts(certData ndn.Data, certDataSigCov enc.Wire, ignoreValidity bo ch <- valid close(ch) }, - IgnoreValidity: optional.Some(ignoreValidity), }) return <-ch } @@ -790,17 +787,10 @@ func testTrustConfigIntra(t *testing.T, schema ndn.TrustSchema) { tcTestT.Log(eveSigner.KeyLocator().String()) eveCertWire, eveCertData, eveSigCov := signCert(rootSigner, tu.NoErr(signer.MarshalSecret(eveSigner)), expiredOpts) network[eveCertData.Name().String()] = eveCertWire - require.False(t, validateCerts(eveCertData, eveSigCov, false)) - require.True(t, validateCerts(eveCertData, eveSigCov, true)) + require.False(t, validateCerts(eveCertData, eveSigCov)) require.False(t, validateSync(ValidateSyncOptions{ name: "/test/eve/data1", signer: eveSigner, - ignoreValidity: false, - })) - require.True(t, validateSync(ValidateSyncOptions{ - name: "/test/eve/data2", - signer: eveSigner, - ignoreValidity: true, })) } diff --git a/std/sync/svs_alo_data.go b/std/sync/svs_alo_data.go index d22035d0..1d61aa51 100644 --- a/std/sync/svs_alo_data.go +++ b/std/sync/svs_alo_data.go @@ -118,8 +118,6 @@ func (s *SvsALO) consumeObject(node enc.Name, boot uint64, seq uint64) { fetchName := s.objectName(node, boot, seq) s.client.ConsumeExt(ndn.ConsumeExtArgs{ Name: fetchName, - // inherit ignoreValidity from SVS, not correct but practical - IgnoreValidity: s.opts.Svs.IgnoreValidity, Callback: func(status ndn.ConsumeState) { s.mutex.Lock() defer s.mutex.Unlock() From d36b132f2a0b2f21d7960b506b5d97d6aee9b018 Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Tue, 28 Apr 2026 22:02:06 -0700 Subject: [PATCH 12/23] fix lint --- std/object/client_consume.go | 8 ++++---- std/security/trust_config_test.go | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/std/object/client_consume.go b/std/object/client_consume.go index e40dc224..06392b4c 100644 --- a/std/object/client_consume.go +++ b/std/object/client_consume.go @@ -130,8 +130,8 @@ func (c *Client) fetchMetadata( return } c.ValidateExt(ndn.ValidateExtArgs{ - Data: args.Data, - SigCovered: args.SigCovered, + Data: args.Data, + SigCovered: args.SigCovered, Callback: func(valid bool, err error) { // validate with trust config if !valid { @@ -183,8 +183,8 @@ func (c *Client) fetchDataByPrefix( return } c.ValidateExt(ndn.ValidateExtArgs{ - Data: args.Data, - SigCovered: args.SigCovered, + Data: args.Data, + SigCovered: args.SigCovered, Callback: func(valid bool, err error) { if !valid { callback(nil, fmt.Errorf("%w: validate by prefix failed: %w", ndn.ErrSecurity, err)) diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index 3aed5f22..76934797 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -89,9 +89,9 @@ var tcTestKeyChain ndn.KeyChain = nil var tcTestFetchCount int = 0 type ValidateSyncOptions struct { - name string - signer ndn.Signer - crossSchema enc.Wire + name string + signer ndn.Signer + crossSchema enc.Wire } // Helper to validate a packet synchronously @@ -789,8 +789,8 @@ func testTrustConfigIntra(t *testing.T, schema ndn.TrustSchema) { network[eveCertData.Name().String()] = eveCertWire require.False(t, validateCerts(eveCertData, eveSigCov)) require.False(t, validateSync(ValidateSyncOptions{ - name: "/test/eve/data1", - signer: eveSigner, + name: "/test/eve/data1", + signer: eveSigner, })) } From 45d2122c79acc26ba6d6b53644fea8cb2cb02296 Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Wed, 29 Apr 2026 10:56:29 -0700 Subject: [PATCH 13/23] add signature time not in cross schema validity period test --- std/security/trust_config_test.go | 74 ++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index 76934797..e01beb08 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -1145,22 +1145,22 @@ func TestSignatureTimeValidationFlows(t *testing.T) { }, listContent, anchorSigner)) listData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(listWireEnc.Wire)) - userSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/app/user/alice")))) - userKeyData := tu.NoErr(signer.MarshalSecretToData(userSigner)) - userCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + aliceSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/app/user/alice")))) + aliceKeyData := tu.NoErr(signer.MarshalSecretToData(aliceSigner)) + aliceCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ Signer: anchorSigner, - Data: userKeyData, + Data: aliceKeyData, IssuerId: enc.NewGenericComponent("app"), NotBefore: nb, NotAfter: na, })) - userCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(userCertWire)) + aliceCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(aliceCertWire)) // User data payload := enc.Wire{[]byte{0x01}} dataWire := tu.NoErr(spec.Spec{}.MakeData(n("/app/user/alice/data"), &ndn.DataConfig{ Freshness: optional.Some(time.Minute), - }, payload, userSigner)) + }, payload, aliceSigner)) dataPkt, dataSigCov, _ := spec.Spec{}.ReadData(enc.NewWireView(dataWire.Wire)) // User data with invalid signature time @@ -1168,18 +1168,51 @@ func TestSignatureTimeValidationFlows(t *testing.T) { dataWireInvalidSigTime := tu.NoErr(spec.Spec{}.MakeData(n("/app/user/alice/data"), &ndn.DataConfig{ Freshness: optional.Some(time.Minute), SigTime: sigTimeBeforeUserCert, - }, payload, userSigner)) + }, payload, aliceSigner)) dataPktInvalidSigTime, dataSigCovInvalidSigTime, _ := spec.Spec{}.ReadData(enc.NewWireView(dataWireInvalidSigTime.Wire)) + bobSigner := tu.NoErr(signer.KeygenEd25519(sec.MakeKeyName(n("/app/user/bob")))) + bobKeyData := tu.NoErr(signer.MarshalSecretToData(bobSigner)) + bobCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{ + Signer: anchorSigner, + Data: bobKeyData, + IssuerId: enc.NewGenericComponent("app"), + NotBefore: nb, + NotAfter: na, + })) + bobCertData, _, _ := spec.Spec{}.ReadData(enc.NewWireView(bobCertWire)) + network[bobCertData.Name().String()] = bobCertWire + + abInvite, err := trust_schema.SignCrossSchema(trust_schema.SignCrossSchemaArgs{ + Name: sname("/app/user/alice/32=INVITE/app/user/bob/v=1"), + Signer: aliceSigner, + Content: trust_schema.CrossSchemaContent{ + SimpleSchemaRules: []*trust_schema.SimpleSchemaRule{{ + NamePrefix: sname("/app/user/alice/app/bob"), + KeyLocator: &spec.KeyLocator{Name: sname("/app/bob/KEY")}, // any key from bob + }}, + }, + NotBefore: time.Now().Add(time.Second * 7), + NotAfter: time.Now().Add(time.Second * 12), + }) + require.NoError(t, err) + + dataWireInvalidSigTimeCrossSchema := tu.NoErr(spec.Spec{}.MakeData(n("/app/user/alice/app/bob/data"), &ndn.DataConfig{ + Freshness: optional.Some(time.Minute), + SigTime: optional.Some(time.Duration(now.UnixMilli()) * time.Millisecond), + CrossSchema: abInvite, + }, payload, bobSigner)) + dataPktInvalidSigTimeCrossSchema, dataSigCovInvalidSigTimeCrossSchema, _ := spec.Spec{}.ReadData(enc.NewWireView(dataWireInvalidSigTimeCrossSchema.Wire)) + require.True(t, schema.Check(anchorCertData.Name(), anchorCertData.Name())) require.True(t, schema.Check(preAnchorData.Name(), ownerCertData.Name())) require.True(t, schema.Check(listData.Name(), anchorCertData.Name())) - require.True(t, schema.Check(userCertData.Name(), anchorCertData.Name())) - require.True(t, schema.Check(dataPkt.Name(), userCertData.Name())) - require.True(t, schema.Check(dataPktInvalidSigTime.Name(), userCertData.Name())) + require.True(t, schema.Check(aliceCertData.Name(), anchorCertData.Name())) + require.True(t, schema.Check(dataPkt.Name(), aliceCertData.Name())) + require.True(t, schema.Check(dataPktInvalidSigTime.Name(), aliceCertData.Name())) network[anchorCertData.Name().String()] = anchorCertWire - network[userCertData.Name().String()] = userCertWire + network[aliceCertData.Name().String()] = aliceCertWire network[ownerCertData.Name().String()] = ownerCertWire type stage struct { @@ -1269,6 +1302,25 @@ func TestSignatureTimeValidationFlows(t *testing.T) { expectData: false, expectErrPart: "data not signed during validity period", }, + { + preprocess: func() { + return + }, + name: "data signed when cross schema invalid", + add: map[string]enc.Wire{ + expiredRootCertListData.Name().String(): expiredRootCertListWire.Wire, + expiredRootVerifCertData.Name().String(): expiredRootVerifCertWire, + listData.Name().String(): listWireEnc.Wire, + preAnchorData.Name().String(): preAnchorWire, + }, + kcInsert: []enc.Wire{expiredRootCertWire, newRootCertWire}, + trustAnchors: []enc.Name{expiredRootCertData.Name(), newRootCertData.Name()}, + data: dataPktInvalidSigTimeCrossSchema, + dataSigCov: dataSigCovInvalidSigTimeCrossSchema, + expectAnchor: true, + expectData: false, + expectErrPart: "cross schema signature time invalid", + }, // Validate flows after the expired root is replaced { From eef8cbcdced05ec2b658f4a68e3d84779830243c Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Wed, 29 Apr 2026 10:57:04 -0700 Subject: [PATCH 14/23] fix lint --- std/security/trust_config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index e01beb08..3abee828 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -1203,7 +1203,7 @@ func TestSignatureTimeValidationFlows(t *testing.T) { CrossSchema: abInvite, }, payload, bobSigner)) dataPktInvalidSigTimeCrossSchema, dataSigCovInvalidSigTimeCrossSchema, _ := spec.Spec{}.ReadData(enc.NewWireView(dataWireInvalidSigTimeCrossSchema.Wire)) - + require.True(t, schema.Check(anchorCertData.Name(), anchorCertData.Name())) require.True(t, schema.Check(preAnchorData.Name(), ownerCertData.Name())) require.True(t, schema.Check(listData.Name(), anchorCertData.Name())) From a167dc653caf2fb9fb183091f20e5c8d9b4cfcc8 Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Thu, 30 Apr 2026 08:02:46 -0700 Subject: [PATCH 15/23] validateSigTime returns true when valid --- std/security/trust_config.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/std/security/trust_config.go b/std/security/trust_config.go index d480cad7..d1876ad4 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -206,7 +206,7 @@ func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { return } - if args.UseSignatureTime.GetOr(false) && ValidateSigTime(args.Data, args.cert) { + if args.UseSignatureTime.GetOr(false) && !ValidateSigTime(args.Data, args.cert) { args.Callback(false, fmt.Errorf("data not signed during validity period: %s", args.cert.Name())) return } @@ -416,7 +416,7 @@ func (tc *TrustConfig) validateCrossSchema(args TrustConfigValidateArgs) { // Check validity period of the cross schema if args.UseSignatureTime.GetOr(false) { // Cross schema was valid at signature time - if ValidateSigTime(args.Data, crossData) { + if !ValidateSigTime(args.Data, crossData) { args.Callback(false, fmt.Errorf("cross schema signature time invalid: %s", crossData.Name())) return } @@ -694,22 +694,22 @@ func (tc *TrustConfig) tryListedCerts(args certListArgs, names []enc.Name, idx i // Returns true if signature time is within certificate validity period func ValidateSigTime(data ndn.Data, cert ndn.Data) bool { if cert.Signature() == nil { - return true + return false } sigTime := data.Signature().SigTime() if sigTime == nil { - return true + return false } notBefore, notAfter := cert.Signature().Validity() if val, ok := notBefore.Get(); !ok || sigTime.Before(val) { - return true + return false } if val, ok := notAfter.Get(); !ok || sigTime.After(val) { - return true + return false } - return false + return true } From 742798a41bac819f6d6bcbe79f06f77512f127fd Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Thu, 7 May 2026 11:52:37 -0700 Subject: [PATCH 16/23] remove IgnoreValidity and introduce UseSignatureTime in SVS and repo --- repo/config.go | 2 -- repo/repo_mgmt.go | 6 +++--- repo/repo_svs.go | 10 +++++----- std/ndn/client.go | 8 ++++---- std/ndn/engine.go | 2 ++ std/object/client_consume.go | 16 ++++++++++------ std/object/client_consume_seg.go | 6 +++--- std/object/client_trust.go | 1 + std/security/keychain/keychain_state.go | 5 ----- std/sync/snapshot_node_history.go | 14 +++++++------- std/sync/snapshot_node_latest.go | 8 ++++---- std/sync/svs.go | 10 +++++----- std/sync/svs_alo.go | 8 -------- std/sync/svs_alo_data.go | 3 ++- 14 files changed, 46 insertions(+), 53 deletions(-) diff --git a/repo/config.go b/repo/config.go index 5da2b4d0..f87deeaa 100644 --- a/repo/config.go +++ b/repo/config.go @@ -17,8 +17,6 @@ type Config struct { KeyChainUri string `json:"keychain"` // List of trust anchor full names. TrustAnchors []string `json:"trust_anchors"` - // IgnoreValidity skips validity period checks when fetching remote data (e.g. SVS snapshots). - IgnoreValidity bool `json:"ignore_validity"` // NameN is the parsed name of the repo service. NameN enc.Name diff --git a/repo/repo_mgmt.go b/repo/repo_mgmt.go index db453111..d58c6f85 100644 --- a/repo/repo_mgmt.go +++ b/repo/repo_mgmt.go @@ -197,9 +197,9 @@ func (r *Repo) fetchSecurityConfig(name enc.Name) (*tlv.SecurityConfigObject, er // Repo should validate this as normal command r.client.ConsumeExt(ndn.ConsumeExtArgs{ - Name: name, - TryStore: true, - IgnoreValidity: optional.Some(r.config.IgnoreValidity), + Name: name, + TryStore: true, + UseSignatureTime: optional.Some(true), Callback: func(state ndn.ConsumeState) { wire = append(wire, state.Content()...) if state.Error() != nil { diff --git a/repo/repo_svs.go b/repo/repo_svs.go index 7bd5652c..00a1ae40 100644 --- a/repo/repo_svs.go +++ b/repo/repo_svs.go @@ -54,10 +54,10 @@ func (r *RepoSvs) Start() (err error) { } snapshot = &ndn_sync.SnapshotNodeHistory{ - Client: r.client, - Threshold: r.cmd.HistorySnapshot.Threshold, - IsRepo: true, - IgnoreValidity: optional.Some(r.config.IgnoreValidity), + Client: r.client, + Threshold: r.cmd.HistorySnapshot.Threshold, + IsRepo: true, + UseSignatureTime: optional.Some(true), } } @@ -78,7 +78,7 @@ func (r *RepoSvs) Start() (err error) { SuppressionPeriod: 500 * time.Millisecond, PeriodicTimeout: 365 * 24 * time.Hour, // basically never Passive: true, - IgnoreValidity: optional.Some(r.config.IgnoreValidity), + UseSignatureTime: optional.Some(true), }, Snapshot: snapshot, MulticastPrefix: multicastPrefix, diff --git a/std/ndn/client.go b/std/ndn/client.go index 9a5ae1bb..b655dd16 100644 --- a/std/ndn/client.go +++ b/std/ndn/client.go @@ -129,8 +129,8 @@ type ConsumeExtArgs struct { OnProgress func(status ConsumeState) // NoMetadata disables fetching RDR metadata (advanced usage). NoMetadata bool - // IgnoreValidity ignores validity period in the validation chain - IgnoreValidity optional.Optional[bool] + // UseSignatureTime checks validity period using signature time + UseSignatureTime optional.Optional[bool] } // ExpressRArgs are the arguments for the express retry API. @@ -167,8 +167,8 @@ type ValidateExtArgs struct { CertNextHop optional.Optional[uint64] // UseDataNameFwHint overrides trust config option. UseDataNameFwHint optional.Optional[bool] - // IgnoreValidity ignores validity period in the validation chain. - IgnoreValidity optional.Optional[bool] + // UseSignatureTime checks validity with signature time + UseSignatureTime optional.Optional[bool] } // Announcement are the arguments for the announce prefix API. diff --git a/std/ndn/engine.go b/std/ndn/engine.go index 68778b9b..9ae6174e 100644 --- a/std/ndn/engine.go +++ b/std/ndn/engine.go @@ -89,6 +89,8 @@ type ExpressCallbackArgs struct { // IsLocal indicates if a local copy of the Data was found. // e.g. returned by ExpressR when used with TryStore. IsLocal bool + + UseSignatureTime optional.Optional[bool] } // InterestHandler represents the callback function for an Interest handler. diff --git a/std/object/client_consume.go b/std/object/client_consume.go index 06392b4c..d2de1a6b 100644 --- a/std/object/client_consume.go +++ b/std/object/client_consume.go @@ -64,7 +64,7 @@ func (c *Client) consumeObject(state *ConsumeState) { // if metadata fetching is disabled, just attempt to fetch one segment // with the prefix, then get the versioned name from the segment. if state.args.NoMetadata { - c.fetchDataByPrefix(name, state.args.TryStore, + c.fetchDataByPrefix(name, state.args.TryStore, state.args.UseSignatureTime.GetOr(false), func(data ndn.Data, err error) { if err != nil { state.finalizeError(err) @@ -81,7 +81,7 @@ func (c *Client) consumeObject(state *ConsumeState) { } // fetch RDR metadata for this object - c.fetchMetadata(name, state.args.TryStore, + c.fetchMetadata(name, state.args.TryStore, state.args.UseSignatureTime.GetOr(false), func(meta *rdr.MetaData, err error) { if err != nil { state.finalizeError(err) @@ -107,6 +107,7 @@ func (c *Client) consumeObjectWithMeta(state *ConsumeState, meta *rdr.MetaData) func (c *Client) fetchMetadata( name enc.Name, tryStore bool, + useSignatureTime bool, callback func(meta *rdr.MetaData, err error), ) { log.Debug(c, "Fetching object metadata", "name", name) @@ -130,8 +131,9 @@ func (c *Client) fetchMetadata( return } c.ValidateExt(ndn.ValidateExtArgs{ - Data: args.Data, - SigCovered: args.SigCovered, + Data: args.Data, + SigCovered: args.SigCovered, + UseSignatureTime: optional.Some(useSignatureTime), Callback: func(valid bool, err error) { // validate with trust config if !valid { @@ -160,6 +162,7 @@ func (c *Client) fetchMetadata( func (c *Client) fetchDataByPrefix( name enc.Name, tryStore bool, + useSignatureTime bool, callback func(data ndn.Data, err error), ) { log.Debug(c, "Fetching data with prefix", "name", name) @@ -183,8 +186,9 @@ func (c *Client) fetchDataByPrefix( return } c.ValidateExt(ndn.ValidateExtArgs{ - Data: args.Data, - SigCovered: args.SigCovered, + Data: args.Data, + SigCovered: args.SigCovered, + UseSignatureTime: optional.Some(useSignatureTime), Callback: func(valid bool, err error) { if !valid { callback(nil, fmt.Errorf("%w: validate by prefix failed: %w", ndn.ErrSecurity, err)) diff --git a/std/object/client_consume_seg.go b/std/object/client_consume_seg.go index 972f171c..5c547fa2 100644 --- a/std/object/client_consume_seg.go +++ b/std/object/client_consume_seg.go @@ -286,9 +286,9 @@ func (s *rrSegFetcher) handleResult(args ndn.ExpressCallbackArgs, state *Consume // The notable exception here is when there is a timeout, which has a separate goroutine. func (s *rrSegFetcher) handleData(args ndn.ExpressCallbackArgs, state *ConsumeState) { s.client.ValidateExt(ndn.ValidateExtArgs{ - Data: args.Data, - SigCovered: args.SigCovered, - IgnoreValidity: state.args.IgnoreValidity, + Data: args.Data, + SigCovered: args.SigCovered, + UseSignatureTime: state.args.UseSignatureTime, Callback: func(valid bool, err error) { if !valid { state.finalizeError(fmt.Errorf("%w: validate seg failed: %w", ndn.ErrSecurity, err)) diff --git a/std/object/client_trust.go b/std/object/client_trust.go index eb648649..b3ec308f 100644 --- a/std/object/client_trust.go +++ b/std/object/client_trust.go @@ -46,6 +46,7 @@ func (c *Client) ValidateExt(args ndn.ValidateExtArgs) { Callback: args.Callback, OverrideName: overrideName, UseDataNameFwHint: args.UseDataNameFwHint, + UseSignatureTime: args.UseSignatureTime, Fetch: func(name enc.Name, config *ndn.InterestConfig, callback ndn.ExpressCallbackFunc) { config.NextHopId = args.CertNextHop c.ExpressR(ndn.ExpressRArgs{ diff --git a/std/security/keychain/keychain_state.go b/std/security/keychain/keychain_state.go index c517be47..f8495d3b 100644 --- a/std/security/keychain/keychain_state.go +++ b/std/security/keychain/keychain_state.go @@ -119,11 +119,6 @@ func (kc *keyChainState) insertCert(wire []byte) error { return ndn.ErrInvalidValue{Item: "certificate name"} } - // Check if certificate is valid - if sec.CertIsExpired(data) { - return ndn.ErrInvalidValue{Item: "certificate expiry"} - } - // Check if certificate already exists for _, existing := range kc.certNames { if existing.Equal(name) { diff --git a/std/sync/snapshot_node_history.go b/std/sync/snapshot_node_history.go index 0032358a..023e2d8b 100644 --- a/std/sync/snapshot_node_history.go +++ b/std/sync/snapshot_node_history.go @@ -44,8 +44,8 @@ type SnapshotNodeHistory struct { // In Repo mode, all snapshots are fetched automtically for persistence. IsRepo bool - // IgnoreValidity ignores validity period in the validation chain - IgnoreValidity optional.Optional[bool] + // UseSignatureTime checks validity period using signature time + UseSignatureTime optional.Optional[bool] // repoKnown is the known snapshot sequence number. repoKnown SvMap[uint64] @@ -162,8 +162,8 @@ func (s *SnapshotNodeHistory) idxName(node enc.Name, boot uint64) enc.Name { // fetchIndex fetches the latest index for a remote node. func (s *SnapshotNodeHistory) fetchIndex(node enc.Name, boot uint64, known uint64) { s.Client.ConsumeExt(ndn.ConsumeExtArgs{ - Name: s.idxName(node, boot), - IgnoreValidity: s.IgnoreValidity, + Name: s.idxName(node, boot), + UseSignatureTime: s.UseSignatureTime, Callback: func(cstate ndn.ConsumeState) { go s.handleIndex(node, boot, known, cstate) }, @@ -210,9 +210,9 @@ func (s *SnapshotNodeHistory) handleIndex(node enc.Name, boot uint64, known uint snapName := s.snapName(node, boot).WithVersion(seqNo) s.Client.ConsumeExt(ndn.ConsumeExtArgs{ - Name: snapName, - IgnoreValidity: s.IgnoreValidity, - Callback: func(cstate ndn.ConsumeState) { snapC <- cstate }, + Name: snapName, + UseSignatureTime: s.UseSignatureTime, + Callback: func(cstate ndn.ConsumeState) { snapC <- cstate }, }) scstate := <-snapC diff --git a/std/sync/snapshot_node_latest.go b/std/sync/snapshot_node_latest.go index 569fd254..294e5d1a 100644 --- a/std/sync/snapshot_node_latest.go +++ b/std/sync/snapshot_node_latest.go @@ -33,8 +33,8 @@ type SnapshotNodeLatest struct { SnapMe func(enc.Name) (enc.Wire, error) // Threshold is the number of updates before a snapshot is taken. Threshold uint64 - // IgnoreValidity ignores validity period in the validation chain - IgnoreValidity optional.Optional[bool] + // UseSignatureTime checks validity period using signature time + UseSignatureTime optional.Optional[bool] // pss is the struct from the svs layer. pss snapPsState @@ -119,8 +119,8 @@ func (s *SnapshotNodeLatest) snapName(node enc.Name, boot uint64) enc.Name { func (s *SnapshotNodeLatest) fetchSnap(node enc.Name, boot uint64) { // Discover the latest snapshot s.Client.ConsumeExt(ndn.ConsumeExtArgs{ - Name: s.snapName(node, boot), - IgnoreValidity: s.IgnoreValidity, + Name: s.snapName(node, boot), + UseSignatureTime: s.UseSignatureTime, Callback: func(cstate ndn.ConsumeState) { if cstate.Error() != nil { // Do not try too fast in case NFD returns NACK diff --git a/std/sync/svs.go b/std/sync/svs.go index 53cf7402..c7924c73 100644 --- a/std/sync/svs.go +++ b/std/sync/svs.go @@ -69,8 +69,8 @@ type SvSyncOpts struct { // Passive mode does not send sign Sync Interests Passive bool - // IgnoreValidity ignores validity period in the validation chain - IgnoreValidity optional.Optional[bool] + // UseSignatureTime checks validity period using signature time + UseSignatureTime optional.Optional[bool] } type SvSyncUpdate struct { @@ -524,9 +524,9 @@ func (s *SvSync) onSyncData(dataWire enc.Wire) { // Validate signature s.o.Client.ValidateExt(ndn.ValidateExtArgs{ - Data: data, - SigCovered: sigCov, - IgnoreValidity: s.o.IgnoreValidity, + Data: data, + SigCovered: sigCov, + UseSignatureTime: s.o.UseSignatureTime, Callback: func(valid bool, err error) { if !valid || err != nil { log.Warn(s, "SvSync failed to validate signature", "name", data.Name(), "valid", valid, "err", err) diff --git a/std/sync/svs_alo.go b/std/sync/svs_alo.go index 384a14eb..2b67ee8b 100644 --- a/std/sync/svs_alo.go +++ b/std/sync/svs_alo.go @@ -140,14 +140,6 @@ func NewSvsALO(opts SvsAloOpts) (*SvsALO, error) { }, s.state) } - // Overeide IgnoreValidity from SVS (incorect but practical) - if latest, ok := s.opts.Snapshot.(*SnapshotNodeLatest); ok { - latest.IgnoreValidity = s.opts.Svs.IgnoreValidity - } - if history, ok := s.opts.Snapshot.(*SnapshotNodeHistory); ok { - history.IgnoreValidity = s.opts.Svs.IgnoreValidity - } - return s, nil } diff --git a/std/sync/svs_alo_data.go b/std/sync/svs_alo_data.go index 1d61aa51..5a52e15d 100644 --- a/std/sync/svs_alo_data.go +++ b/std/sync/svs_alo_data.go @@ -117,7 +117,8 @@ func (s *SvsALO) consumeCheck(node enc.Name) { func (s *SvsALO) consumeObject(node enc.Name, boot uint64, seq uint64) { fetchName := s.objectName(node, boot, seq) s.client.ConsumeExt(ndn.ConsumeExtArgs{ - Name: fetchName, + Name: fetchName, + UseSignatureTime: s.opts.Svs.UseSignatureTime, Callback: func(status ndn.ConsumeState) { s.mutex.Lock() defer s.mutex.Unlock() From cb6b120096f2b834fdbba57a9752fc24e26a2769 Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Thu, 7 May 2026 11:54:13 -0700 Subject: [PATCH 17/23] add signature time exception for testbed and ucla root certs --- std/security/trust_config.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/std/security/trust_config.go b/std/security/trust_config.go index d1876ad4..0a6c715a 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -697,6 +697,17 @@ func ValidateSigTime(data ndn.Data, cert ndn.Data) bool { return false } + exceptionNameStrings := []string{ + "/ndn/edu/ucla/KEY/%2F%0D%23x%03%E5%FFC/NA/v=1770689778343", + "/ndn/KEY/%27%C4%B2%2A%9F%7B%81%27/ndn/v=1651246789556", + } + for _, nameString := range exceptionNameStrings { + name, _ := enc.NameFromStr(nameString) + if cert.Name().Equal(name) { + return true + } + } + sigTime := data.Signature().SigTime() if sigTime == nil { From dd86af77e763c61ce2a5b94dc6d660ae81289257 Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Thu, 7 May 2026 12:02:42 -0700 Subject: [PATCH 18/23] check expiry for excepted certs --- std/security/trust_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/security/trust_config.go b/std/security/trust_config.go index 0a6c715a..05f91268 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -704,7 +704,7 @@ func ValidateSigTime(data ndn.Data, cert ndn.Data) bool { for _, nameString := range exceptionNameStrings { name, _ := enc.NameFromStr(nameString) if cert.Name().Equal(name) { - return true + return CertIsExpired(cert) } } From e05979c4d5237b932846c57ef47d63f746840384 Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Thu, 7 May 2026 14:44:15 -0700 Subject: [PATCH 19/23] Revert "check expiry for excepted certs" This reverts commit dd86af77e763c61ce2a5b94dc6d660ae81289257. --- std/security/trust_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/security/trust_config.go b/std/security/trust_config.go index 05f91268..0a6c715a 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -704,7 +704,7 @@ func ValidateSigTime(data ndn.Data, cert ndn.Data) bool { for _, nameString := range exceptionNameStrings { name, _ := enc.NameFromStr(nameString) if cert.Name().Equal(name) { - return CertIsExpired(cert) + return true } } From 31b2217543c104ad0b19338a91ab25deab86f106 Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Fri, 8 May 2026 10:07:06 -0700 Subject: [PATCH 20/23] readd IgnoreValidity --- repo/repo_mgmt.go | 1 + repo/repo_svs.go | 2 ++ std/ndn/client.go | 4 +++ std/ndn/engine.go | 4 ++- std/object/client_consume.go | 8 +++-- std/object/client_consume_seg.go | 1 + std/object/client_trust.go | 1 + std/security/trust_config.go | 58 ++++++++++++++++++------------- std/security/trust_config_test.go | 24 +++++++++---- std/sync/snapshot_node_history.go | 4 +++ std/sync/snapshot_node_latest.go | 3 ++ std/sync/svs.go | 3 ++ std/sync/svs_alo.go | 8 +++++ std/sync/svs_alo_data.go | 1 + 14 files changed, 87 insertions(+), 35 deletions(-) diff --git a/repo/repo_mgmt.go b/repo/repo_mgmt.go index d58c6f85..625790cc 100644 --- a/repo/repo_mgmt.go +++ b/repo/repo_mgmt.go @@ -200,6 +200,7 @@ func (r *Repo) fetchSecurityConfig(name enc.Name) (*tlv.SecurityConfigObject, er Name: name, TryStore: true, UseSignatureTime: optional.Some(true), + IgnoreValidity: optional.Some(r.config.IgnoreValidity), Callback: func(state ndn.ConsumeState) { wire = append(wire, state.Content()...) if state.Error() != nil { diff --git a/repo/repo_svs.go b/repo/repo_svs.go index 00a1ae40..8ecd6a78 100644 --- a/repo/repo_svs.go +++ b/repo/repo_svs.go @@ -58,6 +58,7 @@ func (r *RepoSvs) Start() (err error) { Threshold: r.cmd.HistorySnapshot.Threshold, IsRepo: true, UseSignatureTime: optional.Some(true), + IgnoreValidity: optional.Some(r.config.IgnoreValidity), } } @@ -79,6 +80,7 @@ func (r *RepoSvs) Start() (err error) { PeriodicTimeout: 365 * 24 * time.Hour, // basically never Passive: true, UseSignatureTime: optional.Some(true), + IgnoreValidity: optional.Some(r.config.IgnoreValidity), }, Snapshot: snapshot, MulticastPrefix: multicastPrefix, diff --git a/std/ndn/client.go b/std/ndn/client.go index b655dd16..9a509e02 100644 --- a/std/ndn/client.go +++ b/std/ndn/client.go @@ -131,6 +131,8 @@ type ConsumeExtArgs struct { NoMetadata bool // UseSignatureTime checks validity period using signature time UseSignatureTime optional.Optional[bool] + // IgnoreValidity ignores validity period in the validation chain + IgnoreValidity optional.Optional[bool] } // ExpressRArgs are the arguments for the express retry API. @@ -169,6 +171,8 @@ type ValidateExtArgs struct { UseDataNameFwHint optional.Optional[bool] // UseSignatureTime checks validity with signature time UseSignatureTime optional.Optional[bool] + // IgnoreValidity ignores validity period in the validation chain + IgnoreValidity optional.Optional[bool] } // Announcement are the arguments for the announce prefix API. diff --git a/std/ndn/engine.go b/std/ndn/engine.go index 9ae6174e..ec242475 100644 --- a/std/ndn/engine.go +++ b/std/ndn/engine.go @@ -89,8 +89,10 @@ type ExpressCallbackArgs struct { // IsLocal indicates if a local copy of the Data was found. // e.g. returned by ExpressR when used with TryStore. IsLocal bool - + // UseSignatureTime checks validity with signature time UseSignatureTime optional.Optional[bool] + // IgnoreValidity ignores validity period in the validation chain + IgnoreValidity optional.Optional[bool] } // InterestHandler represents the callback function for an Interest handler. diff --git a/std/object/client_consume.go b/std/object/client_consume.go index d2de1a6b..64ba3e3e 100644 --- a/std/object/client_consume.go +++ b/std/object/client_consume.go @@ -64,7 +64,7 @@ func (c *Client) consumeObject(state *ConsumeState) { // if metadata fetching is disabled, just attempt to fetch one segment // with the prefix, then get the versioned name from the segment. if state.args.NoMetadata { - c.fetchDataByPrefix(name, state.args.TryStore, state.args.UseSignatureTime.GetOr(false), + c.fetchDataByPrefix(name, state.args.TryStore, state.args.UseSignatureTime.GetOr(false), state.args.IgnoreValidity.GetOr(false), func(data ndn.Data, err error) { if err != nil { state.finalizeError(err) @@ -81,7 +81,7 @@ func (c *Client) consumeObject(state *ConsumeState) { } // fetch RDR metadata for this object - c.fetchMetadata(name, state.args.TryStore, state.args.UseSignatureTime.GetOr(false), + c.fetchMetadata(name, state.args.TryStore, state.args.UseSignatureTime.GetOr(false), state.args.IgnoreValidity.GetOr(false), func(meta *rdr.MetaData, err error) { if err != nil { state.finalizeError(err) @@ -108,6 +108,7 @@ func (c *Client) fetchMetadata( name enc.Name, tryStore bool, useSignatureTime bool, + ignoreValidity bool, callback func(meta *rdr.MetaData, err error), ) { log.Debug(c, "Fetching object metadata", "name", name) @@ -134,6 +135,7 @@ func (c *Client) fetchMetadata( Data: args.Data, SigCovered: args.SigCovered, UseSignatureTime: optional.Some(useSignatureTime), + IgnoreValidity: optional.Some(ignoreValidity), Callback: func(valid bool, err error) { // validate with trust config if !valid { @@ -163,6 +165,7 @@ func (c *Client) fetchDataByPrefix( name enc.Name, tryStore bool, useSignatureTime bool, + ignoreValidity bool, callback func(data ndn.Data, err error), ) { log.Debug(c, "Fetching data with prefix", "name", name) @@ -189,6 +192,7 @@ func (c *Client) fetchDataByPrefix( Data: args.Data, SigCovered: args.SigCovered, UseSignatureTime: optional.Some(useSignatureTime), + IgnoreValidity: optional.Some(ignoreValidity), Callback: func(valid bool, err error) { if !valid { callback(nil, fmt.Errorf("%w: validate by prefix failed: %w", ndn.ErrSecurity, err)) diff --git a/std/object/client_consume_seg.go b/std/object/client_consume_seg.go index 5c547fa2..8fa9440e 100644 --- a/std/object/client_consume_seg.go +++ b/std/object/client_consume_seg.go @@ -289,6 +289,7 @@ func (s *rrSegFetcher) handleData(args ndn.ExpressCallbackArgs, state *ConsumeSt Data: args.Data, SigCovered: args.SigCovered, UseSignatureTime: state.args.UseSignatureTime, + IgnoreValidity: state.args.IgnoreValidity, Callback: func(valid bool, err error) { if !valid { state.finalizeError(fmt.Errorf("%w: validate seg failed: %w", ndn.ErrSecurity, err)) diff --git a/std/object/client_trust.go b/std/object/client_trust.go index b3ec308f..cadb3d7d 100644 --- a/std/object/client_trust.go +++ b/std/object/client_trust.go @@ -47,6 +47,7 @@ func (c *Client) ValidateExt(args ndn.ValidateExtArgs) { OverrideName: overrideName, UseDataNameFwHint: args.UseDataNameFwHint, UseSignatureTime: args.UseSignatureTime, + IgnoreValidity: args.IgnoreValidity, Fetch: func(name enc.Name, config *ndn.InterestConfig, callback ndn.ExpressCallbackFunc) { config.NextHopId = args.CertNextHop c.ExpressR(ndn.ExpressRArgs{ diff --git a/std/security/trust_config.go b/std/security/trust_config.go index 0a6c715a..8f91a227 100644 --- a/std/security/trust_config.go +++ b/std/security/trust_config.go @@ -120,6 +120,8 @@ type TrustConfigValidateArgs struct { Callback func(bool, error) // OverrideName is an override for the data name (advanced usage). OverrideName enc.Name + // ignore ValidityPeriod in the validation chain + IgnoreValidity optional.Optional[bool] // origDataName is the original data name being verified. origDataName enc.Name @@ -179,7 +181,7 @@ func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { // Bail if the data is a cert and is not fresh if t, ok := args.Data.ContentType().Get(); ok && t == ndn.ContentTypeKey { - if !args.UseSignatureTime.GetOr(false) && CertIsExpired(args.Data) { + if !args.UseSignatureTime.GetOr(false) && CertIsExpired(args.Data) && !args.IgnoreValidity.GetOr(false) { args.Callback(false, fmt.Errorf("certificate is expired: %s", args.Data.Name())) return } @@ -206,7 +208,7 @@ func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { return } - if args.UseSignatureTime.GetOr(false) && !ValidateSigTime(args.Data, args.cert) { + if args.UseSignatureTime.GetOr(false) && !ValidateSigTime(args.Data, args.cert) && !args.IgnoreValidity.GetOr(false) { args.Callback(false, fmt.Errorf("data not signed during validity period: %s", args.cert.Name())) return } @@ -231,9 +233,10 @@ func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { args.Callback(valid, fmt.Errorf("cross schema: %w", err)) } }, - OverrideName: args.OverrideName, - cert: args.cert, - depth: args.depth, + OverrideName: args.OverrideName, + IgnoreValidity: args.IgnoreValidity, + cert: args.cert, + depth: args.depth, UseSignatureTime: args.UseSignatureTime, }) @@ -296,10 +299,11 @@ func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { Data: args.cert, DataSigCov: args.certSigCov, - Fetch: args.Fetch, - Callback: args.Callback, - OverrideName: nil, - origDataName: args.origDataName, + Fetch: args.Fetch, + Callback: args.Callback, + OverrideName: nil, + IgnoreValidity: args.IgnoreValidity, + origDataName: args.origDataName, cert: nil, certSigCov: nil, @@ -379,7 +383,7 @@ func (tc *TrustConfig) Validate(args TrustConfigValidateArgs) { } // Bail if the fetched cert is not fresh and not using signature time flow - if !args.UseSignatureTime.GetOr(false) && CertIsExpired(res.Data) { + if !args.UseSignatureTime.GetOr(false) && CertIsExpired(res.Data) && !args.IgnoreValidity.GetOr(false) { args.Callback(false, fmt.Errorf("certificate is expired: %s", res.Data.Name())) return } @@ -414,16 +418,18 @@ func (tc *TrustConfig) validateCrossSchema(args TrustConfigValidateArgs) { } // Check validity period of the cross schema - if args.UseSignatureTime.GetOr(false) { - // Cross schema was valid at signature time - if !ValidateSigTime(args.Data, crossData) { - args.Callback(false, fmt.Errorf("cross schema signature time invalid: %s", crossData.Name())) - return - } - } else { - if CertIsExpired(crossData) { - args.Callback(false, fmt.Errorf("cross schema is expired: %s", crossData.Name())) - return + if !args.IgnoreValidity.GetOr(false) { + if args.UseSignatureTime.GetOr(false) { + // Cross schema was valid at signature time + if !ValidateSigTime(args.Data, crossData) { + args.Callback(false, fmt.Errorf("cross schema signature time invalid: %s", crossData.Name())) + return + } + } else { + if CertIsExpired(crossData) { + args.Callback(false, fmt.Errorf("cross schema is expired: %s", crossData.Name())) + return + } } } @@ -450,9 +456,10 @@ func (tc *TrustConfig) validateCrossSchema(args TrustConfigValidateArgs) { Data: crossData, DataSigCov: crossDataSigCov, - Fetch: args.Fetch, - Callback: args.Callback, - OverrideName: dataName, // original data + Fetch: args.Fetch, + Callback: args.Callback, + OverrideName: dataName, // original data + IgnoreValidity: args.IgnoreValidity, depth: args.depth, @@ -683,8 +690,9 @@ func (tc *TrustConfig) tryListedCerts(args certListArgs, names []enc.Name, idx i } tc.tryListedCerts(args, names, idx+1) }, - origDataName: args.args.origDataName, - depth: args.args.depth, + IgnoreValidity: args.args.IgnoreValidity, + origDataName: args.args.origDataName, + depth: args.args.depth, UseSignatureTime: args.args.UseSignatureTime, }) diff --git a/std/security/trust_config_test.go b/std/security/trust_config_test.go index 3abee828..c441e0a6 100644 --- a/std/security/trust_config_test.go +++ b/std/security/trust_config_test.go @@ -89,9 +89,10 @@ var tcTestKeyChain ndn.KeyChain = nil var tcTestFetchCount int = 0 type ValidateSyncOptions struct { - name string - signer ndn.Signer - crossSchema enc.Wire + name string + signer ndn.Signer + crossSchema enc.Wire + ignoreValidity bool } // Helper to validate a packet synchronously @@ -113,12 +114,13 @@ func validateSync(opts ValidateSyncOptions) bool { ch <- valid close(ch) }, + IgnoreValidity: optional.Some(opts.ignoreValidity), }) return <-ch } // Helper to validate certificates -func validateCerts(certData ndn.Data, certDataSigCov enc.Wire) bool { +func validateCerts(certData ndn.Data, certDataSigCov enc.Wire, ignoreValidity bool) bool { ch := make(chan bool) go tcTestTrustConfig.Validate(sec.TrustConfigValidateArgs{ Data: certData, @@ -129,6 +131,7 @@ func validateCerts(certData ndn.Data, certDataSigCov enc.Wire) bool { ch <- valid close(ch) }, + IgnoreValidity: optional.Some(ignoreValidity), }) return <-ch } @@ -787,10 +790,17 @@ func testTrustConfigIntra(t *testing.T, schema ndn.TrustSchema) { tcTestT.Log(eveSigner.KeyLocator().String()) eveCertWire, eveCertData, eveSigCov := signCert(rootSigner, tu.NoErr(signer.MarshalSecret(eveSigner)), expiredOpts) network[eveCertData.Name().String()] = eveCertWire - require.False(t, validateCerts(eveCertData, eveSigCov)) + require.False(t, validateCerts(eveCertData, eveSigCov, false)) + require.True(t, validateCerts(eveCertData, eveSigCov, true)) require.False(t, validateSync(ValidateSyncOptions{ - name: "/test/eve/data1", - signer: eveSigner, + name: "/test/eve/data1", + signer: eveSigner, + ignoreValidity: false, + })) + require.True(t, validateSync(ValidateSyncOptions{ + name: "/test/eve/data2", + signer: eveSigner, + ignoreValidity: true, })) } diff --git a/std/sync/snapshot_node_history.go b/std/sync/snapshot_node_history.go index 023e2d8b..bf9de2df 100644 --- a/std/sync/snapshot_node_history.go +++ b/std/sync/snapshot_node_history.go @@ -46,6 +46,8 @@ type SnapshotNodeHistory struct { IsRepo bool // UseSignatureTime checks validity period using signature time UseSignatureTime optional.Optional[bool] + // IgnoreValidity ignores validity period in the validation chain + IgnoreValidity optional.Optional[bool] // repoKnown is the known snapshot sequence number. repoKnown SvMap[uint64] @@ -164,6 +166,7 @@ func (s *SnapshotNodeHistory) fetchIndex(node enc.Name, boot uint64, known uint6 s.Client.ConsumeExt(ndn.ConsumeExtArgs{ Name: s.idxName(node, boot), UseSignatureTime: s.UseSignatureTime, + IgnoreValidity: s.IgnoreValidity, Callback: func(cstate ndn.ConsumeState) { go s.handleIndex(node, boot, known, cstate) }, @@ -212,6 +215,7 @@ func (s *SnapshotNodeHistory) handleIndex(node enc.Name, boot uint64, known uint s.Client.ConsumeExt(ndn.ConsumeExtArgs{ Name: snapName, UseSignatureTime: s.UseSignatureTime, + IgnoreValidity: s.IgnoreValidity, Callback: func(cstate ndn.ConsumeState) { snapC <- cstate }, }) diff --git a/std/sync/snapshot_node_latest.go b/std/sync/snapshot_node_latest.go index 294e5d1a..0a5c369a 100644 --- a/std/sync/snapshot_node_latest.go +++ b/std/sync/snapshot_node_latest.go @@ -35,6 +35,8 @@ type SnapshotNodeLatest struct { Threshold uint64 // UseSignatureTime checks validity period using signature time UseSignatureTime optional.Optional[bool] + // IgnoreValidity ignores validity period in the validation chain + IgnoreValidity optional.Optional[bool] // pss is the struct from the svs layer. pss snapPsState @@ -121,6 +123,7 @@ func (s *SnapshotNodeLatest) fetchSnap(node enc.Name, boot uint64) { s.Client.ConsumeExt(ndn.ConsumeExtArgs{ Name: s.snapName(node, boot), UseSignatureTime: s.UseSignatureTime, + IgnoreValidity: s.IgnoreValidity, Callback: func(cstate ndn.ConsumeState) { if cstate.Error() != nil { // Do not try too fast in case NFD returns NACK diff --git a/std/sync/svs.go b/std/sync/svs.go index c7924c73..60e71015 100644 --- a/std/sync/svs.go +++ b/std/sync/svs.go @@ -71,6 +71,8 @@ type SvSyncOpts struct { Passive bool // UseSignatureTime checks validity period using signature time UseSignatureTime optional.Optional[bool] + // IgnoreValidity ignores validity period in the validation chain + IgnoreValidity optional.Optional[bool] } type SvSyncUpdate struct { @@ -527,6 +529,7 @@ func (s *SvSync) onSyncData(dataWire enc.Wire) { Data: data, SigCovered: sigCov, UseSignatureTime: s.o.UseSignatureTime, + IgnoreValidity: s.o.IgnoreValidity, Callback: func(valid bool, err error) { if !valid || err != nil { log.Warn(s, "SvSync failed to validate signature", "name", data.Name(), "valid", valid, "err", err) diff --git a/std/sync/svs_alo.go b/std/sync/svs_alo.go index 2b67ee8b..f6df6e33 100644 --- a/std/sync/svs_alo.go +++ b/std/sync/svs_alo.go @@ -140,6 +140,14 @@ func NewSvsALO(opts SvsAloOpts) (*SvsALO, error) { }, s.state) } + // Override IgnoreValidity from SVS (incorrect but practical) + if latest, ok := s.opts.Snapshot.(*SnapshotNodeLatest); ok { + latest.IgnoreValidity = s.opts.Svs.IgnoreValidity + } + if history, ok := s.opts.Snapshot.(*SnapshotNodeHistory); ok { + history.IgnoreValidity = s.opts.Svs.IgnoreValidity + } + return s, nil } diff --git a/std/sync/svs_alo_data.go b/std/sync/svs_alo_data.go index 5a52e15d..27543385 100644 --- a/std/sync/svs_alo_data.go +++ b/std/sync/svs_alo_data.go @@ -119,6 +119,7 @@ func (s *SvsALO) consumeObject(node enc.Name, boot uint64, seq uint64) { s.client.ConsumeExt(ndn.ConsumeExtArgs{ Name: fetchName, UseSignatureTime: s.opts.Svs.UseSignatureTime, + IgnoreValidity: s.opts.Svs.IgnoreValidity, Callback: func(status ndn.ConsumeState) { s.mutex.Lock() defer s.mutex.Unlock() From 65e6e8c78b730f849b5719f5eb183188ad21736c Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Fri, 8 May 2026 10:09:33 -0700 Subject: [PATCH 21/23] fix IgnoreValidity in repo config --- repo/config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/repo/config.go b/repo/config.go index f87deeaa..5da2b4d0 100644 --- a/repo/config.go +++ b/repo/config.go @@ -17,6 +17,8 @@ type Config struct { KeyChainUri string `json:"keychain"` // List of trust anchor full names. TrustAnchors []string `json:"trust_anchors"` + // IgnoreValidity skips validity period checks when fetching remote data (e.g. SVS snapshots). + IgnoreValidity bool `json:"ignore_validity"` // NameN is the parsed name of the repo service. NameN enc.Name From 1ff59029b0126883b7e4764af18ebee0a7c9f4a7 Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Sat, 27 Jun 2026 12:39:40 -0700 Subject: [PATCH 22/23] keychain warning when expired certificate is inserted --- std/security/keychain/keychain_state.go | 32 ++++++++++++++++--------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/std/security/keychain/keychain_state.go b/std/security/keychain/keychain_state.go index f8495d3b..5a79cb56 100644 --- a/std/security/keychain/keychain_state.go +++ b/std/security/keychain/keychain_state.go @@ -5,6 +5,7 @@ import ( "github.com/named-data/ndnd/std/ndn" spec "github.com/named-data/ndnd/std/ndn/spec_2022" sec "github.com/named-data/ndnd/std/security" + "github.com/named-data/ndnd/std/log" ) // shared keychain state used by multiple implementations. @@ -22,17 +23,8 @@ func newKeyChainState(pubStore ndn.Store) *keyChainState { } } -func isCertName(name enc.Name) bool { - if len(name) < 4 { - return false - } - if !name.At(-4).IsGeneric("KEY") { - return false - } - if !name.At(-1).IsVersion() { - return false - } - return true +func (kc *keyChainState) String() string { + return "keychain-state" } func (kc *keyChainState) rebuildKeyCerts() { @@ -119,6 +111,11 @@ func (kc *keyChainState) insertCert(wire []byte) error { return ndn.ErrInvalidValue{Item: "certificate name"} } + // Warn if certificate is invalid + if sec.CertIsExpired(data) { + log.Warn(kc, "Expired certificate inserted into keychain") + } + // Check if certificate already exists for _, existing := range kc.certNames { if existing.Equal(name) { @@ -224,3 +221,16 @@ func (kc *keyChainState) deleteCert(name enc.Name) error { func (kc *keyChainState) CertNames() []enc.Name { return kc.certNames } + +func isCertName(name enc.Name) bool { + if len(name) < 4 { + return false + } + if !name.At(-4).IsGeneric("KEY") { + return false + } + if !name.At(-1).IsVersion() { + return false + } + return true +} From cd67961082b79650a53b642b1cad55d707892fd4 Mon Sep 17 00:00:00 2001 From: Craig Gu Date: Sat, 27 Jun 2026 13:07:12 -0700 Subject: [PATCH 23/23] fix lint --- std/security/keychain/keychain_state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/security/keychain/keychain_state.go b/std/security/keychain/keychain_state.go index 5a79cb56..a85835fd 100644 --- a/std/security/keychain/keychain_state.go +++ b/std/security/keychain/keychain_state.go @@ -2,10 +2,10 @@ package keychain import ( enc "github.com/named-data/ndnd/std/encoding" + "github.com/named-data/ndnd/std/log" "github.com/named-data/ndnd/std/ndn" spec "github.com/named-data/ndnd/std/ndn/spec_2022" sec "github.com/named-data/ndnd/std/security" - "github.com/named-data/ndnd/std/log" ) // shared keychain state used by multiple implementations.