ed25519-keygen
Advanced tools
Comparing version 0.4.10 to 0.4.11
@@ -0,1 +1,2 @@ | ||
/*! micro-ed25519-hdkey - MIT License (c) 2022 Paul Miller (paulmillr.com) */ | ||
import { ed25519 } from '@noble/curves/ed25519'; | ||
@@ -81,2 +82,3 @@ import { hmac } from '@noble/hashes/hmac'; | ||
const parts = path.replace(/^[mM]'?\//, '').split('/'); | ||
// tslint:disable-next-line | ||
let child = this; | ||
@@ -90,2 +92,3 @@ for (const c of parts) { | ||
throw new Error('Invalid index'); | ||
// hardened key | ||
if (forceHardened || m[2] === "'") | ||
@@ -100,2 +103,3 @@ idx += HARDENED_OFFSET; | ||
throw new Error(`Non-hardened child derivation not possible for Ed25519 (index=${index})`); | ||
// Hardened child: 0x00 || ser256(kpar) || ser32(index) | ||
const data = concatBytes(ZERO, this.privateKey, toU32(index)); | ||
@@ -102,0 +106,0 @@ const I = hmac(sha512, this.chainCode, data); |
@@ -1,2 +0,1 @@ | ||
export {}; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -0,2 +1,2 @@ | ||
"use strict"; | ||
throw new Error('The module has no entry-point: consult README for usage'); | ||
export {}; |
15
ipns.js
@@ -5,5 +5,9 @@ import { ed25519 } from '@noble/curves/ed25519'; | ||
const base36 = utils.chain(utils.radix(36), utils.alphabet('0123456789abcdefghijklmnopqrstuvwxyz'), utils.padding(0), utils.join('')); | ||
// Formats IPNS public key in bytes array format to 'ipns://k...' string format | ||
export function formatPublicKey(pubBytes) { | ||
return `ipns://k${base36.encode(pubBytes)}`; | ||
} | ||
// Takes an IPNS pubkey (address) string as input and returns bytes array of the key | ||
// Supports various formats ('ipns://k', 'ipns://b', 'ipns://f') | ||
// Handles decoding and validation of the key before returning pubkey bytes | ||
export function parseAddress(address) { | ||
@@ -15,5 +19,7 @@ address = address.toLowerCase(); | ||
if (address.startsWith('k')) { | ||
// Decode base-36 pubkey (after removing 'k' prefix) and encode it as a hex string | ||
hexKey = hex.encode(base36.decode(address.slice(1))); | ||
} | ||
else if (address.startsWith('b')) { | ||
// Decode base-32 pubkey (after removing 'b' prefix) and encode it as a hex string | ||
hexKey = hex.encode(base32.decode(address.slice(1).toUpperCase())); | ||
@@ -25,14 +31,21 @@ } | ||
else | ||
throw new Error('Unsupported Base-X Format'); | ||
throw new Error('Unsupported Base-X Format'); // Throw error if pubkey format is not supported | ||
// Check if hexKey has expected prefix '0172002408011220' and length of 80 | ||
if (hexKey.startsWith('0172002408011220') && hexKey.length === 80) { | ||
return hex.decode(hexKey); | ||
} | ||
// Throw error if IPNS key prefix is invalid | ||
throw new Error('Invalid IPNS Key Prefix: ' + hexKey); | ||
} | ||
// Generates an ed25519 pubkey from a seed and converts it to several IPNS pubkey formats | ||
export async function getKeys(seed) { | ||
//? privKey "seed" should be checked for <ed25519.curve.n? | ||
if (seed.length != 32) | ||
throw new TypeError('Seed must be 32 bytes in length'); | ||
// Generate ed25519 public key from seed | ||
const pubKey = await ed25519.getPublicKey(seed); | ||
// Create public key bytes by concatenating prefix bytes and pubKey | ||
const pubKeyBytes = concatBytes(new Uint8Array([0x01, 0x72, 0x00, 0x24, 0x08, 0x01, 0x12, 0x20]), pubKey); | ||
const hexKey = hex.encode(pubKeyBytes).toLowerCase(); | ||
// Return different representations of the keys | ||
return { | ||
@@ -39,0 +52,0 @@ publicKey: `0x${hexKey}`, |
{ | ||
"name": "ed25519-keygen", | ||
"version": "0.4.10", | ||
"version": "0.4.11", | ||
"description": "Generate ed25519 keys for SSH, PGP (GPG), TOR, IPNS and SLIP-0010 hdkey", | ||
@@ -39,11 +39,11 @@ "type": "module", | ||
"dependencies": { | ||
"@noble/curves": "~1.2.0", | ||
"@noble/hashes": "~1.3.2", | ||
"@scure/base": "~1.1.2", | ||
"micro-packed": "^0.3.2" | ||
"@noble/curves": "~1.3.0", | ||
"@noble/hashes": "~1.3.3", | ||
"@scure/base": "~1.1.5", | ||
"micro-packed": "~0.5.2" | ||
}, | ||
"devDependencies": { | ||
"micro-should": "0.4.0", | ||
"prettier": "2.8.4", | ||
"typescript": "5.0.2" | ||
"prettier": "3.1.1", | ||
"typescript": "5.3.2" | ||
}, | ||
@@ -50,0 +50,0 @@ "exports": { |
148
pgp.d.ts
@@ -7,102 +7,56 @@ import * as P from 'micro-packed'; | ||
export declare const PacketLen: P.BytesCoderStream<number> & P.BytesCoder<number>; | ||
export declare const PubKeyPacket: P.CoderType<{ | ||
export declare const PubKeyPacket: P.CoderType<P.StructInput<{ | ||
version: undefined; | ||
created: number; | ||
algo: { | ||
TAG: "ECDH"; | ||
data: { | ||
curve: string; | ||
pub: bigint; | ||
params: { | ||
hash: string; | ||
encryption: string; | ||
} & {}; | ||
} & {}; | ||
} | { | ||
TAG: "EdDSA"; | ||
data: { | ||
curve: string; | ||
pub: bigint; | ||
} & {}; | ||
}; | ||
} & {}>; | ||
declare const SecretKeyPacket: P.CoderType<{ | ||
type: { | ||
TAG: "encrypted"; | ||
data: { | ||
secret: Uint8Array; | ||
enc: string; | ||
S2K: { | ||
TAG: "simple"; | ||
data: { | ||
hash: string; | ||
} & {}; | ||
} | { | ||
TAG: "salted"; | ||
data: { | ||
hash: string; | ||
salt: Uint8Array; | ||
} & {}; | ||
} | { | ||
TAG: "iterated"; | ||
data: { | ||
hash: string; | ||
salt: Uint8Array; | ||
count: number; | ||
} & {}; | ||
}; | ||
iv: Uint8Array; | ||
} & {}; | ||
} | { | ||
TAG: "plain"; | ||
data: { | ||
secret: Uint8Array; | ||
} & {}; | ||
} | { | ||
TAG: "encrypted2"; | ||
data: { | ||
secret: Uint8Array; | ||
enc: string; | ||
S2K: { | ||
TAG: "simple"; | ||
data: { | ||
hash: string; | ||
} & {}; | ||
} | { | ||
TAG: "salted"; | ||
data: { | ||
hash: string; | ||
salt: Uint8Array; | ||
} & {}; | ||
} | { | ||
TAG: "iterated"; | ||
data: { | ||
hash: string; | ||
salt: Uint8Array; | ||
count: number; | ||
} & {}; | ||
}; | ||
iv: Uint8Array; | ||
} & {}; | ||
}; | ||
pub: { | ||
created: number; | ||
algo: { | ||
TAG: "ECDH"; | ||
data: { | ||
curve: string; | ||
pub: bigint; | ||
params: { | ||
hash: string; | ||
encryption: string; | ||
} & {}; | ||
} & {}; | ||
} | { | ||
algo: P.Values<{ | ||
EdDSA: { | ||
TAG: "EdDSA"; | ||
data: { | ||
curve: string; | ||
pub: bigint; | ||
} & {}; | ||
data: P.StructInput<{ | ||
curve: any; | ||
pub: any; | ||
}>; | ||
}; | ||
} & {}; | ||
} & {}>; | ||
ECDH: { | ||
TAG: "ECDH"; | ||
data: P.StructInput<{ | ||
curve: any; | ||
pub: any; | ||
params: any; | ||
}>; | ||
}; | ||
}>; | ||
}>>; | ||
declare const SecretKeyPacket: P.CoderType<P.StructInput<{ | ||
pub: P.StructInput<{ | ||
version: any; | ||
created: any; | ||
algo: any; | ||
}>; | ||
type: P.Values<{ | ||
plain: { | ||
TAG: "plain"; | ||
data: P.StructInput<{ | ||
secret: any; | ||
}>; | ||
}; | ||
encrypted: { | ||
TAG: "encrypted"; | ||
data: P.StructInput<{ | ||
enc: any; | ||
S2K: any; | ||
iv: any; | ||
secret: any; | ||
}>; | ||
}; | ||
encrypted2: { | ||
TAG: "encrypted2"; | ||
data: P.StructInput<{ | ||
enc: any; | ||
S2K: any; | ||
iv: any; | ||
secret: any; | ||
}>; | ||
}; | ||
}>; | ||
}>>; | ||
type SecretKeyType = P.UnwrapCoder<typeof SecretKeyPacket>; | ||
@@ -109,0 +63,0 @@ export declare const Stream: P.CoderType<any[]>; |
55
pgp.js
@@ -12,2 +12,9 @@ import { ed25519, x25519 } from '@noble/curves/ed25519'; | ||
import * as P from 'micro-packed'; | ||
// RFCS: | ||
// - main: https://datatracker.ietf.org/doc/html/rfc4880 | ||
// - ecdh: https://datatracker.ietf.org/doc/html/rfc6637 | ||
// - ed25519: https://www.ietf.org/archive/id/draft-koch-eddsa-for-openpgp-04.txt | ||
// - bis: https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.1 | ||
// Safari supports AES_CFB via webCrypto, but chromium/firefox do not. | ||
// Test page: https://diafygi.github.io/webcrypto-examples/ | ||
const BLOCK_LEN = 16; | ||
@@ -49,2 +56,4 @@ const IV = new Uint8Array(BLOCK_LEN); | ||
} | ||
// PGP Types | ||
// Multiprecision Integers [RFC4880](https://datatracker.ietf.org/doc/html/rfc4880) | ||
export const mpi = P.wrap({ | ||
@@ -60,2 +69,7 @@ encodeStream: (w, value) => { | ||
}); | ||
// GnuGP violates spec by using non-zero stripped MPI's for secret keys (opaque MPI/SOS). | ||
// We need to do the same to create equal keys. | ||
// More info: | ||
// - https://www.mhonarc.org/archive/html/ietf-openpgp/2019-10/msg00041.html | ||
// - https://marc.info/?l=gnupg-devel&m=161518990118244&w=2 | ||
export const opaquempi = P.wrap({ | ||
@@ -68,4 +82,7 @@ encodeStream: (w, value) => { | ||
}); | ||
const OID_MSB = 2 ** 7; | ||
const OID_NO_MSB = 2 ** 7 - 1; | ||
// ASN.1 OID (object identifier) without tag & length | ||
// First two elements: [i0 * 40 + i1]. | ||
// Others: split in groups of 7 bit chunks, add 0x80 every byte except last(stop flag), like utf8. | ||
const OID_MSB = 2 ** 7; // mask for 8 bit | ||
const OID_NO_MSB = 2 ** 7 - 1; // mask for all bits except 8 | ||
export const oid = P.wrap({ | ||
@@ -132,3 +149,5 @@ encodeStream: (w, value) => { | ||
}); | ||
const PGP_PACKET_VERSION = P.magic(P.hex(1), '04'); | ||
// PGP Structures | ||
const PGP_PACKET_VERSION = P.magic(P.hex(1), '04'); // only version 4 is supported | ||
// Other (RSA/ElGamal/etc) is unsupported | ||
const pubKeyEnum = P.map(P.U8, { | ||
@@ -185,2 +204,3 @@ ECDH: 18, | ||
}); | ||
// bis4880 | ||
const AEADEnum = P.map(P.U8, { | ||
@@ -191,2 +211,3 @@ None: 0, | ||
}); | ||
// https://datatracker.ietf.org/doc/html/rfc4880#section-3.7.1 | ||
const S2KEnum = P.map(P.U8, { simple: 0, salted: 1, iterated: 3 }); | ||
@@ -198,2 +219,3 @@ const S2K = P.tag(S2KEnum, { | ||
}); | ||
// https://datatracker.ietf.org/doc/html/rfc6637#section-9 | ||
const ECDSAPub = P.struct({ curve: ECEnum, pub: mpi }); | ||
@@ -223,5 +245,7 @@ const ECDHPub = P.struct({ | ||
S2K, | ||
// IV as blocksize of algo. For AES it is 16 bytes, others is not supported | ||
iv: P.bytes(16), | ||
secret: P.bytes(null), | ||
}); | ||
// NOTE: SecretKey is specific packet type as per spec. For user facing API we using 'privateKey' | ||
const SecretKeyPacket = P.struct({ | ||
@@ -231,6 +255,9 @@ pub: PubKeyPacket, | ||
plain: [0x00, PlainSecretKey], | ||
// Skipping 'Any other value is a symmetric-key encryption algorithm identifier.' | ||
encrypted: [254, EncryptedSecretKey], | ||
// Same as above, but secret is with checksum | ||
encrypted2: [255, EncryptedSecretKey], | ||
}), | ||
}); | ||
// https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.1 | ||
const SigTypeEnum = P.map(P.U8, { | ||
@@ -253,2 +280,3 @@ binary: 0x00, | ||
}); | ||
// https://datatracker.ietf.org/doc/html/rfc4880.html#section-5.2.3.1 | ||
const signatureSubpacket = P.map(P.U8, { | ||
@@ -279,2 +307,3 @@ signatureCreationTime: 2, | ||
embeddedSignature: 32, | ||
// https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.1 | ||
issuerFingerprint: 33, | ||
@@ -320,7 +349,10 @@ preferredAEADAlgorithms: 34, | ||
hashPrefix: P.bytes(2), | ||
// 2: ec + dsa, 1 for rsa | ||
sig: P.array(null, mpi), | ||
}); | ||
const UserPacket = P.string(null); | ||
// PGP Functions | ||
const EXPBIAS6 = (count) => (16 + (count & 15)) << ((count >> 4) + 6); | ||
function deriveKey(hash, len, password, salt, count) { | ||
// Important: there is difference between zero and empty count | ||
count = count === undefined ? 0 : EXPBIAS6(count); | ||
@@ -335,2 +367,3 @@ const data = salt ? concatBytes(salt, password) : password; | ||
const h = hashC.create(); | ||
// prefix | ||
if (r > 0) | ||
@@ -352,2 +385,3 @@ h.update(new Uint8Array(r)); | ||
}; | ||
// https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.4 | ||
const hashTail = new Uint8Array([0x04, 0xff]); | ||
@@ -381,2 +415,3 @@ const hashPubKey = P.struct({ | ||
const getKeyId = (fp) => fp.slice(-16); | ||
// https://datatracker.ietf.org/doc/html/rfc4880#section-6.1 | ||
function crc24(data) { | ||
@@ -402,5 +437,9 @@ let crc = 0xb704ce; | ||
}; | ||
// https://datatracker.ietf.org/doc/html/rfc4880#section-4.2 | ||
// Old packet: [1, version: 0, tag(4), lenType(2)] -- 8 bits | ||
// New packet: [1, version: 1, tag(6)] + len(bytes) -> not supported, GPG generates version 0 for now | ||
const PacketHead = P.struct({ | ||
magic: P.magic(P.bits(1), 1), | ||
version: P.magic(P.bits(1), 0), | ||
// https://datatracker.ietf.org/doc/html/rfc4880#section-4.3 | ||
tag: P.map(P.bits(4), { | ||
@@ -442,2 +481,3 @@ public_key_encrypted_session_key: 1, | ||
export const Stream = P.array(null, Packet); | ||
// Key generation | ||
const EDSIGN = P.array(null, P.U256BE); | ||
@@ -452,2 +492,3 @@ async function signData(head, unhashed, data, privateKey) { | ||
const [data, checksum] = [secret.slice(0, -2), P.U16BE.decode(secret.slice(-2))]; | ||
// Wow, third checksum algorithm in single spec! | ||
let ourChecksum = 0; | ||
@@ -477,2 +518,3 @@ for (let i = 0; i < data.length; i++) | ||
throw new Error(`PGP.secretKey unsupported publicKey algorithm: ${key.pub.algo.TAG}`); | ||
// Decoded as generic MPI, not as OpaqueMPI | ||
if (key.type.TAG === 'encrypted2') | ||
@@ -515,2 +557,3 @@ return decodeSecretChecksum(decryptedKey); | ||
async function getCerts(edPriv, cvPriv, user, created = 0) { | ||
// key settings same as in PGP to avoid fingerprinting since they are part of public key | ||
const preferredEncryptionAlgorithms = ['aes256', 'aes192', 'aes128', 'tripledes']; | ||
@@ -572,2 +615,7 @@ const preferredHashAlgorithms = ['sha512', 'sha384', 'sha256', 'sha224', 'sha1']; | ||
} | ||
/* | ||
NOTE: gpg: warning: lower 3 bits of the secret key are not cleared | ||
happens even for keys generated with GnuPG 2.3.6, because check looks at item as Opaque MPI, when it is just MPI: | ||
https://dev.gnupg.org/rGdbfb7f809b89cfe05bdacafdb91a2d485b9fe2e0 | ||
*/ | ||
export async function getKeys(privKey, user, password, created = 0) { | ||
@@ -577,2 +625,3 @@ const { keyId } = await getPublicPackets(privKey, privKey, created); | ||
const publicKey = await formatPublic(privKey, cvPrivate, user, created); | ||
// The slow part | ||
const privateKey = await formatPrivate(privKey, cvPrivate, user, password, created); | ||
@@ -579,0 +628,0 @@ return { keyId, privateKey, publicKey }; |
45
ssh.d.ts
@@ -5,31 +5,30 @@ import * as P from 'micro-packed'; | ||
export declare const SSHKeyType: P.CoderType<undefined>; | ||
export declare const PublicKey: P.CoderType<{ | ||
export declare const PublicKey: P.CoderType<P.StructInput<{ | ||
keyType: undefined; | ||
pubKey: Uint8Array; | ||
} & {}>; | ||
export declare const AuthData: P.CoderType<{ | ||
}>>; | ||
export declare const AuthData: P.CoderType<P.StructInput<{ | ||
nonce: Uint8Array; | ||
auth: string; | ||
userAuthRequest: number; | ||
user: string; | ||
pubKey: { | ||
pubKey: Uint8Array; | ||
} & {}; | ||
userAuthRequest: number; | ||
conn: string; | ||
auth: string; | ||
haveSig: number; | ||
} & {}>; | ||
keyType: undefined; | ||
pubKey: P.StructInput<{ | ||
keyType: any; | ||
pubKey: any; | ||
}>; | ||
}>>; | ||
export type AuthDataType = P.UnwrapCoder<typeof AuthData>; | ||
export declare const PrivateExport: P.Coder<{ | ||
keys: ({ | ||
pubKey: { | ||
pubKey: Uint8Array; | ||
} & {}; | ||
privKey: { | ||
pubKey: Uint8Array; | ||
check1: Uint8Array; | ||
check2: Uint8Array; | ||
privKey: Uint8Array; | ||
comment: string; | ||
} & {}; | ||
} & {})[]; | ||
} & {}, string>; | ||
export declare const PrivateExport: P.Coder<P.StructInput<{ | ||
magic: undefined; | ||
ciphername: undefined; | ||
kdfname: undefined; | ||
kdfopts: undefined; | ||
keys: P.StructInput<{ | ||
pubKey: any; | ||
privKey: any; | ||
}>[]; | ||
}>, string>; | ||
export declare function formatPublicKey(bytes: Uint8Array, comment?: string): string; | ||
@@ -36,0 +35,0 @@ export declare function getFingerprint(bytes: Uint8Array): string; |
11
ssh.js
@@ -18,9 +18,10 @@ import { ed25519 } from '@noble/curves/ed25519'; | ||
}), (i) => i + 1); | ||
// https://tools.ietf.org/html/draft-miller-ssh-agent-02#section-4.5 | ||
export const AuthData = P.struct({ | ||
nonce: SSHBuf, | ||
userAuthRequest: P.U8, | ||
userAuthRequest: P.U8, // == 50 | ||
user: SSHString, | ||
conn: SSHString, | ||
auth: SSHString, | ||
haveSig: P.U8, | ||
haveSig: P.U8, // == 1 | ||
keyType: SSHKeyType, | ||
@@ -31,2 +32,3 @@ pubKey: P.prefix(P.U32BE, PublicKey), | ||
magic: P.magicBytes('openssh-key-v1\0'), | ||
// Only decrypted ed25519 keys supported for now | ||
ciphername: P.magic(SSHString, 'none'), | ||
@@ -46,4 +48,7 @@ kdfname: P.magic(SSHString, 'none'), | ||
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 async function getKeys(privateKey, comment, checkBytes = randomBytes(4)) { | ||
@@ -60,2 +65,3 @@ const pubKey = await ed25519.getPublicKey(privateKey); | ||
privKey: { | ||
// Check bytes, should be same | ||
check1: checkBytes, | ||
@@ -72,2 +78,3 @@ check2: checkBytes, | ||
} | ||
// For SSH Agents | ||
export function authSign(privateKey, data) { | ||
@@ -74,0 +81,0 @@ return ed25519.sign(AuthData.encode(data), privateKey); |
@@ -7,3 +7,5 @@ import { ed25519 } from '@noble/curves/ed25519'; | ||
export function formatPublicKey(pubBytes) { | ||
// checksum = H(".onion checksum" || pubkey || version) | ||
const checksum = sha3_256(concatBytes(utf8.decode('.onion checksum'), pubBytes, ADDRESS_VERSION)); | ||
// onion_address = base32(pubkey || checksum || version); | ||
const addr = concatBytes(pubBytes, checksum.slice(0, 2), ADDRESS_VERSION); | ||
@@ -16,2 +18,3 @@ return `${base32.encode(addr).toLowerCase()}.onion`; | ||
const addr = base32.decode(address.replace(/\.onion$/, '').toUpperCase()); | ||
// skip last 3 bytes | ||
const skip = addr.slice(0, addr.length - 3); | ||
@@ -18,0 +21,0 @@ const key = formatPublicKey(skip); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
92761
2030
+ Added@noble/curves@1.3.0(transitive)
+ Addedmicro-packed@0.5.3(transitive)
- Removed@noble/curves@1.2.0(transitive)
- Removed@noble/hashes@1.3.2(transitive)
- Removedmicro-packed@0.3.2(transitive)
Updated@noble/curves@~1.3.0
Updated@noble/hashes@~1.3.3
Updated@scure/base@~1.1.5
Updatedmicro-packed@~0.5.2