Skip to content
This repository has been archived by the owner on Jul 21, 2023. It is now read-only.

Commit

Permalink
chore: add error codes (#155)
Browse files Browse the repository at this point in the history
* chore: add error codes

* chore: create errors with new Error()

* fix: better error testin

* refactor: simplify random bytes error checks
  • Loading branch information
dirkmc authored and jacobheun committed Jul 22, 2019
1 parent 2d15e71 commit 0b686d3
Show file tree
Hide file tree
Showing 23 changed files with 172 additions and 72 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"bn.js": "^5.0.0",
"browserify-aes": "^1.2.0",
"bs58": "^4.0.1",
"err-code": "^1.1.2",
"iso-random-stream": "^1.1.0",
"keypair": "^1.0.1",
"libp2p-crypto-secp256k1": "~0.4.0",
Expand Down
17 changes: 17 additions & 0 deletions src/aes/cipher-mode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict'

const errcode = require('err-code')

const CIPHER_MODES = {
16: 'aes-128-ctr',
32: 'aes-256-ctr'
}

module.exports = function (key) {
const mode = CIPHER_MODES[key.length]
if (!mode) {
const modes = Object.entries(CIPHER_MODES).map(([k, v]) => `${k} (${v})`).join(' / ')
throw errcode(new Error(`Invalid key length ${key.length} bytes. Must be ${modes}`), 'ERR_INVALID_KEY_LENGTH')
}
return mode
}
6 changes: 3 additions & 3 deletions src/aes/index-browser.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use strict'

const asm = require('asmcrypto.js')
const validateCipherMode = require('./cipher-mode')

exports.create = async function (key, iv) { // eslint-disable-line require-await
if (key.length !== 16 && key.length !== 32) {
throw new Error('Invalid key length')
}
// Throws an error if mode is invalid
validateCipherMode(key)

const enc = new asm.AES_CTR.Encrypt({
key: key,
Expand Down
12 changes: 2 additions & 10 deletions src/aes/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
'use strict'

const ciphers = require('./ciphers')

const CIPHER_MODES = {
16: 'aes-128-ctr',
32: 'aes-256-ctr'
}
const cipherMode = require('./cipher-mode')

exports.create = async function (key, iv) { // eslint-disable-line require-await
const mode = CIPHER_MODES[key.length]
if (!mode) {
throw new Error('Invalid key length')
}

const mode = cipherMode(key)
const cipher = ciphers.createCipheriv(mode, key, iv)
const decipher = ciphers.createDecipheriv(mode, key, iv)

Expand Down
6 changes: 5 additions & 1 deletion src/keys/ecdh-browser.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
'use strict'

const errcode = require('err-code')
const webcrypto = require('../webcrypto.js')
const BN = require('asn1.js').bignum
const { toBase64, toBn } = require('../util')
const validateCurveType = require('./validate-curve-type')

const bits = {
'P-256': 256,
Expand All @@ -11,6 +13,8 @@ const bits = {
}

exports.generateEphmeralKeyPair = async function (curve) {
validateCurveType(Object.keys(bits), curve)

const pair = await webcrypto.subtle.generateKey(
{
name: 'ECDH',
Expand Down Expand Up @@ -96,7 +100,7 @@ function unmarshalPublicKey (curve, key) {
const byteLen = curveLengths[curve]

if (!key.slice(0, 1).equals(Buffer.from([4]))) {
throw new Error('Invalid key format')
throw errcode(new Error('Cannot unmarshal public key - invalid key format'), 'ERR_INVALID_KEY_FORMAT')
}
const x = new BN(key.slice(1, byteLen + 1))
const y = new BN(key.slice(1 + byteLen))
Expand Down
6 changes: 3 additions & 3 deletions src/keys/ecdh.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

const crypto = require('crypto')
const validateCurveType = require('./validate-curve-type')

const curves = {
'P-256': 'prime256v1',
Expand All @@ -9,9 +10,8 @@ const curves = {
}

exports.generateEphmeralKeyPair = async function (curve) { // eslint-disable-line require-await
if (!curves[curve]) {
throw new Error(`Unkown curve: ${curve}`)
}
validateCurveType(Object.keys(curves), curve)

const ecdh = crypto.createECDH(curves[curve])
ecdh.generateKeys()

Expand Down
7 changes: 2 additions & 5 deletions src/keys/ed25519-class.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const multihashing = require('multihashing-async')
const protobuf = require('protons')
const bs58 = require('bs58')
const errcode = require('err-code')

const crypto = require('./ed25519')
const pbm = protobuf(require('./keys.proto'))
Expand Down Expand Up @@ -49,10 +50,6 @@ class Ed25519PrivateKey {
}

get public () {
if (!this._publicKey) {
throw new Error('public key not provided')
}

return new Ed25519PublicKey(this._publicKey)
}

Expand Down Expand Up @@ -117,7 +114,7 @@ function ensureKey (key, length) {
key = new Uint8Array(key)
}
if (!(key instanceof Uint8Array) || key.length !== length) {
throw new Error('Key must be a Uint8Array or Buffer of length ' + length)
throw errcode(new Error('Key must be a Uint8Array or Buffer of length ' + length), 'ERR_INVALID_KEY_TYPE')
}
return key
}
Expand Down
51 changes: 23 additions & 28 deletions src/keys/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require('node-forge/lib/asn1')
require('node-forge/lib/rsa')
require('node-forge/lib/pbe')
const forge = require('node-forge/lib/forge')
const errcode = require('err-code')

exports = module.exports

Expand All @@ -18,34 +19,34 @@ const supportedKeys = {
exports.supportedKeys = supportedKeys
exports.keysPBM = keysPBM

function isValidKeyType (keyType) {
const key = supportedKeys[keyType.toLowerCase()]
return key !== undefined
const ErrMissingSecp256K1 = {
message: 'secp256k1 support requires libp2p-crypto-secp256k1 package',
code: 'ERR_MISSING_PACKAGE'
}

function typeToKey (type) {
let key = supportedKeys[type.toLowerCase()]
if (!key) {
const supported = Object.keys(supportedKeys).join(' / ')
throw errcode(new Error(`invalid or unsupported key type ${type}. Must be ${supported}`), 'ERR_UNSUPPORTED_KEY_TYPE')
}
return key
}

exports.keyStretcher = require('./key-stretcher')
exports.generateEphemeralKeyPair = require('./ephemeral-keys')

// Generates a keypair of the given type and bitsize
exports.generateKeyPair = async (type, bits) => { // eslint-disable-line require-await
let key = supportedKeys[type.toLowerCase()]

if (!key) {
throw new Error('invalid or unsupported key type')
}

return key.generateKeyPair(bits)
return typeToKey(type).generateKeyPair(bits)
}

// Generates a keypair of the given type and bitsize
// seed is a 32 byte uint8array
exports.generateKeyPairFromSeed = async (type, seed, bits) => { // eslint-disable-line require-await
let key = supportedKeys[type.toLowerCase()]
if (!key) {
throw new Error('invalid or unsupported key type')
}
const key = typeToKey(type)
if (type.toLowerCase() !== 'ed25519') {
throw new Error('Seed key derivation is unimplemented for RSA or secp256k1')
throw errcode(new Error('Seed key derivation is unimplemented for RSA or secp256k1'), 'ERR_UNSUPPORTED_KEY_DERIVATION_TYPE')
}
return key.generateKeyPairFromSeed(seed, bits)
}
Expand All @@ -65,20 +66,17 @@ exports.unmarshalPublicKey = (buf) => {
if (supportedKeys.secp256k1) {
return supportedKeys.secp256k1.unmarshalSecp256k1PublicKey(data)
} else {
throw new Error('secp256k1 support requires libp2p-crypto-secp256k1 package')
throw errcode(new Error(ErrMissingSecp256K1.message), ErrMissingSecp256K1.code)
}
default:
throw new Error('invalid or unsupported key type')
typeToKey(decoded.Type) // throws because type is not supported
}
}

// Converts a public key object into a protobuf serialized public key
exports.marshalPublicKey = (key, type) => {
type = (type || 'rsa').toLowerCase()
if (!isValidKeyType(type)) {
throw new Error('invalid or unsupported key type')
}

typeToKey(type) // check type
return key.bytes
}

Expand All @@ -97,27 +95,24 @@ exports.unmarshalPrivateKey = async (buf) => { // eslint-disable-line require-aw
if (supportedKeys.secp256k1) {
return supportedKeys.secp256k1.unmarshalSecp256k1PrivateKey(data)
} else {
throw new Error('secp256k1 support requires libp2p-crypto-secp256k1 package')
throw errcode(new Error(ErrMissingSecp256K1.message), ErrMissingSecp256K1.code)
}
default:
throw new Error('invalid or unsupported key type')
typeToKey(decoded.Type) // throws because type is not supported
}
}

// Converts a private key object into a protobuf serialized private key
exports.marshalPrivateKey = (key, type) => {
type = (type || 'rsa').toLowerCase()
if (!isValidKeyType(type)) {
throw new Error('invalid or unsupported key type')
}

typeToKey(type) // check type
return key.bytes
}

exports.import = async (pem, password) => { // eslint-disable-line require-await
const key = forge.pki.decryptRsaPrivateKey(pem, password)
if (key === null) {
throw new Error('Cannot read the key, most likely the password is wrong or not a RSA key')
throw errcode(new Error('Cannot read the key, most likely the password is wrong or not a RSA key'), 'ERR_CANNOT_DECRYPT_PEM')
}
let der = forge.asn1.toDer(forge.pki.privateKeyToAsn1(key))
der = Buffer.from(der.getBytes(), 'binary')
Expand Down
6 changes: 4 additions & 2 deletions src/keys/key-stretcher.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const errcode = require('err-code')
const hmac = require('../hmac')

const cipherMap = {
Expand All @@ -23,11 +24,12 @@ module.exports = async (cipherType, hash, secret) => {
const cipher = cipherMap[cipherType]

if (!cipher) {
throw new Error('unkown cipherType passed')
const allowed = Object.keys(cipherMap).join(' / ')
throw errcode(new Error(`unknown cipher type '${cipherType}'. Must be ${allowed}`), 'ERR_INVALID_CIPHER_TYPE')
}

if (!hash) {
throw new Error('unkown hashType passed')
throw errcode(new Error(`missing hash type`), 'ERR_MISSING_HASH_TYPE')
}

const cipherKeySize = cipher.keySize
Expand Down
5 changes: 3 additions & 2 deletions src/keys/rsa-class.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const multihashing = require('multihashing-async')
const protobuf = require('protons')
const bs58 = require('bs58')
const errcode = require('err-code')

const crypto = require('./rsa')
const pbm = protobuf(require('./keys.proto'))
Expand Down Expand Up @@ -61,7 +62,7 @@ class RsaPrivateKey {

get public () {
if (!this._publicKey) {
throw new Error('public key not provided')
throw errcode(new Error('public key not provided'), 'ERR_PUBKEY_NOT_PROVIDED')
}

return new RsaPublicKey(this._publicKey)
Expand Down Expand Up @@ -123,7 +124,7 @@ class RsaPrivateKey {
}
pem = forge.pki.encryptRsaPrivateKey(privateKey, password, options)
} else {
throw new Error(`Unknown export format '${format}'`)
throw errcode(new Error(`Unknown export format '${format}'. Must be pkcs-8`), 'ERR_INVALID_EXPORT_FORMAT')
}

return pem
Expand Down
3 changes: 2 additions & 1 deletion src/keys/rsa.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

const crypto = require('crypto')
const errcode = require('err-code')
const randomBytes = require('../random-bytes')

let keypair
Expand Down Expand Up @@ -40,7 +41,7 @@ exports.generateKey = async function (bits) { // eslint-disable-line require-awa
// Takes a jwk key
exports.unmarshalPrivateKey = async function (key) { // eslint-disable-line require-await
if (!key) {
throw new Error('Key is invalid')
throw errcode(new Error('Missing key parameter'), 'ERR_MISSING_KEY')
}
return {
privateKey: key,
Expand Down
10 changes: 10 additions & 0 deletions src/keys/validate-curve-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict'

const errcode = require('err-code')

module.exports = function (curveTypes, type) {
if (!curveTypes.includes(type)) {
const names = curveTypes.join(' / ')
throw errcode(new Error(`Unknown curve: ${type}. Must be ${names}`), 'ERR_INVALID_CURVE')
}
}
4 changes: 3 additions & 1 deletion src/pbkdf2.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const forgePbkdf2 = require('node-forge/lib/pbkdf2')
const forgeUtil = require('node-forge/lib/util')
const errcode = require('err-code')

/**
* Maps an IPFS hash name to its node-forge equivalent.
Expand Down Expand Up @@ -29,7 +30,8 @@ const hashName = {
function pbkdf2 (password, salt, iterations, keySize, hash) {
const hasher = hashName[hash]
if (!hasher) {
throw new Error(`Hash '${hash}' is unknown or not supported`)
const types = Object.keys(hashName).join(' / ')
throw errcode(new Error(`Hash '${hash}' is unknown or not supported. Must be ${types}`), 'ERR_UNSUPPORTED_HASH_TYPE')
}
const dek = forgePbkdf2(
password,
Expand Down
9 changes: 5 additions & 4 deletions src/random-bytes.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
'use strict'
const randomBytes = require('iso-random-stream/src/random')
const errcode = require('err-code')

module.exports = function (number) {
if (!number || typeof number !== 'number') {
throw new Error('first argument must be a Number bigger than 0')
module.exports = function (length) {
if (isNaN(length) || length <= 0) {
throw errcode(new Error('random bytes length must be a Number bigger than 0'), 'ERR_INVALID_LENGTH')
}
return randomBytes(number)
return randomBytes(length)
}
7 changes: 7 additions & 0 deletions test/aes/aes.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const { expectErrCode } = require('../util')

const crypto = require('../../src')
const fixtures = require('./../fixtures/aes')
Expand Down Expand Up @@ -84,6 +85,12 @@ describe('AES-CTR', () => {
}
})
})

it('checks key length', () => {
const key = Buffer.alloc(5)
const iv = Buffer.alloc(16)
return expectErrCode(crypto.aes.create(key, iv), 'ERR_INVALID_KEY_LENGTH')
})
})

async function encryptAndDecrypt (cipher) {
Expand Down
Loading

0 comments on commit 0b686d3

Please sign in to comment.