Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add module: "key management" #9

Merged
merged 20 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
aa0da02
Add key management module
abhi3700 May 30, 2024
5e0641d
Add tests along with keyToPem function with examples
abhi3700 May 31, 2024
b87753e
Add more functions to auto-id & its arch diagram
abhi3700 Jun 3, 2024
c6282db
Add new functions & organize tests removing redundant code snippets
abhi3700 Jun 3, 2024
8ac9d7b
Add all the functions of key module & also added a few tests
abhi3700 Jun 3, 2024
ff5d538
Add tests for saveKey function
abhi3700 Jun 4, 2024
40cd653
Add tests for loading private/public keys
abhi3700 Jun 4, 2024
c2a154e
Add some examples for showing usage of functions of key management mo…
abhi3700 Jun 4, 2024
2ad656c
Add a README for Auto ID package
abhi3700 Jun 4, 2024
2b4ef3e
Removed .vscode from ignoring & Add index.ts
abhi3700 Jun 4, 2024
7613079
shift the relevant package to dependencies
abhi3700 Jun 4, 2024
80ab436
Removed the examples as it's already included in tests
abhi3700 Jun 4, 2024
53733ed
Remove examples' scripts from package.json
abhi3700 Jun 4, 2024
fdbe0ac
Remove unncessary inclusion of tests/ folder
abhi3700 Jun 4, 2024
c446b07
Merge branch 'main' into add-key-module
abhi3700 Jun 4, 2024
67d9744
Merge branch 'main' of https://github.com/subspace/auto-sdk into add-…
abhi3700 Jun 5, 2024
d679460
Modify saveKey function & its tests of auto-id package
abhi3700 Jun 5, 2024
1d9cf2f
Incorporate save function from auto-utils into Load key function & tests
abhi3700 Jun 5, 2024
f7c1adc
Modify load key functions considering the dynamic read function added…
abhi3700 Jun 5, 2024
d825e21
Update lock file
abhi3700 Jun 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
abhi3700 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading