Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
17 changes: 13 additions & 4 deletions docs/security-util.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ ndnd sec sign-cert alice.key < alice.key > alice.cert
# Generate a key for /ndn/bob and create a certificate signed by /ndn/alice
# Sets the issuer field to ALICE
ndnd sec keygen /ndn/bob rsa 2048 > bob.key
ndnd sec sign-cert -issuer ALICE alice.key < bob.key > bob1.cert
ndnd sec sign-cert --issuer ALICE alice.key < bob.key > bob1.cert

# Create a certificate with a defined validity period
ndnd sec sign-cert -issuer ALICE -start 20150101000000 -end 20350101000000 alice.key < bob.key > bob2.cert
ndnd sec sign-cert --issuer ALICE --start 20150101000000 --end 20350101000000 alice.key < bob.key > bob2.cert

# Re-sign the public key in a certificate (or CSR) with a provided key
ndnd sec keygen /ndn/carol ed25519 > carol.key
ndnd sec sign-cert -issuer CAROL carol.key < bob1.cert > bob3.cert
ndnd sec sign-cert --issuer CAROL carol.key < bob1.cert > bob3.cert
```

## `ndnd sec key-list`

List all keys in the keychain.
List all keys and certs in the keychain.

```bash
# List all keys in the keychain directory /etc/app/keys
Expand Down Expand Up @@ -83,6 +83,15 @@ Delete a key (and its certificates) from the keychain.
ndnd sec key-delete dir:///etc/app/keys /ndn/bob/KEY/%A6%0Ei%1F%A8J%D4%8E
```

## `ndnd sec cert-export`

Export a certificate from the keychain.

```bash
# Export a specific certificate from a keychain
ndnd sec cert-delete dir:///etc/app/keys /ndn/bob/KEY/%A6%0Ei%1F%A8J%D4%8E/self/v=1
Comment thread
zjkmxy marked this conversation as resolved.
Outdated
```

## `ndnd sec cert-delete`

Delete a certificate from the keychain.
Expand Down
51 changes: 47 additions & 4 deletions tools/sec/keychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (t *ToolKeychain) configure(cmd *cobra.Command) {
cmd.AddCommand(&cobra.Command{
GroupID: "keychain",
Use: "key-list KEYCHAIN-URI",
Short: "List keys in a keychain",
Short: "List keys and certs in a keychain",
Run: t.List,
Args: cobra.ExactArgs(1),
Example: ` ndnd sec key-list dir:///safe/keys`,
Expand Down Expand Up @@ -74,9 +74,18 @@ and the default key of the identity will be exported.`,
Example: ` ndnd sec key-export dir:///safe/keys /alice`,
Run: t.Export,
})

cmd.AddCommand(&cobra.Command{
GroupID: "keychain",
Use: "cert-export KEYCHAIN-URI CERT-NAME",
Short: "Export a certificate from a keychain",
Args: cobra.ExactArgs(2),
Example: ` ndnd sec cert-export dir:///safe/keys /alice/KEY/~%E8t%A5%A3V%88%81/NA/v=0`,
Run: t.ExportCert,
})
}

// (AI GENERATED DESCRIPTION): Lists all identities and their keys in the keychain at the given path, printing each identity name followed by the names of its keys.
// Lists all identities, their keys, and the associated certs in the keychain at the given URI, printing each identity name followed by the names of its keys, then its names of its certs
func (*ToolKeychain) List(_ *cobra.Command, args []string) {
kc, err := keychain.NewKeyChain(args[0], storage.NewMemoryStore())
if err != nil {
Expand All @@ -89,11 +98,14 @@ func (*ToolKeychain) List(_ *cobra.Command, args []string) {
fmt.Printf("%s\n", id.Name())
for _, key := range id.Keys() {
fmt.Printf("==> %s\n", key.KeyName())
for _, cert := range key.UniqueCerts() {
fmt.Printf(" ==> %s\n", cert)
}
}
}
Comment thread
zjkmxy marked this conversation as resolved.
}

// (AI GENERATED DESCRIPTION): Imports keychain entries from standard input into the keychain named by the first argument, storing them in a memory-based keychain.
// Imports keychain entries from standard input into the keychain named by the first argument
func (*ToolKeychain) Import(_ *cobra.Command, args []string) {
kc, err := keychain.NewKeyChain(args[0], storage.NewMemoryStore())
if err != nil {
Expand All @@ -117,7 +129,7 @@ func (*ToolKeychain) Import(_ *cobra.Command, args []string) {
}
}

// (AI GENERATED DESCRIPTION): Exports a specified key (or an identity’s default key) from a keychain, PEM‑encodes its secret key, and writes it to standard output.
// Exports a specified key (or an identity’s default key) from a keychain, PEM‑encodes its secret key, and writes it to standard output.
func (*ToolKeychain) Export(_ *cobra.Command, args []string) {
name, err := enc.NameFromStr(args[1])
if err != nil {
Expand Down Expand Up @@ -205,6 +217,37 @@ func (*ToolKeychain) DeleteKey(_ *cobra.Command, args []string) {
}
}

// Exports a specified certificate from a keychain, PEM‑encodes it, and writes it to standard output.
func (*ToolKeychain) ExportCert(_ *cobra.Command, args []string) {
name, err := enc.NameFromStr(args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "Invalid certificate name: %s\n", args[1])
os.Exit(1)
return
}

kc, err := keychain.NewKeyChain(args[0], storage.NewMemoryStore())
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to open keychain: %s\n", err)
os.Exit(1)
return
}

wire, err := kc.Store().Get(name.Prefix(-1), true)
if err != nil || wire == nil {
fmt.Fprintf(os.Stderr, "Certificate not found: %s\n", name)
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cert-export takes CERT-NAME, but the lookup uses name.Prefix(-1) with prefix=true, which drops the version component and can export a different (newest) version than the one explicitly requested. Consider using an exact Get(name, false) when a version component is present, and only falling back to prefix search when the user provides a non-versioned cert name.

Suggested change
wire, err := kc.Store().Get(name.Prefix(-1), true)
if err != nil || wire == nil {
fmt.Fprintf(os.Stderr, "Certificate not found: %s\n", name)
wire, err := kc.Store().Get(name, false)
if err == nil && wire == nil {
wire, err = kc.Store().Get(name, true)
}
if err != nil || wire == nil {
fmt.Fprintf(os.Stderr, "Certificate not found: %s\n", name)
os.Exit(1)
return

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you remove the last component of the certificate?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UniqueCerts() always returns names with v=0 so it would fail if that was passed to it, I updated so that it works for all 3 cases (v=0, prefix, or specific cert).

Should I change key-list to get the full cert names instead of output of UniqueCerts() with v=0? I didn't before because no function currently exists in the Keychain or Store interface to make it possible to list every version of a unique certificate

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already forgot about the keystore. @tianyuan129 What do you think?

}

out, err := security.PemEncode(enc.Wire{wire}.Join())
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to convert certificate to text: %s\n", err)
os.Exit(1)
Comment thread
zjkmxy marked this conversation as resolved.
Outdated
return
}

os.Stdout.Write(out)
}

// DeleteCert removes a certificate from the keychain.
func (*ToolKeychain) DeleteCert(_ *cobra.Command, args []string) {
name, err := enc.NameFromStr(args[1])
Expand Down
Loading