Compare commits

...

8 Commits

Author SHA1 Message Date
Jacob Heun
8b3dc56dc2 chore: release version v0.17.9 2020-08-05 17:18:44 +02:00
Jacob Heun
7888afada6 chore: update contributors 2020-08-05 17:18:43 +02:00
Jacob Heun
7273739f04 feat: add exporting/importing of non rsa keys in libp2p-key format (#179)
* feat: add exporting/importing of ed25519 keys in libp2p-key format

* feat: add libp2p-key export/import support for rsa and secp keys

* chore: dep bumps

* chore: update aegir

* refactor: import and export base64 strings

* refactor: simplify api for now

* chore: fix lint

* refactor: remove extraneous param

* refactor: clean up

* fix: review patches
2020-08-05 17:14:12 +02:00
Jacob Heun
609297be65 chore: release version v0.17.8 2020-07-20 15:23:49 +02:00
Jacob Heun
89a297793d chore: update contributors 2020-07-20 15:23:49 +02:00
Jacob Heun
32fae9b505 chore: fix lint 2020-07-20 15:13:46 +02:00
Jacob Heun
c2dd0a535d chore(types): fix typing 2020-07-20 15:13:46 +02:00
Jacob Heun
2f18a077b4 fix: go ed25519 interop
fixes https://github.com/libp2p/js-libp2p-crypto/issues/175
2020-07-20 15:13:46 +02:00
20 changed files with 498 additions and 87 deletions

View File

@@ -1,3 +1,23 @@
<a name="0.17.9"></a>
## [0.17.9](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.8...v0.17.9) (2020-08-05)
### Features
* add exporting/importing of non rsa keys in libp2p-key format ([#179](https://github.com/libp2p/js-libp2p-crypto/issues/179)) ([7273739](https://github.com/libp2p/js-libp2p-crypto/commit/7273739))
<a name="0.17.8"></a>
## [0.17.8](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.7...v0.17.8) (2020-07-20)
### Bug Fixes
* go ed25519 interop ([2f18a07](https://github.com/libp2p/js-libp2p-crypto/commit/2f18a07))
<a name="0.17.7"></a>
## [0.17.7](https://github.com/libp2p/js-libp2p-crypto/compare/v0.17.6...v0.17.7) (2020-06-09)

View File

@@ -262,14 +262,23 @@ Returns `Promise<RsaPrivateKey|Ed25519PrivateKey|Secp256k1PrivateKey>`
Converts a protobuf serialized private key into its representative object.
### `crypto.keys.import(pem, password)`
### `crypto.keys.import(encryptedKey, password)`
- `pem: string`
- `encryptedKey: string`
- `password: string`
Returns `Promise<RsaPrivateKey>`
Returns `Promise<PrivateKey>`
Converts a PEM password protected private key into its representative object.
Converts an exported private key into its representative object. Supported formats are 'pem' (RSA only) and 'libp2p-key'.
### `privateKey.export(password, format)`
- `password: string`
- `format: string` the format to export to: 'pem' (rsa only), 'libp2p-key'
Returns `string`
Exports the password protected `PrivateKey`. RSA keys will be exported as password protected PEM by default. Ed25519 and Secp256k1 keys will be exported as password protected AES-GCM base64 encoded strings ('libp2p-key' format).
### `crypto.randomBytes(number)`

View File

@@ -1,14 +1,15 @@
{
"name": "libp2p-crypto",
"version": "0.17.7",
"version": "0.17.9",
"description": "Crypto primitives for libp2p",
"main": "src/index.js",
"types": "src/index.d.ts",
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
"browser": {
"./src/aes/ciphers.js": "./src/aes/ciphers-browser.js",
"./src/ciphers/aes-gcm.js": "./src/ciphers/aes-gcm.browser.js",
"./src/hmac/index.js": "./src/hmac/index-browser.js",
"./src/keys/ecdh.js": "./src/keys/ecdh-browser.js",
"./src/aes/ciphers.js": "./src/aes/ciphers-browser.js",
"./src/keys/rsa.js": "./src/keys/rsa-browser.js"
},
"files": [
@@ -43,21 +44,22 @@
"is-typedarray": "^1.0.0",
"iso-random-stream": "^1.1.0",
"keypair": "^1.0.1",
"multibase": "^0.7.0",
"multibase": "^1.0.1",
"multicodec": "^1.0.4",
"multihashing-async": "^0.8.1",
"node-forge": "^0.9.1",
"pem-jwk": "^2.0.0",
"protons": "^1.0.1",
"protons": "^1.2.1",
"secp256k1": "^4.0.0",
"ursa-optional": "~0.10.1"
"uint8arrays": "^1.0.0",
"ursa-optional": "^0.10.1"
},
"devDependencies": {
"@types/chai": "^4.2.11",
"@types/chai": "^4.2.12",
"@types/chai-string": "^1.4.2",
"@types/dirty-chai": "^2.0.2",
"@types/mocha": "^7.0.1",
"@types/sinon": "^9.0.0",
"aegir": "^22.0.0",
"@types/mocha": "^8.0.1",
"aegir": "^25.0.0",
"benchmark": "^2.1.4",
"chai": "^4.2.0",
"chai-string": "^1.5.0",

View File

@@ -0,0 +1,89 @@
'use strict'
const concat = require('uint8arrays/concat')
const fromString = require('uint8arrays/from-string')
const webcrypto = require('../webcrypto')
// Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples
/**
*
* @param {object} [options]
* @param {string} [options.algorithm=AES-GCM]
* @param {Number} [options.nonceLength=12]
* @param {Number} [options.keyLength=16]
* @param {string} [options.digest=sha256]
* @param {Number} [options.saltLength=16]
* @param {Number} [options.iterations=32767]
* @returns {*}
*/
function create ({
algorithm = 'AES-GCM',
nonceLength = 12,
keyLength = 16,
digest = 'SHA-256',
saltLength = 16,
iterations = 32767
} = {}) {
const crypto = webcrypto.get()
keyLength *= 8 // Browser crypto uses bits instead of bytes
/**
* Uses the provided password to derive a pbkdf2 key. The key
* will then be used to encrypt the data.
*
* @param {Uint8Array} data The data to decrypt
* @param {string} password A plain password
* @returns {Promise<Uint8Array>}
*/
async function encrypt (data, password) { // eslint-disable-line require-await
const salt = crypto.getRandomValues(new Uint8Array(saltLength))
const nonce = crypto.getRandomValues(new Uint8Array(nonceLength))
const aesGcm = { name: algorithm, iv: nonce }
// Derive a key using PBKDF2.
const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } }
const rawKey = await crypto.subtle.importKey('raw', fromString(password), { name: 'PBKDF2' }, false, ['deriveKey', 'deriveBits'])
const cryptoKey = await crypto.subtle.deriveKey(deriveParams, rawKey, { name: algorithm, length: keyLength }, true, ['encrypt'])
// Encrypt the string.
const ciphertext = await crypto.subtle.encrypt(aesGcm, cryptoKey, data)
return concat([salt, aesGcm.iv, new Uint8Array(ciphertext)])
}
/**
* Uses the provided password to derive a pbkdf2 key. The key
* will then be used to decrypt the data. The options used to create
* this decryption cipher must be the same as those used to create
* the encryption cipher.
*
* @param {Uint8Array} data The data to decrypt
* @param {string} password A plain password
* @returns {Promise<Uint8Array>}
*/
async function decrypt (data, password) {
const salt = data.slice(0, saltLength)
const nonce = data.slice(saltLength, saltLength + nonceLength)
const ciphertext = data.slice(saltLength + nonceLength)
const aesGcm = { name: algorithm, iv: nonce }
// Derive the key using PBKDF2.
const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } }
const rawKey = await crypto.subtle.importKey('raw', fromString(password), { name: 'PBKDF2' }, false, ['deriveKey', 'deriveBits'])
const cryptoKey = await crypto.subtle.deriveKey(deriveParams, rawKey, { name: algorithm, length: keyLength }, true, ['decrypt'])
// Decrypt the string.
const plaintext = await crypto.subtle.decrypt(aesGcm, cryptoKey, ciphertext)
return new Uint8Array(plaintext)
}
return {
encrypt,
decrypt
}
}
module.exports = {
create
}

120
src/ciphers/aes-gcm.js Normal file
View File

@@ -0,0 +1,120 @@
'use strict'
const crypto = require('crypto')
// Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples
/**
*
* @param {object} [options]
* @param {Number} [options.algorithmTagLength=16]
* @param {Number} [options.nonceLength=12]
* @param {Number} [options.keyLength=16]
* @param {string} [options.digest=sha256]
* @param {Number} [options.saltLength=16]
* @param {Number} [options.iterations=32767]
* @returns {*}
*/
function create ({
algorithmTagLength = 16,
nonceLength = 12,
keyLength = 16,
digest = 'sha256',
saltLength = 16,
iterations = 32767
} = {}) {
const algorithm = 'aes-128-gcm'
/**
*
* @private
* @param {Buffer} data
* @param {Buffer} key
* @returns {Promise<Buffer>}
*/
async function encryptWithKey (data, key) { // eslint-disable-line require-await
const nonce = crypto.randomBytes(nonceLength)
// Create the cipher instance.
const cipher = crypto.createCipheriv(algorithm, key, nonce)
// Encrypt and prepend nonce.
const ciphertext = Buffer.concat([cipher.update(data), cipher.final()])
return Buffer.concat([nonce, ciphertext, cipher.getAuthTag()])
}
/**
* Uses the provided password to derive a pbkdf2 key. The key
* will then be used to encrypt the data.
*
* @param {Buffer} data The data to decrypt
* @param {string|Buffer} password A plain password
* @returns {Promise<Buffer>}
*/
async function encrypt (data, password) { // eslint-disable-line require-await
// Generate a 128-bit salt using a CSPRNG.
const salt = crypto.randomBytes(saltLength)
// Derive a key using PBKDF2.
const key = crypto.pbkdf2Sync(Buffer.from(password), salt, iterations, keyLength, digest)
// Encrypt and prepend salt.
return Buffer.concat([salt, await encryptWithKey(Buffer.from(data), key)])
}
/**
* Decrypts the given cipher text with the provided key. The `key` should
* be a cryptographically safe key and not a plaintext password. To use
* a plaintext password, use `decrypt`. The options used to create
* this decryption cipher must be the same as those used to create
* the encryption cipher.
*
* @private
* @param {Buffer} ciphertextAndNonce The data to decrypt
* @param {Buffer} key
* @returns {Promise<Buffer>}
*/
async function decryptWithKey (ciphertextAndNonce, key) { // eslint-disable-line require-await
// Create buffers of nonce, ciphertext and tag.
const nonce = ciphertextAndNonce.slice(0, nonceLength)
const ciphertext = ciphertextAndNonce.slice(nonceLength, ciphertextAndNonce.length - algorithmTagLength)
const tag = ciphertextAndNonce.slice(ciphertext.length + nonceLength)
// Create the cipher instance.
const cipher = crypto.createDecipheriv(algorithm, key, nonce)
// Decrypt and return result.
cipher.setAuthTag(tag)
return Buffer.concat([cipher.update(ciphertext), cipher.final()])
}
/**
* Uses the provided password to derive a pbkdf2 key. The key
* will then be used to decrypt the data. The options used to create
* this decryption cipher must be the same as those used to create
* the encryption cipher.
*
* @param {Buffer} data The data to decrypt
* @param {string|Buffer} password A plain password
*/
async function decrypt (data, password) { // eslint-disable-line require-await
// Create buffers of salt and ciphertextAndNonce.
const salt = data.slice(0, saltLength)
const ciphertextAndNonce = data.slice(saltLength)
// Derive the key using PBKDF2.
const key = crypto.pbkdf2Sync(Buffer.from(password), salt, iterations, keyLength, digest)
// Decrypt and return result.
return decryptWithKey(ciphertextAndNonce, key)
}
return {
encrypt,
decrypt
}
}
module.exports = {
create
}

29
src/index.d.ts vendored
View File

@@ -94,6 +94,10 @@ export interface PrivateKey {
* of the PKCS SubjectPublicKeyInfo.
*/
id(): Promise<string>;
/**
* Exports the password protected key in the format specified.
*/
export(password: string, format?: "pkcs-8" | string): Promise<string>;
}
export interface Keystretcher {
@@ -132,9 +136,6 @@ export namespace keys {
hash(): Promise<Buffer>;
}
// Type alias for export method
export type KeyInfo = any;
class RsaPrivateKey implements PrivateKey {
constructor(key: any, publicKey: Buffer);
readonly public: RsaPublicKey;
@@ -146,13 +147,7 @@ export namespace keys {
equals(key: PrivateKey): boolean;
hash(): Promise<Buffer>;
id(): Promise<string>;
/**
* Exports the key into a password protected PEM format
*
* @param password The password to read the encrypted PEM
* @param format Defaults to 'pkcs-8'.
*/
export(password: string, format?: "pkcs-8" | string): KeyInfo;
export(password: string, format?: string): Promise<string>;
}
function unmarshalRsaPublicKey(buf: Buffer): RsaPublicKey;
function unmarshalRsaPrivateKey(buf: Buffer): Promise<RsaPrivateKey>;
@@ -180,6 +175,7 @@ export namespace keys {
equals(key: PrivateKey): boolean;
hash(): Promise<Buffer>;
id(): Promise<string>;
export(password: string, format?: string): Promise<string>;
}
function unmarshalEd25519PrivateKey(
@@ -212,6 +208,7 @@ export namespace keys {
equals(key: PrivateKey): boolean;
hash(): Promise<Buffer>;
id(): Promise<string>;
export(password: string, format?: string): Promise<string>;
}
function unmarshalSecp256k1PrivateKey(
@@ -234,16 +231,14 @@ export namespace keys {
bits: number
): Promise<PrivateKey>;
export function generateKeyPair(
type: "Ed25519",
bits: number
type: "Ed25519"
): Promise<keys.supportedKeys.ed25519.Ed25519PrivateKey>;
export function generateKeyPair(
export function generateKeyPair(
type: "RSA",
bits: number
): Promise<keys.supportedKeys.rsa.RsaPrivateKey>;
export function generateKeyPair(
type: "secp256k1",
bits: number
export function generateKeyPair(
type: "secp256k1"
): Promise<keys.supportedKeys.secp256k1.Secp256k1PrivateKey>;
/**
@@ -318,7 +313,7 @@ export namespace keys {
* @param pem Password protected private key in PEM format.
* @param password The password used to protect the key.
*/
function _import(pem: string, password: string): Promise<supportedKeys.rsa.RsaPrivateKey>;
function _import(pem: string, password: string, format?: string): Promise<supportedKeys.rsa.RsaPrivateKey>;
export { _import as import };
}

View File

@@ -8,6 +8,7 @@ const errcode = require('err-code')
const crypto = require('./ed25519')
const pbm = protobuf(require('./keys.proto'))
const exporter = require('./exporter')
class Ed25519PublicKey {
constructor (key) {
@@ -86,12 +87,35 @@ class Ed25519PrivateKey {
const hash = await this.public.hash()
return multibase.encode('base58btc', hash).toString().slice(1)
}
/**
* Exports the key into a password protected `format`
*
* @param {string} password - The password to encrypt the key
* @param {string} [format=libp2p-key] - The format in which to export as
* @returns {Promise<Buffer>} The encrypted private key
*/
async export (password, format = 'libp2p-key') { // eslint-disable-line require-await
if (format === 'libp2p-key') {
return exporter.export(this.bytes, password)
} else {
throw errcode(new Error(`export format '${format}' is not supported`), 'ERR_INVALID_EXPORT_FORMAT')
}
}
}
function unmarshalEd25519PrivateKey (bytes) {
bytes = ensureKey(bytes, crypto.privateKeyLength + crypto.publicKeyLength)
// Try the old, redundant public key version
if (bytes.length > crypto.privateKeyLength) {
bytes = ensureKey(bytes, crypto.privateKeyLength + crypto.publicKeyLength)
const privateKeyBytes = bytes.slice(0, crypto.privateKeyLength)
const publicKeyBytes = bytes.slice(crypto.privateKeyLength, bytes.length)
return new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes)
}
bytes = ensureKey(bytes, crypto.privateKeyLength)
const privateKeyBytes = bytes.slice(0, crypto.privateKeyLength)
const publicKeyBytes = bytes.slice(crypto.privateKeyLength, bytes.length)
const publicKeyBytes = bytes.slice(crypto.publicKeyLength)
return new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes)
}
@@ -111,11 +135,9 @@ async function generateKeyPairFromSeed (seed) {
}
function ensureKey (key, length) {
if (Buffer.isBuffer(key)) {
key = new Uint8Array(key)
}
if (!(key instanceof Uint8Array) || key.length !== length) {
throw errcode(new Error('Key must be a Uint8Array or Buffer of length ' + length), 'ERR_INVALID_KEY_TYPE')
key = Uint8Array.from(key || [])
if (key.length !== length) {
throw errcode(new Error(`Key must be a Uint8Array or Buffer of length ${length}, got ${key.length}`), 'ERR_INVALID_KEY_TYPE')
}
return key
}

22
src/keys/exporter.js Normal file
View File

@@ -0,0 +1,22 @@
'use strict'
const multibase = require('multibase')
const ciphers = require('../ciphers/aes-gcm')
module.exports = {
/**
* Exports the given PrivateKey as a base64 encoded string.
* The PrivateKey is encrypted via a password derived PBKDF2 key
* leveraging the aes-gcm cipher algorithm.
*
* @param {Buffer} privateKey The PrivateKey protobuf buffer
* @param {string} password
* @returns {Promise<string>} A base64 encoded string
*/
export: async function (privateKey, password) {
const cipher = ciphers.create()
const encryptedKey = await cipher.encrypt(privateKey, password)
const base64 = multibase.names.base64
return base64.encode(encryptedKey)
}
}

22
src/keys/importer.js Normal file
View File

@@ -0,0 +1,22 @@
'use strict'
const multibase = require('multibase')
const ciphers = require('../ciphers/aes-gcm')
module.exports = {
/**
* Attempts to decrypt a base64 encoded PrivateKey string
* with the given password. The privateKey must have been exported
* using the same password and underlying cipher (aes-gcm)
*
* @param {string} privateKey A base64 encoded encrypted key
* @param {string} password
* @returns {Promise<Buffer>} The private key protobuf buffer
*/
import: async function (privateKey, password) {
const base64 = multibase.names.base64
const encryptedKey = base64.decode(privateKey)
const cipher = ciphers.create()
return await cipher.decrypt(encryptedKey, password)
}
}

View File

@@ -8,6 +8,8 @@ require('node-forge/lib/pbe')
const forge = require('node-forge/lib/forge')
const errcode = require('err-code')
const importer = require('./importer')
exports = module.exports
const supportedKeys = {
@@ -109,8 +111,21 @@ exports.marshalPrivateKey = (key, type) => {
return key.bytes
}
exports.import = async (pem, password) => { // eslint-disable-line require-await
const key = forge.pki.decryptRsaPrivateKey(pem, password)
/**
*
* @param {string} encryptedKey
* @param {string} password
*/
exports.import = async (encryptedKey, password) => { // eslint-disable-line require-await
try {
const key = await importer.import(encryptedKey, password)
return exports.unmarshalPrivateKey(key)
} catch (_) {
// Ignore and try the old pem decrypt
}
// Only rsa supports pem right now
const key = forge.pki.decryptRsaPrivateKey(encryptedKey, password)
if (key === null) {
throw errcode(new Error('Cannot read the key, most likely the password is wrong or not a RSA key'), 'ERR_CANNOT_DECRYPT_PEM')
}

View File

@@ -5,12 +5,14 @@ const protobuf = require('protons')
const multibase = require('multibase')
const errcode = require('err-code')
const crypto = require('./rsa')
const pbm = protobuf(require('./keys.proto'))
require('node-forge/lib/sha512')
require('node-forge/lib/ed25519')
const forge = require('node-forge/lib/forge')
const crypto = require('./rsa')
const pbm = protobuf(require('./keys.proto'))
const exporter = require('./exporter')
class RsaPublicKey {
constructor (key) {
this._key = key
@@ -109,28 +111,26 @@ class RsaPrivateKey {
* Exports the key into a password protected PEM format
*
* @param {string} password - The password to read the encrypted PEM
* @param {string} [format] - Defaults to 'pkcs-8'.
* @param {string} [format=pkcs-8] - The format in which to export as
*/
async export (password, format = 'pkcs-8') { // eslint-disable-line require-await
let pem = null
const buffer = new forge.util.ByteBuffer(this.marshal())
const asn1 = forge.asn1.fromDer(buffer)
const privateKey = forge.pki.privateKeyFromAsn1(asn1)
if (format === 'pkcs-8') {
const buffer = new forge.util.ByteBuffer(this.marshal())
const asn1 = forge.asn1.fromDer(buffer)
const privateKey = forge.pki.privateKeyFromAsn1(asn1)
const options = {
algorithm: 'aes256',
count: 10000,
saltSize: 128 / 8,
prfAlgorithm: 'sha512'
}
pem = forge.pki.encryptRsaPrivateKey(privateKey, password, options)
return forge.pki.encryptRsaPrivateKey(privateKey, password, options)
} else if (format === 'libp2p-key') {
return exporter.export(this.bytes, password)
} else {
throw errcode(new Error(`Unknown export format '${format}'. Must be pkcs-8`), 'ERR_INVALID_EXPORT_FORMAT')
throw errcode(new Error(`export format '${format}' is not supported`), 'ERR_INVALID_EXPORT_FORMAT')
}
return pem
}
}

View File

@@ -8,6 +8,7 @@ const { bigIntegerToUintBase64url, base64urlToBigInteger } = require('./../util'
// Convert a PKCS#1 in ASN1 DER format to a JWK key
exports.pkcs1ToJwk = function (bytes) {
bytes = Buffer.from(bytes) // convert Uint8Arrays
const asn1 = forge.asn1.fromDer(bytes.toString('binary'))
const privateKey = forge.pki.privateKeyFromAsn1(asn1)

View File

@@ -2,6 +2,9 @@
const multibase = require('multibase')
const sha = require('multihashing-async/src/sha')
const errcode = require('err-code')
const exporter = require('./exporter')
module.exports = (keysProtobuf, randomBytes, crypto) => {
crypto = crypto || require('./secp256k1')(randomBytes)
@@ -78,13 +81,27 @@ module.exports = (keysProtobuf, randomBytes, crypto) => {
* The public key is a protobuf encoding containing a type and the DER encoding
* of the PKCS SubjectPublicKeyInfo.
*
* @param {function(Error, id)} callback
* @returns {undefined}
* @returns {Promise<string>}
*/
async id () {
const hash = await this.public.hash()
return multibase.encode('base58btc', hash).toString().slice(1)
}
/**
* Exports the key into a password protected `format`
*
* @param {string} password - The password to encrypt the key
* @param {string} [format=libp2p-key] - The format in which to export as
* @returns {Promise<string>} The encrypted private key
*/
async export (password, format = 'libp2p-key') { // eslint-disable-line require-await
if (format === 'libp2p-key') {
return exporter.export(this.bytes, password)
} else {
throw errcode(new Error(`export format '${format}' is not supported`), 'ERR_INVALID_EXPORT_FORMAT')
}
}
}
function unmarshalSecp256k1PrivateKey (bytes) {

View File

@@ -98,7 +98,7 @@ describe('AES-CTR', () => {
// @ts-check
/**
* @type {function(Cipher): void}
* @type {function(Cipher): Promise<void>}
*/
async function encryptAndDecrypt (cipher) {
const data = Buffer.alloc(100)

View File

@@ -2,9 +2,11 @@
'use strict'
const chai = require('chai')
const dirtyChai = require('dirty-chai')
chai.use(require('dirty-chai'))
const expect = chai.expect
chai.use(dirtyChai)
const { Buffer } = require('buffer')
const crypto = require('../')
const webcrypto = require('../src/webcrypto')

View File

@@ -2,28 +2,42 @@
const { Buffer } = require('buffer')
module.exports = {
// These were generated in a gore (https://github.com/motemen/gore) repl session:
// Generation code from https://github.com/libp2p/js-libp2p-crypto/issues/175#issuecomment-634467463
//
// :import github.com/libp2p/go-libp2p-crypto
// :import crypto/rand
// priv, pub, err := crypto.GenerateEd25519Key(rand.Reader)
// pubkeyBytes, err := pub.Bytes()
// privkeyBytes, err := priv.Bytes()
// data := []byte("hello! and welcome to some awesome crypto primitives")
// sig, err := priv.Sign(data)
// package main
//
// :import io/ioutil
// ioutil.WriteFile("/tmp/pubkey_go.bin", pubkeyBytes, 0644)
// // etc..
// import (
// "crypto/rand"
// "fmt"
// "strings"
// "github.com/libp2p/go-libp2p-core/crypto"
// )
// func main() {
// priv, pub, _ := crypto.GenerateEd25519Key(rand.Reader)
// pubkeyBytes, _ := pub.Bytes()
// privkeyBytes, _ := priv.Bytes()
// data := []byte("hello! and welcome to some awesome crypto primitives")
// sig, _ := priv.Sign(data)
// fmt.Println("{\n publicKey: Buffer.from(", strings.Replace(fmt.Sprint(pubkeyBytes), " ", ",", -1), "),")
// fmt.Println(" privateKey: Buffer.from(", strings.Replace(fmt.Sprint(privkeyBytes), " ", ",", -1), "),")
// fmt.Println(" data: Buffer.from(", strings.Replace(fmt.Sprint(data), " ", ",", -1), "),")
// fmt.Println(" signature: Buffer.from(", strings.Replace(fmt.Sprint(sig), " ", ",", -1), ")\n}")
// }
//
// Then loaded into a node repl and dumped to arrays with:
//
// var pubkey = Array.from(fs.readFileSync('/tmp/pubkey_go.bin'))
// console.log(JSON.stringify(pubkey))
verify: {
// The legacy key unnecessarily appends the publickey. (It's already included) See https://github.com/libp2p/js-libp2p-crypto/issues/175
redundantPubKey: {
privateKey: Buffer.from([8, 1, 18, 96, 201, 208, 1, 110, 176, 16, 230, 37, 66, 184, 149, 252, 78, 56, 206, 136, 2, 38, 118, 152, 226, 197, 117, 200, 54, 189, 156, 218, 184, 7, 118, 57, 233, 49, 221, 97, 164, 158, 241, 129, 73, 166, 225, 255, 193, 118, 22, 84, 55, 15, 249, 168, 225, 180, 198, 191, 14, 75, 187, 243, 150, 91, 232, 37, 233, 49, 221, 97, 164, 158, 241, 129, 73, 166, 225, 255, 193, 118, 22, 84, 55, 15, 249, 168, 225, 180, 198, 191, 14, 75, 187, 243, 150, 91, 232, 37]),
publicKey: Buffer.from([8, 1, 18, 32, 233, 49, 221, 97, 164, 158, 241, 129, 73, 166, 225, 255, 193, 118, 22, 84, 55, 15, 249, 168, 225, 180, 198, 191, 14, 75, 187, 243, 150, 91, 232, 37]),
data: Buffer.from([104, 101, 108, 108, 111, 33, 32, 97, 110, 100, 32, 119, 101, 108, 99, 111, 109, 101, 32, 116, 111, 32, 115, 111, 109, 101, 32, 97, 119, 101, 115, 111, 109, 101, 32, 99, 114, 121, 112, 116, 111, 32, 112, 114, 105, 109, 105, 116, 105, 118, 101, 115]),
signature: Buffer.from([7, 230, 175, 164, 228, 58, 78, 208, 62, 243, 73, 142, 83, 195, 176, 217, 166, 62, 41, 165, 168, 164, 75, 179, 163, 86, 102, 32, 18, 84, 150, 237, 39, 207, 213, 20, 134, 237, 50, 41, 176, 183, 229, 133, 38, 255, 42, 228, 68, 186, 100, 14, 175, 156, 243, 118, 125, 125, 120, 212, 124, 103, 252, 12])
},
verify: {
publicKey: Buffer.from([8, 1, 18, 32, 163, 176, 195, 47, 254, 208, 49, 5, 192, 102, 32, 63, 58, 202, 171, 153, 146, 164, 25, 212, 25, 91, 146, 26, 117, 165, 148, 6, 207, 90, 217, 126]),
privateKey: Buffer.from([8, 1, 18, 64, 232, 56, 175, 20, 240, 160, 19, 47, 92, 88, 115, 221, 164, 13, 36, 162, 158, 136, 247, 31, 29, 231, 76, 143, 12, 91, 193, 4, 88, 33, 67, 23, 163, 176, 195, 47, 254, 208, 49, 5, 192, 102, 32, 63, 58, 202, 171, 153, 146, 164, 25, 212, 25, 91, 146, 26, 117, 165, 148, 6, 207, 90, 217, 126]),
data: Buffer.from([104, 101, 108, 108, 111, 33, 32, 97, 110, 100, 32, 119, 101, 108, 99, 111, 109, 101, 32, 116, 111, 32, 115, 111, 109, 101, 32, 97, 119, 101, 115, 111, 109, 101, 32, 99, 114, 121, 112, 116, 111, 32, 112, 114, 105, 109, 105, 116, 105, 118, 101, 115]),
signature: Buffer.from([160, 125, 30, 62, 213, 189, 239, 92, 87, 76, 205, 169, 251, 149, 187, 57, 96, 85, 175, 213, 22, 132, 229, 60, 196, 18, 117, 194, 12, 174, 135, 31, 39, 168, 174, 103, 78, 55, 37, 222, 37, 172, 222, 239, 153, 63, 197, 152, 67, 167, 191, 215, 161, 212, 216, 163, 81, 77, 45, 228, 151, 79, 101, 1])
}
}

View File

@@ -85,6 +85,26 @@ describe('ed25519', function () {
expect(id).to.be.a('string')
})
it('should export a password encrypted libp2p-key', async () => {
const key = await crypto.keys.generateKeyPair('Ed25519')
const encryptedKey = await key.export('my secret')
// Import the key
const importedKey = await crypto.keys.import(encryptedKey, 'my secret')
expect(key.equals(importedKey)).to.equal(true)
})
it('should fail to import libp2p-key with wrong password', async () => {
const key = await crypto.keys.generateKeyPair('Ed25519')
const encryptedKey = await key.export('my secret', 'libp2p-key')
try {
await crypto.keys.import(encryptedKey, 'not my secret')
} catch (err) {
expect(err).to.exist()
return
}
expect.fail('should have thrown')
})
describe('key equals', () => {
it('equals itself', () => {
expect(
@@ -131,25 +151,28 @@ describe('ed25519', function () {
describe('go interop', () => {
// @ts-check
/**
* @type {PrivateKey}
*/
let privateKey
before(async () => {
const key = await crypto.keys.unmarshalPrivateKey(fixtures.verify.privateKey)
privateKey = key
})
it('verifies with data from go', async () => {
const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
const ok = await key.verify(fixtures.verify.data, fixtures.verify.signature)
expect(ok).to.eql(true)
})
it('verifies with data from go with redundant public key', async () => {
const key = crypto.keys.unmarshalPublicKey(fixtures.redundantPubKey.publicKey)
const ok = await key.verify(fixtures.redundantPubKey.data, fixtures.redundantPubKey.signature)
expect(ok).to.eql(true)
})
it('generates the same signature as go', async () => {
const sig = await privateKey.sign(fixtures.verify.data)
const key = await crypto.keys.unmarshalPrivateKey(fixtures.verify.privateKey)
const sig = await key.sign(fixtures.verify.data)
expect(sig).to.eql(fixtures.verify.signature)
})
it('generates the same signature as go with redundant public key', async () => {
const key = await crypto.keys.unmarshalPrivateKey(fixtures.redundantPubKey.privateKey)
const sig = await key.sign(fixtures.redundantPubKey.data)
expect(sig).to.eql(fixtures.redundantPubKey.signature)
})
})
})

View File

@@ -135,6 +135,24 @@ describe('RSA', function () {
expect(key.equals(clone)).to.eql(true)
})
it('should export a password encrypted libp2p-key', async () => {
const encryptedKey = await key.export('my secret', 'libp2p-key')
// Import the key
const importedKey = await crypto.keys.import(encryptedKey, 'my secret')
expect(key.equals(importedKey)).to.equal(true)
})
it('should fail to import libp2p-key with wrong password', async () => {
const encryptedKey = await key.export('my secret', 'libp2p-key')
try {
await crypto.keys.import(encryptedKey, 'not my secret')
} catch (err) {
expect(err).to.exist()
return
}
expect.fail('should have thrown')
})
it('needs correct password', async () => {
const pem = await key.export('another secret')
try {

View File

@@ -31,7 +31,7 @@ describe('secp256k1 keys', () => {
})
it('optionally accepts a `bits` argument when generating a key', async () => {
const _key = await secp256k1.generateKeyPair(256)
const _key = await secp256k1.generateKeyPair()
expect(_key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey)
})
@@ -63,6 +63,26 @@ describe('secp256k1 keys', () => {
expect(id).to.be.a('string')
})
it('should export a password encrypted libp2p-key', async () => {
const key = await crypto.keys.generateKeyPair('secp256k1')
const encryptedKey = await key.export('my secret')
// Import the key
const importedKey = await crypto.keys.import(encryptedKey, 'my secret')
expect(key.equals(importedKey)).to.equal(true)
})
it('should fail to import libp2p-key with wrong password', async () => {
const key = await crypto.keys.generateKeyPair('secp256k1')
const encryptedKey = await key.export('my secret', 'libp2p-key')
try {
await crypto.keys.import(encryptedKey, 'not my secret')
} catch (err) {
expect(err).to.exist()
return
}
expect.fail('should have thrown')
})
describe('key equals', () => {
it('equals itself', () => {
expect(key.equals(key)).to.eql(true)
@@ -71,7 +91,7 @@ describe('secp256k1 keys', () => {
})
it('not equals other key', async () => {
const key2 = await secp256k1.generateKeyPair(256)
const key2 = await secp256k1.generateKeyPair()
expect(key.equals(key2)).to.eql(false)
expect(key2.equals(key)).to.eql(false)
expect(key.public.equals(key2.public)).to.eql(false)

View File

@@ -6,7 +6,7 @@ const expect = chai.expect
// @ts-check
/**
* @type {function(any, string): void}
* @type {function(any, string): Promise<void>}
*/
const expectErrCode = async (p, code) => {
try {