keystore
import keystore from "keystore";
import crypto from "crypto";
import io from "io";
fn main() -> i32 {
var k: keystore.Key = keystore.open_or_create("myapp-device", keystore.Use.Sign) or {
io.println("no system keystore: {err}"); // Linux: use crypto's software keys
return 1;
};
defer k.close();
let pubkey: [65]u8 = k.public_sec1() or { return 1; };
let sig: [64]u8 = k.sign("hello") or { return 1; };
// anyone with the public key checks it with crypto
var hash: [32]u8;
crypto.sha256_digest("hello", hash[0..32]);
crypto.ecdsa_verify_raw(crypto.Curve.P256, pubkey[0..65], hash[0..32], sig[0..32], sig[32..64]) or {
return 2;
};
io.println("in hardware: {k.hardware_backed()}");
return 0;
}keystore keeps a device’s private keys where the program can use them
but never read them: a TPM’s or the Secure Enclave’s key never leaves the
chip. A key is made the first time its name is opened and found again by
that name on every later run.
Keys are P-256 and do one thing each:
Use | The key | Its operation |
|---|---|---|
Sign | ECDSA | sign(msg): SHA-256 of msg, signed; the signature as r ‖ s, 64 bytes |
Agree | ECDH | agree(peer_sec1): the shared point’s x coordinate, 32 bytes, with no KDF after it |
The formats are the common ones: crypto.ecdsa_verify_raw checks a
signature, crypto.p256_ecdh on the other side gives the same 32 bytes,
and a public key is SEC 1 uncompressed (04, then X and Y, 65 bytes).
Functions
| Function | Does |
|---|---|
open_or_create(name, use) -> Key or KeyError | The key under name, made if the system has none |
k.public_sec1() -> [65]u8 or KeyError | Its public key |
k.sign(msg) -> [64]u8 or KeyError | An ECDSA signature over SHA-256 of msg; traps on an Agree key |
k.agree(peer_sec1) -> [32]u8 or KeyError | ECDH with the peer’s public key; BadPeer unless it is 65 bytes, starts 04 and is on the curve; traps on a Sign key |
k.hardware_backed() -> bool | Whether the key lives in hardware |
k.remove() -> void or KeyError | Deletes the key from the system (and closes k) |
k.close() | Frees the handle; the key stays. Closing twice does nothing |
A name is 1 to 128 bytes without control characters, / or \; any
other traps. Opening a name that holds a key for the other use is
WrongUse.
KeyError | When |
|---|---|
Unsupported | The system keeps no keys here (see below) |
WrongUse | The name holds a key for the other use |
BadPeer | agree was given something that isn’t a P-256 public key |
Failed | The system’s keystore refused |
OutOfMemory | Out of memory |
Platforms
| Where keys live | |
|---|---|
| Windows | The TPM (the Microsoft Platform Crypto Provider) when there is one, else the software key storage (DPAPI, per user). hardware_backed says which |
| macOS | The Secure Enclave. A program needs to be signed with a keychain entitlement for it; without one (any binary tape build makes), or on a Mac without a Secure Enclave, open_or_create is Unsupported |
| Linux, Uusi | Unsupported: use crypto’s P-256 (p256_keypair) and keep the private key yourself |
Every function may be called from any thread: a library that calls you
back on threads of its own (a Rust core’s device keystore callback) can
use a Key from all of them.
Last modified: