UNPKG

micro-key-producer

Version:

Produces secure passwords & keys for WebCrypto, SSH, PGP, SLIP10, OTP and many others

219 lines 7.71 kB
/*! micro-key-producer - MIT License (c) 2024 Paul Miller (paulmillr.com) */ /** * Deterministic producer of ed25519 SSH keys. * @module */ import { ed25519 } from '@noble/curves/ed25519.js'; import { sha256 } from '@noble/hashes/sha2.js'; import { concatBytes, randomBytes } from '@noble/hashes/utils.js'; import { base64 } from '@scure/base'; import * as P from 'micro-packed'; import { base64armor } from "./utils.js"; /** * SSH length-prefixed string coder. * @example * Encode the SSH string framing used by OpenSSH packets. * ```ts * import { SSHString } from 'micro-key-producer/ssh.js'; * SSHString.encode('ssh-ed25519'); * ``` */ export const SSHString = /* @__PURE__ */ P.string(P.U32BE); /** * SSH length-prefixed byte-string coder. * @example * Encode one SSH binary blob with the standard length prefix. * ```ts * import { SSHBuf } from 'micro-key-producer/ssh.js'; * SSHBuf.encode(new Uint8Array([1, 2, 3])); * ``` */ export const SSHBuf = /* @__PURE__ */ P.bytes(P.U32BE); /** * SSH key-type tag coder for `ssh-ed25519`. * @example * Encode the fixed OpenSSH key type tag for Ed25519 keys. * ```ts * import { SSHKeyType } from 'micro-key-producer/ssh.js'; * SSHKeyType.encode(undefined); * ``` */ export const SSHKeyType = /* @__PURE__ */ P.magic(SSHString, 'ssh-ed25519'); /** * SSH public-key blob coder. * @example * Encode the raw public-key blob that OpenSSH places after `ssh-ed25519`. * ```ts * import { randomBytes } from '@noble/hashes/utils.js'; * import { PublicKey, getKeys } from 'micro-key-producer/ssh.js'; * const seed = randomBytes(32); * PublicKey.encode({ pubKey: getKeys(seed).publicKeyBytes }); * ``` */ export const PublicKey = /* @__PURE__ */ P.struct({ keyType: SSHKeyType, pubKey: /* @__PURE__ */ P.bytes(P.U32BE) }); const PrivateKey = /* @__PURE__ */ P.padRight(8, /* @__PURE__ */ P.struct({ check1: /* @__PURE__ */ P.bytes(4), check2: /* @__PURE__ */ P.bytes(4), keyType: SSHKeyType, pubKey: SSHBuf, privKey: SSHBuf, comment: SSHString, }), (i) => i + 1); // https://tools.ietf.org/html/draft-miller-ssh-agent-02#section-4.5 /** * SSH agent user-auth request coder. * @example * Encode the payload that SSH agents sign during public-key authentication. * ```ts * import { randomBytes } from '@noble/hashes/utils.js'; * import { AuthData, getKeys } from 'micro-key-producer/ssh.js'; * const seed = randomBytes(32); * AuthData.encode({ * nonce: randomBytes(32), * userAuthRequest: 50, * user: 'alice', * conn: 'ssh-connection', * auth: 'publickey', * haveSig: 1, * pubKey: { pubKey: getKeys(seed).publicKeyBytes }, * }); * ``` */ export const AuthData = /* @__PURE__ */ P.struct({ nonce: SSHBuf, userAuthRequest: P.U8, // == 50 user: SSHString, conn: SSHString, auth: SSHString, haveSig: P.U8, // == 1 keyType: SSHKeyType, pubKey: /* @__PURE__ */ P.prefix(P.U32BE, PublicKey), }); /** * OpenSSH private-key armor coder. * @example * Decode the armored private key text that `getKeys()` emits. * ```ts * import { randomBytes } from '@noble/hashes/utils.js'; * import { PrivateExport, getKeys } from 'micro-key-producer/ssh.js'; * const seed = randomBytes(32); * PrivateExport.decode(getKeys(seed, 'alice@example.com').privateKey); * ``` */ export const PrivateExport = /* @__PURE__ */ base64armor('openssh private key', 70, /* @__PURE__ */ P.struct({ magic: /* @__PURE__ */ P.magicBytes('openssh-key-v1\0'), // Only decrypted ed25519 keys supported for now ciphername: /* @__PURE__ */ P.magic(SSHString, 'none'), kdfname: /* @__PURE__ */ P.magic(SSHString, 'none'), kdfopts: /* @__PURE__ */ P.magic(SSHString, ''), keys: /* @__PURE__ */ P.array(P.U32BE, /* @__PURE__ */ P.struct({ pubKey: /* @__PURE__ */ P.prefix(P.U32BE, PublicKey), privKey: /* @__PURE__ */ P.prefix(P.U32BE, PrivateKey), })), })); /** * Encodes an OpenSSH public key line. * @param bytes - Raw ed25519 public key bytes. * @param comment - Optional trailing comment. * @returns `ssh-ed25519 ...` public key line. * @example * Render the OpenSSH public key line you can paste into `authorized_keys`. * ```ts * import { randomBytes } from '@noble/hashes/utils.js'; * import { formatPublicKey, getKeys } from 'micro-key-producer/ssh.js'; * const seed = randomBytes(32); * formatPublicKey(getKeys(seed).publicKeyBytes, 'alice@example.com'); * ``` */ export function formatPublicKey(bytes, comment) { const blob = PublicKey.encode({ pubKey: bytes }); return `ssh-ed25519 ${base64.encode(blob)}${comment ? ` ${comment}` : ''}`; } /** * Computes the OpenSSH SHA-256 fingerprint for a public key. * @param bytes - Raw ed25519 public key bytes. * @returns SSH fingerprint string. * @example * Compute the fingerprint shown by `ssh-keygen -l`. * ```ts * import { randomBytes } from '@noble/hashes/utils.js'; * import { getFingerprint, getKeys } from 'micro-key-producer/ssh.js'; * const seed = randomBytes(32); * getFingerprint(getKeys(seed).publicKeyBytes); * ``` */ export function getFingerprint(bytes) { const blob = PublicKey.encode({ pubKey: bytes }); // ssh-keygen -l -f ~/.ssh/id_ed25519 // 256 SHA256:+WK/Sl4XJjoxDlAWYuhq4Fl2hka9j3GOUjYczQkqnCI user@comp.local (ED25519) return `SHA256:${base64.encode(sha256(blob)).replace(/=$/, '')}`; } /** * Derives deterministic OpenSSH key material from an ed25519 secret key. * @param privateKey - 32-byte ed25519 secret key. * @param comment - Optional key comment. * @param checkBytes - Optional repeated check bytes for the private-key block. * @returns Public key bytes, public key text, fingerprint, and armored private key. * @example * Export both the public and private OpenSSH key material from one seed. * ```ts * import { randomBytes } from '@noble/hashes/utils.js'; * import { getKeys } from 'micro-key-producer/ssh.js'; * const seed = randomBytes(32); * getKeys(seed, 'alice@example.com').privateKey; * ``` */ export function getKeys(privateKey, comment, checkBytes = randomBytes(4)) { const pubKey = ed25519.getPublicKey(privateKey); return { publicKeyBytes: pubKey, publicKey: formatPublicKey(pubKey, comment), fingerprint: getFingerprint(pubKey), privateKey: PrivateExport.encode({ keys: [ { pubKey: { pubKey }, privKey: { // Check bytes, should be same check1: checkBytes, check2: checkBytes, pubKey, privKey: concatBytes(privateKey, pubKey), comment: comment || '', }, }, ], }), }; } /** * Signs SSH agent authentication data with an ed25519 private key. * @param privateKey - 32-byte ed25519 secret key. * @param data - SSH agent authentication payload. * @returns Detached signature bytes. * @example * Sign the SSH agent payload that will be verified against the exported public key. * ```ts * import { randomBytes } from '@noble/hashes/utils.js'; * import { authSign, getKeys } from 'micro-key-producer/ssh.js'; * const seed = randomBytes(32); * const keys = getKeys(seed); * authSign(seed, { * nonce: randomBytes(32), * userAuthRequest: 50, * user: 'alice', * conn: 'ssh-connection', * auth: 'publickey', * haveSig: 1, * pubKey: { pubKey: keys.publicKeyBytes }, * }); * ``` */ export function authSign(privateKey, data) { return ed25519.sign(AuthData.encode(data), privateKey); } export default getKeys; //# sourceMappingURL=ssh.js.map