Skip to content

Commit 51774b8

Browse files
committed
security: keychain refactor
1 parent a3691c7 commit 51774b8

File tree

9 files changed

+661
-120
lines changed

9 files changed

+661
-120
lines changed

docs/security-util.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,22 @@ ndnd sec key-export dir:///etc/app/keys /ndn/bob
7272

7373
# Export a specific key from a keychain
7474
ndnd sec key-export dir:///etc/app/keys /ndn/bob/KEY/%A6%0Ei%1F%A8J%D4%8E
75-
```
75+
```
76+
77+
## `ndnd sec key-delete`
78+
79+
Delete a key (and its certificates) from the keychain.
80+
81+
```bash
82+
# Delete a specific key from the keychain
83+
ndnd sec key-delete dir:///etc/app/keys /ndn/bob/KEY/%A6%0Ei%1F%A8J%D4%8E
84+
```
85+
86+
## `ndnd sec cert-delete`
87+
88+
Delete a certificate from the keychain.
89+
90+
```bash
91+
# Delete a certificate by name
92+
ndnd sec cert-delete dir:///etc/app/keys /ndn/bob/KEY/%A6%0Ei%1F%A8J%D4%8E/self/v=1
93+
```

std/ndn/security.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,12 @@ type KeyChain interface {
6464
IdentityByName(enc.Name) KeyChainIdentity
6565
// InsertKey inserts a key to the keychain.
6666
InsertKey(Signer) error
67+
// DeleteKey removes a key and all its certificates from the keychain.
68+
DeleteKey(enc.Name) error
6769
// InsertCert inserts a certificate to the keychain.
6870
InsertCert([]byte) error
71+
// DeleteCert removes a certificate from the keychain.
72+
DeleteCert(enc.Name) error
6973
}
7074

7175
// KeyChainIdentity is the interface of a signing identity.

std/security/keychain/keychain_dir.go

Lines changed: 168 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,19 @@ const EXT_PEM = ".pem"
2121

2222
// KeyChainDir is a directory-based keychain.
2323
type KeyChainDir struct {
24-
mem ndn.KeyChain
25-
path string
24+
state *keyChainState
25+
path string
26+
files map[string]string
27+
refs map[string]int
2628
}
2729

2830
// NewKeyChainDir creates a new in-memory keychain.
2931
func NewKeyChainDir(path string, pubStore ndn.Store) (ndn.KeyChain, error) {
3032
kc := &KeyChainDir{
31-
mem: NewKeyChainMem(pubStore),
32-
path: path,
33+
state: newKeyChainState(pubStore),
34+
path: path,
35+
files: make(map[string]string),
36+
refs: make(map[string]int),
3337
}
3438

3539
// Create directory if it doesn't exist
@@ -55,15 +59,55 @@ func NewKeyChainDir(path string, pubStore ndn.Store) (ndn.KeyChain, error) {
5559
}
5660

5761
filename := filepath.Join(path, entry.Name())
62+
written := make(map[string]struct{})
5863
content, err := os.ReadFile(filename)
5964
if err != nil {
6065
log.Warn(kc, "Failed to read keychain entry", "file", filename, "err", err)
6166
continue
6267
}
6368

64-
err = InsertFile(kc.mem, content)
69+
signers, certs, err := sec.DecodeFile(content)
6570
if err != nil {
66-
log.Error(kc, "Failed to insert keychain entries", "file", filename, "err", err)
71+
log.Error(kc, "Failed to parse keychain entry", "file", filename, "err", err)
72+
continue
73+
}
74+
75+
// Normalize keys and certs for hashed names
76+
for _, wire := range certs {
77+
if err := kc.state.insertCert(wire); err != nil {
78+
log.Error(kc, "Failed to insert keychain certificate", "file", filename, "err", err)
79+
continue
80+
}
81+
path, err := kc.writeFile(wire, EXT_CERT)
82+
if err != nil {
83+
log.Error(kc, "Failed to persist keychain certificate", "file", filename, "err", err)
84+
continue
85+
}
86+
written[path] = struct{}{}
87+
}
88+
89+
for _, signer := range signers {
90+
if err := kc.state.insertKey(signer); err != nil {
91+
log.Error(kc, "Failed to insert keychain key", "file", filename, "err", err)
92+
continue
93+
}
94+
secret, err := sig.MarshalSecret(signer)
95+
if err != nil {
96+
log.Error(kc, "Failed to marshal keychain key", "file", filename, "err", err)
97+
continue
98+
}
99+
path, err := kc.writeFile(secret.Join(), EXT_KEY)
100+
if err != nil {
101+
log.Error(kc, "Failed to persist keychain key", "file", filename, "err", err)
102+
continue
103+
}
104+
written[path] = struct{}{}
105+
}
106+
107+
if len(written) > 0 {
108+
if _, ok := written[filename]; !ok {
109+
_ = os.Remove(filename)
110+
}
67111
}
68112
}
69113

@@ -77,22 +121,22 @@ func (kc *KeyChainDir) String() string {
77121

78122
// (AI GENERATED DESCRIPTION): Returns the underlying in‑memory store that backs the KeyChainDir.
79123
func (kc *KeyChainDir) Store() ndn.Store {
80-
return kc.mem.Store()
124+
return kc.state.pubStore
81125
}
82126

83127
// (AI GENERATED DESCRIPTION): Returns a slice of all identities currently stored in the key‑chain directory.
84128
func (kc *KeyChainDir) Identities() []ndn.KeyChainIdentity {
85-
return kc.mem.Identities()
129+
return kc.state.Identities()
86130
}
87131

88132
// (AI GENERATED DESCRIPTION): Retrieves and returns the identity that matches the specified name from the keychain directory’s in‑memory store.
89133
func (kc *KeyChainDir) IdentityByName(name enc.Name) ndn.KeyChainIdentity {
90-
return kc.mem.IdentityByName(name)
134+
return kc.state.IdentityByName(name)
91135
}
92136

93137
// (AI GENERATED DESCRIPTION): Adds a signer to the in‑memory key chain and writes its secret key to disk in a file with the key extension.
94138
func (kc *KeyChainDir) InsertKey(signer ndn.Signer) error {
95-
err := kc.mem.InsertKey(signer)
139+
err := kc.state.insertKey(signer)
96140
if err != nil {
97141
return err
98142
}
@@ -102,29 +146,136 @@ func (kc *KeyChainDir) InsertKey(signer ndn.Signer) error {
102146
return err
103147
}
104148

105-
return kc.writeFile(secret.Join(), EXT_KEY)
149+
_, err = kc.writeFile(secret.Join(), EXT_KEY)
150+
return err
106151
}
107152

108153
// (AI GENERATED DESCRIPTION): Inserts the given certificate (in wire format) into the in‑memory key chain and writes it to disk with the certificate file extension.
109154
func (kc *KeyChainDir) InsertCert(wire []byte) error {
110-
err := kc.mem.InsertCert(wire)
155+
err := kc.state.insertCert(wire)
156+
if err != nil {
157+
return err
158+
}
159+
160+
_, err = kc.writeFile(wire, EXT_CERT)
161+
return err
162+
}
163+
164+
// DeleteKey removes a key and its certificates from the keychain and disk.
165+
func (kc *KeyChainDir) DeleteKey(keyName enc.Name) error {
166+
idName, err := sec.GetIdentityFromKeyName(keyName)
167+
if err != nil {
168+
return err
169+
}
170+
171+
id := kc.state.IdentityByName(idName)
172+
if id == nil {
173+
return enc.ErrNotFound{Key: keyName.String()}
174+
}
175+
176+
var signer ndn.Signer
177+
for _, key := range id.Keys() {
178+
if key.KeyName().Equal(keyName) {
179+
signer = key.Signer()
180+
break
181+
}
182+
}
183+
if signer == nil {
184+
return enc.ErrNotFound{Key: keyName.String()}
185+
}
186+
187+
secret, err := sig.MarshalSecret(signer)
188+
if err != nil {
189+
return err
190+
}
191+
192+
// collect certificate wires before removal
193+
certWires := make([][]byte, 0)
194+
for _, cert := range kc.state.CertNames() {
195+
if keyName.IsPrefix(cert) {
196+
wire, err := kc.Store().Get(cert, false)
197+
if err != nil {
198+
return err
199+
}
200+
if wire != nil {
201+
certWires = append(certWires, wire)
202+
}
203+
}
204+
}
205+
206+
if err := kc.state.deleteKey(keyName); err != nil {
207+
return err
208+
}
209+
210+
if err := kc.deleteFile(secret.Join(), EXT_KEY); err != nil {
211+
return err
212+
}
213+
for _, wire := range certWires {
214+
if err := kc.deleteFile(wire, EXT_CERT); err != nil {
215+
return err
216+
}
217+
}
218+
return nil
219+
}
220+
221+
// DeleteCert removes a certificate from the keychain and disk.
222+
func (kc *KeyChainDir) DeleteCert(name enc.Name) error {
223+
wire, err := kc.Store().Get(name, false)
111224
if err != nil {
112225
return err
113226
}
227+
if wire == nil {
228+
return enc.ErrNotFound{Key: name.String()}
229+
}
230+
231+
if err := kc.state.deleteCert(name); err != nil {
232+
return err
233+
}
114234

115-
return kc.writeFile(wire, EXT_CERT)
235+
return kc.deleteFile(wire, EXT_CERT)
116236
}
117237

118238
// (AI GENERATED DESCRIPTION): Writes the given binary data to a PEM‑encoded file named after its SHA‑256 hash (plus the supplied extension) in the keychain directory, with permissions set to 0600.
119-
func (kc *KeyChainDir) writeFile(wire []byte, ext string) error {
239+
func (kc *KeyChainDir) writeFile(wire []byte, ext string) (string, error) {
120240
hash := sha256.Sum256(wire)
121-
filename := hex.EncodeToString(hash[:])
122-
path := filepath.Join(kc.path, filename+ext)
241+
filename := hex.EncodeToString(hash[:]) + ext
242+
path := filepath.Join(kc.path, filename)
123243

124244
str, err := sec.PemEncode(wire)
125245
if err != nil {
246+
return "", err
247+
}
248+
249+
if err := os.WriteFile(path, str, 0600); err != nil {
250+
return "", err
251+
}
252+
253+
kc.files[filename] = path
254+
kc.refs[path]++
255+
return path, nil
256+
}
257+
258+
func (kc *KeyChainDir) deleteFile(wire []byte, ext string) error {
259+
hash := sha256.Sum256(wire)
260+
filename := hex.EncodeToString(hash[:]) + ext
261+
path, ok := kc.files[filename]
262+
if !ok {
263+
path = filepath.Join(kc.path, filename)
264+
}
265+
266+
if count, ok := kc.refs[path]; ok {
267+
if count > 1 {
268+
kc.refs[path] = count - 1
269+
delete(kc.files, filename)
270+
return nil
271+
}
272+
delete(kc.refs, path)
273+
}
274+
275+
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
126276
return err
127277
}
128278

129-
return os.WriteFile(path, str, 0600)
279+
delete(kc.files, filename)
280+
return nil
130281
}

0 commit comments

Comments
 (0)