@synet/keys
Version:
Zero-dependency, secure key generation library. Supports ed25519, x25519, secp256k1, RSA, and WireGuard keys.
475 lines (474 loc) • 17 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateKeyPair = generateKeyPair;
exports.derivePublicKey = derivePublicKey;
exports.getShortId = getShortId;
exports.getFingerprint = getFingerprint;
exports.pemToHex = pemToHex;
exports.pemPrivateKeyToHex = pemPrivateKeyToHex;
exports.hexToPem = hexToPem;
exports.base64ToHex = base64ToHex;
exports.hexToBase64 = hexToBase64;
exports.detectKeyFormat = detectKeyFormat;
exports.toHex = toHex;
exports.hexPrivateKeyToPem = hexPrivateKeyToPem;
const crypto = __importStar(require("node:crypto"));
const verify_1 = require("./verify");
/**
* Generate a cryptographic key pair
* @param type The type of key to generate
* @param options Optional configuration for key generation
* @returns A key pair object containing the private and public keys
* @throws Error if the key type is unsupported
*/
function generateKeyPair(type, options) {
const format = options?.format || "pem";
try {
switch (type) {
case "rsa":
return generateRsaKeyPair(format);
case "ed25519":
return generateEd25519KeyPair(format);
case "x25519":
return generateX25519KeyPair(format);
case "secp256k1":
return generateSecp256k1KeyPair(format);
case "wireguard":
return generateWireguardKeyPair();
default:
throw new Error(`Unsupported key type: ${type}`);
}
}
catch (error) {
throw new Error(`Failed to generate ${type} key pair: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Generate RSA key pair (2048-bit)
*/
function generateRsaKeyPair(format) {
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" },
});
return {
publicKey: publicKey.toString(),
privateKey: privateKey.toString(),
type: "rsa"
};
}
/**
* Generate Ed25519 key pair (digital signatures)
*/
function generateEd25519KeyPair(format) {
if (format === "hex" || format === "base64") {
// For hex/base64 format, we'll use DER generation and extract the raw bytes
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519", {
publicKeyEncoding: { type: "spki", format: "der" },
privateKeyEncoding: { type: "pkcs8", format: "der" },
});
// Extract raw 32-byte keys from DER format
const privateKeyRaw = Buffer.from(privateKey).subarray(-32); // Last 32 bytes
const publicKeyRaw = Buffer.from(publicKey).subarray(-32); // Last 32 bytes
return {
privateKey: format === "base64" ? privateKeyRaw.toString("base64") : privateKeyRaw.toString("hex"),
publicKey: format === "base64" ? publicKeyRaw.toString("base64") : publicKeyRaw.toString("hex"),
type: "ed25519"
};
}
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519", {
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" },
});
return {
publicKey: publicKey.toString(),
privateKey: privateKey.toString(),
type: "ed25519"
};
}
/**
* Generate X25519 key pair (key exchange, used by WireGuard)
*/
function generateX25519KeyPair(format) {
if (format === "hex" || format === "base64") {
// For hex/base64 format, we'll use DER generation and extract the raw bytes
const { publicKey, privateKey } = crypto.generateKeyPairSync("x25519", {
publicKeyEncoding: { type: "spki", format: "der" },
privateKeyEncoding: { type: "pkcs8", format: "der" },
});
// Extract raw 32-byte keys from DER format
const privateKeyRaw = Buffer.from(privateKey).subarray(-32); // Last 32 bytes
const publicKeyRaw = Buffer.from(publicKey).subarray(-32); // Last 32 bytes
return {
privateKey: format === "base64" ? privateKeyRaw.toString("base64") : privateKeyRaw.toString("hex"),
publicKey: format === "base64" ? publicKeyRaw.toString("base64") : publicKeyRaw.toString("hex"),
type: "x25519"
};
}
const { publicKey, privateKey } = crypto.generateKeyPairSync("x25519", {
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" },
});
return {
publicKey: publicKey.toString(),
privateKey: privateKey.toString(),
type: "x25519"
};
}
/**
* Generate secp256k1 key pair (Bitcoin/Ethereum style)
*/
function generateSecp256k1KeyPair(format) {
const { publicKey, privateKey } = crypto.generateKeyPairSync("ec", {
namedCurve: "secp256k1",
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" },
});
return {
publicKey: publicKey.toString(),
privateKey: privateKey.toString(),
type: "secp256k1"
};
}
/**
* Generate WireGuard-compatible key pair (X25519 with base64 encoding)
*/
function generateWireguardKeyPair() {
// WireGuard uses X25519 keys in base64 format
const { publicKey, privateKey } = crypto.generateKeyPairSync("x25519", {
publicKeyEncoding: { type: "spki", format: "der" },
privateKeyEncoding: { type: "pkcs8", format: "der" },
});
// Extract raw 32-byte keys from DER format for WireGuard compatibility
const privateKeyRaw = Buffer.from(privateKey).subarray(-32); // Last 32 bytes
const publicKeyRaw = Buffer.from(publicKey).subarray(-32); // Last 32 bytes
return {
privateKey: privateKeyRaw.toString("base64"),
publicKey: publicKeyRaw.toString("base64"),
type: "wireguard"
};
}
/**
* Extract the public key from a private key
* @param privateKey The private key in PEM or hex format
* @returns The corresponding public key in the same format, or null if extraction fails
*/
function derivePublicKey(privateKey) {
try {
if (!privateKey) {
return null;
}
// Check if it's PEM format
if (privateKey.includes("-----BEGIN") && privateKey.includes("-----END")) {
// Create a KeyObject from the private key PEM
const privateKeyObj = crypto.createPrivateKey({
key: privateKey,
format: "pem",
});
// Derive the public key from the private key
const publicKey = crypto.createPublicKey(privateKeyObj).export({
type: "spki",
format: "pem",
});
return publicKey.toString();
}
// Check if it's hex format
if (/^[0-9a-fA-F]+$/.test(privateKey.trim())) {
// For hex format, we need to convert to PEM first, derive, then convert back
// This is a simplified approach - hex keys need proper reconstruction
// For now, return null as hex derivation is not implemented
return null;
}
return null;
}
catch (error) {
console.error("Failed to derive public key:", error);
return null;
}
}
/**
* Compute a short identifier from a public key
* @param publicKey The public key in PEM format
* @returns A 16-character hexadecimal identifier
*/
function getShortId(publicKey) {
const hash = crypto.createHash("sha256").update(publicKey).digest("hex");
return hash.substring(0, 16);
}
/**
* Compute a fingerprint from a public key
* @param publicKey The public key in PEM format
* @returns A 64-character hexadecimal fingerprint
*/
function getFingerprint(publicKey) {
return crypto.createHash("sha256").update(publicKey).digest("hex");
}
/**
* Key format conversion utilities
*/
/**
* Convert PEM key to hex format
* @param pemKey PEM formatted key
* @returns Hex string or null if conversion fails
*/
function pemToHex(pemKey) {
try {
if (!pemKey || !pemKey.includes("-----BEGIN")) {
return null;
}
// Import the PEM key and export as DER
const keyObj = crypto.createPublicKey({
key: pemKey,
format: "pem",
});
const der = keyObj.export({
type: "spki",
format: "der",
});
// Extract the raw key bytes from DER format
// For Ed25519 and X25519, the key is the last 32 bytes of the SPKI structure
const derBuffer = Buffer.from(der);
// SPKI structure for Ed25519: 30 2a 30 05 06 03 2b 65 70 03 21 00 [32 bytes key]
// SPKI structure for X25519: 30 2a 30 05 06 03 2b 65 6e 03 21 00 [32 bytes key]
// For these curves, we can extract the last 32 bytes
if (derBuffer.length >= 32) {
const keyBytes = derBuffer.subarray(-32);
return keyBytes.toString("hex");
}
return null;
}
catch (error) {
console.error("Failed to convert PEM to hex:", error);
return null;
}
}
/**
* Convert PEM private key to hex format
* @param pemKey The PEM-encoded private key
* @returns Hex string or null if conversion fails
*/
function pemPrivateKeyToHex(pemKey) {
try {
if (!pemKey || !pemKey.includes("-----BEGIN PRIVATE KEY-----")) {
return null;
}
// Import the PEM private key
const privateKeyObj = crypto.createPrivateKey({
key: pemKey,
format: "pem",
});
// Export as DER format
const der = privateKeyObj.export({
type: "pkcs8",
format: "der",
});
const derBuffer = Buffer.from(der);
// For Ed25519 private keys, extract the 32-byte private key from PKCS8 DER
// PKCS8 structure for Ed25519: ... [header] ... 04 20 [32 bytes private key]
if (derBuffer.length >= 32) {
// Find the OCTET STRING containing the private key (look for 04 20 pattern)
for (let i = 0; i < derBuffer.length - 33; i++) {
if (derBuffer[i] === 0x04 && derBuffer[i + 1] === 0x20) {
const privateKeyBytes = derBuffer.subarray(i + 2, i + 34);
return privateKeyBytes.toString("hex");
}
}
}
return null;
}
catch (error) {
console.error("Failed to convert PEM private key to hex:", error);
return null;
}
}
/**
* Convert hex key to PEM format
* @param hexKey Hex string key
* @param keyType Key type for proper PEM formatting
* @returns PEM formatted key or null if conversion fails
*/
function hexToPem(hexKey, keyType) {
try {
if (!hexKey || !/^[0-9a-fA-F]+$/.test(hexKey)) {
return null;
}
const keyBuffer = Buffer.from(hexKey, "hex");
// For Ed25519 and X25519, we need to construct the proper DER format
if (keyType === "ed25519" || keyType === "x25519") {
// Create a proper DER-encoded public key
const algorithmOID = keyType === "ed25519"
? Buffer.from([0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00])
: Buffer.from([0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00]);
const derKey = Buffer.concat([algorithmOID, keyBuffer]);
const publicKeyObj = crypto.createPublicKey({
key: derKey,
format: "der",
type: "spki",
});
return publicKeyObj.export({
type: "spki",
format: "pem",
}).toString();
}
return null;
}
catch (error) {
console.error("Failed to convert hex to PEM:", error);
return null;
}
}
/**
* Convert base64 key to hex format
* @param base64Key Base64 encoded key
* @returns Hex string or null if conversion fails
*/
function base64ToHex(base64Key) {
try {
if (!base64Key) {
return null;
}
// Validate base64 format first
if (!(0, verify_1.isValidBase64)(base64Key)) {
return null;
}
const buffer = Buffer.from(base64Key, "base64");
return buffer.toString("hex");
}
catch (error) {
console.error("Failed to convert base64 to hex:", error);
return null;
}
}
/**
* Convert hex key to base64 format
* @param hexKey Hex string key
* @returns Base64 encoded key or null if conversion fails
*/
function hexToBase64(hexKey) {
try {
if (!hexKey || !/^[0-9a-fA-F]+$/.test(hexKey)) {
return null;
}
const buffer = Buffer.from(hexKey, "hex");
return buffer.toString("base64");
}
catch (error) {
console.error("Failed to convert hex to base64:", error);
return null;
}
}
/**
* Detect key format
* @param key Key in any format
* @returns Detected format or null if unknown
*/
function detectKeyFormat(key) {
if (!key || typeof key !== "string") {
return null;
}
// Check for PEM format
if (key.includes("-----BEGIN") && key.includes("-----END")) {
return "pem";
}
// Check for hex format
if (/^[0-9a-fA-F]+$/.test(key.trim())) {
return "hex";
}
// Check for base64 format (basic check)
if (/^[A-Za-z0-9+/]+=*$/.test(key.trim())) {
return "base64";
}
return null;
}
/**
* Convert key to hex format regardless of input format
* @param key Key in any format
* @param keyType Key type (needed for PEM conversion)
* @returns Hex string or null if conversion fails
*/
function toHex(key, keyType) {
const format = detectKeyFormat(key);
switch (format) {
case "hex":
return key.toLowerCase();
case "base64":
return base64ToHex(key);
case "pem":
return pemToHex(key);
default:
return null;
}
}
/**
* Convert Ed25519 private key from hex to PEM format
* @param hexKey The hex-encoded private key (64 bytes: 32 private + 32 public)
* @returns PEM formatted private key or null if conversion fails
*/
function hexPrivateKeyToPem(hexKey) {
try {
if (!hexKey || !/^[0-9a-fA-F]+$/.test(hexKey)) {
return null;
}
// Ed25519 private keys are 64 bytes (32 private + 32 public)
// Extract the private key part (first 32 bytes)
const privateKeyBytes = Buffer.from(hexKey.substring(0, 64), 'hex');
// Create PKCS8 DER format for Ed25519 private key
const pkcs8Header = Buffer.from([
0x30, 0x2e, // SEQUENCE, 46 bytes
0x02, 0x01, 0x00, // INTEGER version 0
0x30, 0x05, // SEQUENCE, 5 bytes
0x06, 0x03, 0x2b, 0x65, 0x70, // OID for Ed25519
0x04, 0x22, // OCTET STRING, 34 bytes
0x04, 0x20 // OCTET STRING, 32 bytes (the actual private key)
]);
const derKey = Buffer.concat([pkcs8Header, privateKeyBytes]);
// Create private key object and export as PEM
const privateKeyObj = crypto.createPrivateKey({
key: derKey,
format: 'der',
type: 'pkcs8'
});
return privateKeyObj.export({
type: 'pkcs8',
format: 'pem'
}).toString();
}
catch (error) {
console.error("Failed to convert hex private key to PEM:", error);
return null;
}
}