micro-key-producer
Version:
Produces secure keys and passwords. Supports SSH, PGP, BLS, OTP, and many others
142 lines (132 loc) • 3.82 kB
text/typescript
/*! micro-key-producer - MIT License (c) 2024 Paul Miller (paulmillr.com) */
import { ed25519 } from '@noble/curves/ed25519';
import { sha256 } from '@noble/hashes/sha256';
import { concatBytes, randomBytes } from '@noble/hashes/utils';
import { base64 } from '@scure/base';
import * as P from 'micro-packed';
import { base64armor } from './utils.js';
export const SSHString: P.CoderType<string> = P.string(P.U32BE);
export const SSHBuf: P.CoderType<Uint8Array> = P.bytes(P.U32BE);
export const SSHKeyType: P.CoderType<undefined> = P.magic(SSHString, 'ssh-ed25519');
export const PublicKey: P.CoderType<
P.StructInput<{
keyType: undefined;
pubKey: Uint8Array;
}>
> = P.struct({ keyType: SSHKeyType, pubKey: P.bytes(P.U32BE) });
const PrivateKey = P.padRight(
8,
P.struct({
check1: P.bytes(4),
check2: P.bytes(4),
keyType: SSHKeyType,
pubKey: SSHBuf,
privKey: SSHBuf,
comment: SSHString,
}),
(i: number) => i + 1
);
// https://tools.ietf.org/html/draft-miller-ssh-agent-02#section-4.5
export const AuthData: P.CoderType<
P.StructInput<{
nonce: Uint8Array;
userAuthRequest: number;
user: string;
conn: string;
auth: string;
haveSig: number;
keyType: undefined;
pubKey: P.StructInput<{
keyType: undefined;
pubKey: Uint8Array;
}>;
}>
> = P.struct({
nonce: SSHBuf,
userAuthRequest: P.U8, // == 50
user: SSHString,
conn: SSHString,
auth: SSHString,
haveSig: P.U8, // == 1
keyType: SSHKeyType,
pubKey: P.prefix(P.U32BE, PublicKey),
});
export type AuthDataType = P.UnwrapCoder<typeof AuthData>;
export const PrivateExport: P.Coder<
P.StructInput<{
magic: undefined;
ciphername: undefined;
kdfname: undefined;
kdfopts: undefined;
keys: P.StructInput<{
pubKey: any;
privKey: any;
}>[];
}>,
string
> = base64armor(
'openssh private key',
70,
P.struct({
magic: P.magicBytes('openssh-key-v1\0'),
// Only decrypted ed25519 keys supported for now
ciphername: P.magic(SSHString, 'none'),
kdfname: P.magic(SSHString, 'none'),
kdfopts: P.magic(SSHString, ''),
keys: P.array(
P.U32BE,
P.struct({
pubKey: P.prefix(P.U32BE, PublicKey),
privKey: P.prefix(P.U32BE, PrivateKey),
})
),
})
);
export function formatPublicKey(bytes: Uint8Array, comment?: string): string {
const blob = PublicKey.encode({ pubKey: bytes });
return `ssh-ed25519 ${base64.encode(blob)}${comment ? ` ${comment}` : ''}`;
}
export function getFingerprint(bytes: Uint8Array): string {
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(/=$/, '')}`;
}
// For determenistic generation in tests
export function getKeys(
privateKey: Uint8Array,
comment?: string,
checkBytes: Uint8Array = randomBytes(4)
): {
publicKeyBytes: Uint8Array;
publicKey: string;
fingerprint: string;
privateKey: string;
} {
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 || '',
},
},
],
}),
};
}
// For SSH Agents
export function authSign(privateKey: Uint8Array, data: AuthDataType): Uint8Array {
return ed25519.sign(AuthData.encode(data), privateKey);
}
export default getKeys;