keystore

tape
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:

UseThe keyIts operation
SignECDSAsign(msg): SHA-256 of msg, signed; the signature as r ‖ s, 64 bytes
AgreeECDHagree(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

FunctionDoes
open_or_create(name, use) -> Key or KeyErrorThe key under name, made if the system has none
k.public_sec1() -> [65]u8 or KeyErrorIts public key
k.sign(msg) -> [64]u8 or KeyErrorAn ECDSA signature over SHA-256 of msg; traps on an Agree key
k.agree(peer_sec1) -> [32]u8 or KeyErrorECDH 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() -> boolWhether the key lives in hardware
k.remove() -> void or KeyErrorDeletes 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.

KeyErrorWhen
UnsupportedThe system keeps no keys here (see below)
WrongUseThe name holds a key for the other use
BadPeeragree was given something that isn’t a P-256 public key
FailedThe system’s keystore refused
OutOfMemoryOut of memory

Platforms

Where keys live
WindowsThe TPM (the Microsoft Platform Crypto Provider) when there is one, else the software key storage (DPAPI, per user). hardware_backed says which
macOSThe 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, UusiUnsupported: 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: