Skip to content

Commit

Permalink
Merge pull request #9 from subspace/add-key-module
Browse files Browse the repository at this point in the history
Add module: "Key Management"
  • Loading branch information
abhi3700 authored Jun 5, 2024
2 parents b53e8a6 + d825e21 commit 0252e96
Show file tree
Hide file tree
Showing 12 changed files with 836 additions and 18 deletions.
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/auto-id/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
preset: 'ts-jest',
}
13 changes: 12 additions & 1 deletion packages/auto-id/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,24 @@
"version": "0.1.0",
"main": "dist/index.js",
"scripts": {
"build": "tsc"
"build": "tsc",
"clean": "rm -rf dist",
"format": "prettier --write \"src/**/*.ts\"",
"test": "jest"
},
"dependencies": {
"@autonomys/auto-utils": "workspace:*",
"@types/node": "^20.12.12"
},
"files": [
"dist",
"README.md"
],
"devDependencies": {
"@types/jest": "^29.5.12",
"jest": "^29.7.0",
"ts-jest": "^29.1.4",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
}
}
4 changes: 1 addition & 3 deletions packages/auto-id/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
export const helloWorld = () => {
console.log('Hello World');
};
export * from './keyManagement'
289 changes: 289 additions & 0 deletions packages/auto-id/src/keyManagement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
import { read, save } from '@autonomys/auto-utils'
import { KeyObject, createPrivateKey, createPublicKey, generateKeyPairSync } from 'crypto'

/**
* Generates an RSA key pair.
* @param keySize The size of the key in bits. Default is 2048.
* @returns A tuple containing the the RSA private key and public key.
*/
export function generateRsaKeyPair(keySize: number = 2048): [string, string] {
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
modulusLength: keySize,
publicExponent: 65537,
// TODO: Need to select the correct type - `"pkcs1" | "spki"`
publicKeyEncoding: { type: 'spki', format: 'pem' },
// TODO: Need to select the correct type - `"pkcs1" | "pkcs8"`
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
})

return [privateKey, publicKey]
}

/**
* Generates an Ed25519 key pair.
* @returns A tuple containing the Ed25519 private key and public key.
*/
export function generateEd25519KeyPair(): [string, string] {
const { publicKey, privateKey } = generateKeyPairSync('ed25519', {
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
})

return [privateKey, publicKey]
}

/**
* Converts a cryptographic key object into a PEM formatted string.
* This function can handle both private and public key objects.
* For private keys, it supports optional encryption using a passphrase.
*
* @param key The cryptographic key object to be exported. It must be either a private or public key object.
* @param password Optional passphrase for encrypting the private key. If provided, the private key
* will be exported in an encrypted form using AES-256-CBC cipher. This parameter is ignored
* for public keys.
*
* @returns Returns the PEM formatted string of the key. If a private key is provided with a passphrase,
* it returns the key in an encrypted format. Otherwise, it returns an unencrypted PEM.
*
* @throws Will throw an error if the provided `key` is neither a private nor a public key object.
*
* @example
* Follow "../examples/eg3.ts" & "../examples/eg4.ts" for a complete example.
* // Create a private key object (assuming you have the appropriate private key data)
* const privateKey = createPrivateKey({
* key: '-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANB ...',
* format: 'pem'
* });
*
* // Create a public key object from the private key
* const publicKey = createPublicKey(privateKey);
*
* // Export the private key without encryption
* console.log(keyToPem(privateKey));
*
* // Export the private key with encryption
* console.log(keyToPem(privateKey, 'your-secure-password'));
*
* // Export the public key
* console.log(keyToPem(publicKey));
*/
export function keyToPem(key: KeyObject, password?: string): string {
if (key.asymmetricKeyType) {
// Handle private key
if (key.type === 'private') {
const options: any = {
type: 'pkcs8' as 'pkcs8', // type for private keys
format: 'pem' as 'pem', // Output format set to 'pem'
}
// If a password is provided, apply encryption
if (password) {
options.cipher = 'aes-256-cbc' // Cipher type
options.passphrase = password // Passphrase as a string
}
return key.export(options) as string
}
// Handle public key
else if (key.type === 'public') {
const options = {
type: 'spki' as 'spki', // type for public keys
format: 'pem' as 'pem', // Output format set to 'pem'
}
return key.export(options) as string
}
}
throw new Error('Invalid key type. Key must be a private or public key object.')
}

/**
* Saves a cryptographic key object to a file in PEM format. If it's a private key and a password is provided,
* the key will be encrypted before being written to the file.
*
* @param key The cryptographic key object to be saved. It must be either a private or public key object.
* @param filePath The file system path where the key should be saved.
* @param password Optional password for encrypting the private key.
*
* @example
* // Assuming privateKey is a valid KeyObject
* saveKey(privateKey, './myPrivateKey.pem', 'strongpassword')
* .then(() => console.log('Key saved successfully'))
* .catch(err => console.error('Error saving key:', err));
*/
export async function saveKey(key: KeyObject, filePath: string, password?: string): Promise<void> {
try {
const pem = keyToPem(key, password)
await save(filePath, pem)
} catch (e: any) {
throw new Error(`Failed to save key: ${e.message}`)
}
}

/**
* Converts a PEM-encoded string to a private key object. If the PEM string is encrypted,
* a password must be provided to decrypt it.
*
* @param pemData The PEM string to convert to a private key.
* @param password Optional password used to decrypt the encrypted PEM string.
* @returns The private key object.
*
* @example
* const pemString = '-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFDjBABgkqhk ...';
* const privateKey = pemToPrivateKey(pemString, 'mypassword');
* console.log(privateKey);
*/
export function pemToPrivateKey(pemData: string, password?: string): KeyObject {
const options: any = {
key: pemData,
format: 'pem' as 'pem',
}

// Add password to options if it is provided
if (password) {
options.passphrase = password
}

return createPrivateKey(options)
}

/**
* Loads a private key from a file. If the file is encrypted, a password must be provided.
*
* @param filePath Path to the private key file.
* @param password Optional password used to decrypt the encrypted key file.
* @returns The private key object.
*
* @example
* async function main() {
* try {
* const privateKey = await loadPrivateKey('./path/to/private/key.pem', 'optional-password');
* console.log('Private Key:', privateKey);
* } catch (error) {
* console.error('Error loading private key:', error);
* }
* }
*
* main();
*/
export async function loadPrivateKey(filePath: string, password?: string): Promise<KeyObject> {
try {
const keyData = await read(filePath)
const privateKey = pemToPrivateKey(keyData, password)
return privateKey;
} catch (error: any) {
throw new Error(`Failed to load private key: ${error.message}`)
}
}

/**
* Converts a PEM-encoded string to a public key object.
*
* @param pemData The PEM string to convert to a public key.
* @returns The public key object.
*
* @example
* const pemString = '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...';
* const publicKey = pemToPublicKey(pemString);
* console.log('Public Key:', publicKey);
*/
export function pemToPublicKey(pemData: string): KeyObject {
return createPublicKey({
key: pemData,
format: 'pem' as 'pem',
})
}

/**
* Loads a public key from a file.
*
* @param filePath Path to the public key file.
* @returns The public key object.
*
* @example
* async function main() {
* try {
* const publicKey = await loadPublicKey('./path/to/public/key.pem');
* console.log('Public Key:', publicKey);
* } catch (error) {
* console.error('Error loading public key:', error);
* }
* }
*
* main();
*/
export async function loadPublicKey(filePath: string): Promise<KeyObject> {
try {
const keyData = await read(filePath)
const publicKey = pemToPublicKey(keyData)
return publicKey
} catch (error: any) {
throw new Error(`Failed to load public key: ${error.message}`)
}
}

/**
* Converts a private or public key to a hex string representation.
*
* @param key The key to convert (either a private or public key).
* @returns The hex string representation of the key.
*
* @example
* const keyHex = keyToHex(privateKeyObject); // privateKeyObject should be a valid KeyObject
* console.log('Key Hex:', keyHex);
*/
export function keyToHex(key: KeyObject): string {
let keyData: Buffer

// Check the type of the key to determine how to handle it
if (key.type === 'private') {
// Convert private key to DER format
keyData = key.export({
type: 'pkcs8',
format: 'der',
})
} else if (key.type === 'public') {
// Convert public key to DER format
keyData = key.export({
type: 'spki',
format: 'der',
})
} else {
throw new Error('Unsupported key type')
}

// Convert the binary data to a hexadecimal string
return keyData.toString('hex')
}

/**
* Checks if two public keys match.
*
* @param publicKey1 The first public key as a KeyObject.
* @param publicKey2 The second public key as a KeyObject.
* @returns True if the keys match, False otherwise.
*
* @example
* const key1 = createPublicKey({
* key: publicKeyPem1,
* format: 'pem'
* });
* const key2 = createPublicKey({
* key: publicKeyPem2,
* format: 'pem'
* });
* const match = doPublicKeysMatch(key1, key2);
* console.log('Keys match:', match);
*/
export function doPublicKeysMatch(publicKey1: KeyObject, publicKey2: KeyObject): boolean {
// Serialize both public keys to DER format for comparison
const publicKey1Der = publicKey1.export({
type: 'spki',
format: 'der',
})

const publicKey2Der = publicKey2.export({
type: 'spki',
format: 'der',
})

// Compare the serialized public key data
return publicKey1Der.equals(publicKey2Der)
}
Loading

0 comments on commit 0252e96

Please sign in to comment.