Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions repo/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"path/filepath"

enc "github.com/named-data/ndnd/std/encoding"
"github.com/named-data/ndnd/std/ndn"
sec "github.com/named-data/ndnd/std/security"
)

type Config struct {
Expand Down Expand Up @@ -59,6 +61,13 @@ func (c *Config) TrustAnchorNames() []enc.Name {
return res
}

func (c *Config) certExpiredCallback() ndn.CertExpiredCallback {
if c.IgnoreValidity {
return sec.IgnoreExpiredCert
}
return sec.ValidateAtSignatureTime
}

// (AI GENERATED DESCRIPTION): Returns a new Config with default placeholder values: empty Name and StorageDir strings, and a nil NameN slice.
func DefaultConfig() *Config {
return &Config{
Expand Down
8 changes: 3 additions & 5 deletions repo/repo_mgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/named-data/ndnd/std/object"
sec "github.com/named-data/ndnd/std/security"
"github.com/named-data/ndnd/std/security/trust_schema"
"github.com/named-data/ndnd/std/types/optional"
)

// (AI GENERATED DESCRIPTION): Parses a repository management command from the received wire and dispatches it to the sync‑join handler if present, otherwise logs a warning about an unknown command.
Expand Down Expand Up @@ -197,10 +196,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,
UseSignatureTime: optional.Some(true),
IgnoreValidity: optional.Some(r.config.IgnoreValidity),
Name: name,
TryStore: true,
OnCertExpired: r.config.certExpiredCallback(),
Callback: func(state ndn.ConsumeState) {
wire = append(wire, state.Content()...)
if state.Error() != nil {
Expand Down
13 changes: 5 additions & 8 deletions repo/repo_svs.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
spec "github.com/named-data/ndnd/std/ndn/spec_2022"
"github.com/named-data/ndnd/std/ndn/svs_ps"
ndn_sync "github.com/named-data/ndnd/std/sync"
"github.com/named-data/ndnd/std/types/optional"
)

type RepoSvs struct {
Expand Down Expand Up @@ -54,11 +53,10 @@ func (r *RepoSvs) Start() (err error) {
}

snapshot = &ndn_sync.SnapshotNodeHistory{
Client: r.client,
Threshold: r.cmd.HistorySnapshot.Threshold,
IsRepo: true,
UseSignatureTime: optional.Some(true),
IgnoreValidity: optional.Some(r.config.IgnoreValidity),
Client: r.client,
Threshold: r.cmd.HistorySnapshot.Threshold,
IsRepo: true,
OnCertExpired: r.config.certExpiredCallback(),
}
}

Expand All @@ -79,8 +77,7 @@ func (r *RepoSvs) Start() (err error) {
SuppressionPeriod: 500 * time.Millisecond,
PeriodicTimeout: 365 * 24 * time.Hour, // basically never
Passive: true,
UseSignatureTime: optional.Some(true),
IgnoreValidity: optional.Some(r.config.IgnoreValidity),
OnCertExpired: r.config.certExpiredCallback(),
},
Snapshot: snapshot,
MulticastPrefix: multicastPrefix,
Expand Down
14 changes: 6 additions & 8 deletions std/ndn/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,9 @@ type ConsumeExtArgs struct {
OnProgress func(status ConsumeState)
// NoMetadata disables fetching RDR metadata (advanced usage).
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]
// OnCertExpired decides whether an expired certificate may be used.
// A nil callback rejects expired certificates.
OnCertExpired CertExpiredCallback
}

// ExpressRArgs are the arguments for the express retry API.
Expand Down Expand Up @@ -169,10 +168,9 @@ type ValidateExtArgs struct {
CertNextHop optional.Optional[uint64]
// UseDataNameFwHint overrides trust config option.
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]
// OnCertExpired decides whether an expired certificate may be used.
// A nil callback rejects expired certificates.
OnCertExpired CertExpiredCallback
}

// Announcement are the arguments for the announce prefix API.
Expand Down
4 changes: 0 additions & 4 deletions std/ndn/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,6 @@ 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.
Expand Down
22 changes: 22 additions & 0 deletions std/ndn/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,28 @@ type Signer interface {
// Create a go routine for time consuming jobs.
type SigChecker func(name enc.Name, sigCovered enc.Wire, sig Signature) bool

// CertExpiredCallbackArgs are the arguments passed to CertExpiredCallback.
type CertExpiredCallbackArgs struct {
// Data is the packet whose validation depends on Cert's validity.
Data Data
// Cert is the certificate or cross-schema packet authorizing Data.
Cert Data
}

// CertExpiredCallback decides whether a validation relation involving an
// expired validity period may be used. For a Data <- Cert relation, it is
// called when Cert is expired or when expired Data is validated through Cert.
// Call complete with nil to continue validation or an error to reject it.
// Acceptance is authoritative: a successful validation may use the relation to
// establish trust that remains available to later validations.
// complete may be called synchronously or asynchronously, but must be called
// exactly once. The callback itself should return promptly without blocking
// the validation goroutine.
// An expired certificate in a chain may cause one call for Data <- certificate
// and another for certificate <- upstream signer.
// Separate validation operations may invoke the callback concurrently.
type CertExpiredCallback func(args CertExpiredCallbackArgs, complete func(error))

// KeyChain is the interface of a keychain.
// Note that Keychains are not thread-safe, and the owner should provide a lock.
type KeyChain interface {
Expand Down
24 changes: 10 additions & 14 deletions std/object/client_consume.go
Original file line number Diff line number Diff line change
Expand Up @@ -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), state.args.IgnoreValidity.GetOr(false),
c.fetchDataByPrefix(name, state.args.TryStore, state.args.OnCertExpired,
func(data ndn.Data, err error) {
if err != nil {
state.finalizeError(err)
Expand All @@ -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), state.args.IgnoreValidity.GetOr(false),
c.fetchMetadata(name, state.args.TryStore, state.args.OnCertExpired,
func(meta *rdr.MetaData, err error) {
if err != nil {
state.finalizeError(err)
Expand All @@ -107,8 +107,7 @@ func (c *Client) consumeObjectWithMeta(state *ConsumeState, meta *rdr.MetaData)
func (c *Client) fetchMetadata(
name enc.Name,
tryStore bool,
useSignatureTime bool,
ignoreValidity bool,
onCertExpired ndn.CertExpiredCallback,
callback func(meta *rdr.MetaData, err error),
) {
log.Debug(c, "Fetching object metadata", "name", name)
Expand All @@ -132,10 +131,9 @@ func (c *Client) fetchMetadata(
return
}
c.ValidateExt(ndn.ValidateExtArgs{
Data: args.Data,
SigCovered: args.SigCovered,
UseSignatureTime: optional.Some(useSignatureTime),
IgnoreValidity: optional.Some(ignoreValidity),
Data: args.Data,
SigCovered: args.SigCovered,
OnCertExpired: onCertExpired,
Callback: func(valid bool, err error) {
// validate with trust config
if !valid {
Expand Down Expand Up @@ -164,8 +162,7 @@ func (c *Client) fetchMetadata(
func (c *Client) fetchDataByPrefix(
name enc.Name,
tryStore bool,
useSignatureTime bool,
ignoreValidity bool,
onCertExpired ndn.CertExpiredCallback,
callback func(data ndn.Data, err error),
) {
log.Debug(c, "Fetching data with prefix", "name", name)
Expand All @@ -189,10 +186,9 @@ func (c *Client) fetchDataByPrefix(
return
}
c.ValidateExt(ndn.ValidateExtArgs{
Data: args.Data,
SigCovered: args.SigCovered,
UseSignatureTime: optional.Some(useSignatureTime),
IgnoreValidity: optional.Some(ignoreValidity),
Data: args.Data,
SigCovered: args.SigCovered,
OnCertExpired: onCertExpired,
Callback: func(valid bool, err error) {
if !valid {
callback(nil, fmt.Errorf("%w: validate by prefix failed: %w", ndn.ErrSecurity, err))
Expand Down
7 changes: 3 additions & 4 deletions std/object/client_consume_seg.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +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,
UseSignatureTime: state.args.UseSignatureTime,
IgnoreValidity: state.args.IgnoreValidity,
Data: args.Data,
SigCovered: args.SigCovered,
OnCertExpired: state.args.OnCertExpired,
Callback: func(valid bool, err error) {
if !valid {
state.finalizeError(fmt.Errorf("%w: validate seg failed: %w", ndn.ErrSecurity, err))
Expand Down
17 changes: 15 additions & 2 deletions std/object/client_trust.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ func (c *Client) ValidateExt(args ndn.ValidateExtArgs) {
Callback: args.Callback,
OverrideName: overrideName,
UseDataNameFwHint: args.UseDataNameFwHint,
UseSignatureTime: args.UseSignatureTime,
IgnoreValidity: args.IgnoreValidity,
OnCertExpired: c.wrapOnCertExpired(args.OnCertExpired),
Fetch: func(name enc.Name, config *ndn.InterestConfig, callback ndn.ExpressCallbackFunc) {
config.NextHopId = args.CertNextHop
c.ExpressR(ndn.ExpressRArgs{
Expand All @@ -61,6 +60,20 @@ func (c *Client) ValidateExt(args ndn.ValidateExtArgs) {
})
}

// wrapOnCertExpired ensures validation resumes on the engine goroutine.
func (c *Client) wrapOnCertExpired(appCallback ndn.CertExpiredCallback) ndn.CertExpiredCallback {
if appCallback == nil {
return nil
}
return func(args ndn.CertExpiredCallbackArgs, resumeValidation func(error)) {
appCallback(args, func(err error) {
c.engine.Post(func() {
resumeValidation(err)
})
})
}
}

// SetTrustSchema replaces the client's trust schema at runtime.
// No-op if the client does not have a trust config or if schema is nil.
func (c *Client) SetTrustSchema(schema ndn.TrustSchema) {
Expand Down
29 changes: 21 additions & 8 deletions std/security/cert_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ type CertCache struct {
}

type certCacheEntry struct {
data ndn.Data
expiry time.Time
data ndn.Data
sigCovered enc.Wire
expiry time.Time
}

// CertListCache stores validated CertList Data packets keyed by prefix and full name.
Expand Down Expand Up @@ -68,30 +69,42 @@ func NewCertCache() *CertCache {

// Get retrieves a certificate from the cache.
// The name can be either the certificate name or the key locator.
// If the cert expires in less than 5 minutes, it is considered stale.
// Entries are retained until five minutes after certificate expiry.
func (cc *CertCache) Get(name enc.Name) (ndn.Data, bool) {
entry, ok := cc.get(name)
return entry.data, ok
}

// get retrieves the certificate and its signature verification evidence.
func (cc *CertCache) get(name enc.Name) (certCacheEntry, bool) {
str := name.TlvStr()
if v, ok := cc.cache.Load(str); ok {
entry := v.(certCacheEntry)
if entry.expiry.Add(5 * time.Minute).After(time.Now()) {
return entry.data, true
return entry, true
} else {
cc.cache.Delete(str)
}
}
return nil, false
return certCacheEntry{}, false
}

// Put stores a certificate in the cache
// Put stores certificate data without signature verification evidence.
func (cc *CertCache) Put(cert ndn.Data) {
cc.put(cert, nil)
}

// put stores a certificate with the wire covered by its signature.
func (cc *CertCache) put(cert ndn.Data, sigCovered enc.Wire) {
_, expiry := cert.Signature().Validity()
if !expiry.IsSet() {
return // huh?
}

entry := certCacheEntry{
data: cert,
expiry: expiry.Unwrap(),
data: cert,
sigCovered: sigCovered,
expiry: expiry.Unwrap(),
}

// Store the certificate by its own name
Expand Down
20 changes: 20 additions & 0 deletions std/security/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,26 @@ func CertIsExpired(cert ndn.Data) bool {
return false
}

// RejectExpiredCert rejects an expired certificate.
func RejectExpiredCert(args ndn.CertExpiredCallbackArgs, complete func(error)) {
complete(fmt.Errorf("certificate is expired: %s", args.Cert.Name()))
}

// IgnoreExpiredCert accepts an expired certificate without additional checks.
func IgnoreExpiredCert(_ ndn.CertExpiredCallbackArgs, complete func(error)) {
complete(nil)
}

// ValidateAtSignatureTime accepts an expired validation chain when each
// affected Data packet was signed during its Cert's validity period.
func ValidateAtSignatureTime(args ndn.CertExpiredCallbackArgs, complete func(error)) {
if ValidateSigTime(args.Data, args.Cert) {
complete(nil)
return
}
complete(fmt.Errorf("data not signed during validity period: %s", args.Cert.Name()))
}

// getPubKey gets the public key from an NDN data.
// returns [public key, key name, error].
func getPubKey(data ndn.Data) ([]byte, enc.Name, error) {
Expand Down
Loading
Loading