# hdpki

Hierarchical deterministic key derivation for OpenSSH and X.509. Derives
ed25519 keys from a master seed using HMAC-SHA512 chain codes (BIP-32 style),
producing both OpenSSH keys and X.509 certificates from the same deterministic
key tree.

## Features

- **BIP-32-style HD derivation** with HMAC-SHA512 chain codes for ed25519
- **BIP-39 mnemonic** generation and recovery
- **OpenSSH key output** (private key + authorized_keys format)
- **X.509 certificate generation** (self-signed or CA-signed)
- **Canonical path validation**: `m/ssh/<principal>/<domain>/<host>/<bucket>`

## CLI usage

The `hdpki` binary supports generating master seeds, deriving SSH keys, and
creating X.509 certificates.

```shell
# Generate a new master seed and write it to master.seed (base64 encoded)
hdpki master --output master.seed

# Create a BIP-39 mnemonic and print it alongside the seed
hdpki master --bip39

# Derive an SSH key for a canonical HD path
hdpki derive \
  -m master.seed \
  -p m/ssh/user:alice/example.com/bastion.example.com/1700000000 \
  -o id_ed25519 \
  -A id_ed25519.pub

# Generate a self-signed X.509 certificate
hdpki cert \
  -m master.seed \
  -p m/ssh/user:alice/example.com/bastion.example.com/1700000000 \
  --cn "alice@example.com" \
  --san-dns bastion.example.com \
  -o cert.pem \
  -k key.pem

# Generate a CA certificate
hdpki cert \
  -m master.seed \
  -p m/ssh/user:ca/example.com/ca.example.com/1 \
  --cn "Example CA" --org "Example Corp" \
  --ca --days 3650 \
  -o ca.pem -k ca-key.pem

# Sign a leaf certificate with the CA
hdpki cert \
  -m master.seed \
  -p m/ssh/user:web/example.com/web.example.com/1 \
  --cn "web.example.com" \
  --san-dns web.example.com \
  --issuer-path m/ssh/user:ca/example.com/ca.example.com/1 \
  --issuer-cert ca.pem \
  --days 90 \
  -o leaf.pem -k leaf-key.pem
```

## Library usage

```rust
use hdpki::{master, X509Params};

let seed = b"my-secret-master-seed-material";
let root = master(seed).unwrap();

// Derive a node and get SSH material
let node = root.derive("m/ssh/user:alice/example.com/bastion.example.com/1").unwrap();
let material = node.ed25519().unwrap();

// Generate a self-signed X.509 certificate
let params = X509Params {
    common_name: "alice@example.com".to_string(),
    san_dns_names: vec!["bastion.example.com".to_string()],
    ..Default::default()
};
let cert = node.x509_self_signed(&params).unwrap();
println!("{}", cert.cert_pem);
```

## Path format

The canonical derivation path follows this structure:

```
m/ssh/<principal>/<domain>/<host>/<bucket>
```

- **principal**: `user:<name>` or `host:<name>`
- **domain**: Fully qualified domain (e.g., `example.com`)
- **host**: Must be within the domain (e.g., `bastion.example.com`)
- **bucket**: Numeric value for key rotation (e.g., epoch timestamp)

Use `--raw-path` (or `-r`) to bypass strict validation.

## License

Apache-2.0
