diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 366d5be..0aee45b 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -33,7 +33,7 @@ jobs: - name: Install WasmEdge run: | VERSION=0.13.4 - curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | sudo bash -s -- -e all --version=$VERSION --plugins=wasi_nn-tensorflowlite -p /usr/local + curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | sudo bash -s -- -e all --version=$VERSION --plugins=wasi_nn-tensorflowlite --plugins=wasi_crypto -p /usr/local wget https://github.com/WasmEdge/WasmEdge/releases/download/$VERSION/WasmEdge-plugin-wasmedge_rustls-$VERSION-ubuntu20.04_x86_64.tar.gz sudo chmod +x /usr/local/lib/wasmedge @@ -191,8 +191,7 @@ jobs: run: | cargo test test_fs --target=wasm32-wasi --release - #- name: Node fs module test (maybe timeout) - # timeout-minutes: 5 - # continue-on-error: true - # run: | - # cargo test test_fs --target=wasm32-wasi --release -- --ignored + - name: Node crypto module test + #timeout-minutes: 10 + run: | + cargo test test_crypto --target=wasm32-wasi --release --features=nodejs_crypto diff --git a/Cargo.lock b/Cargo.lock index 3a5df7c..a04a4a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,7 +301,7 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1078,12 +1078,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "wasmedge_quickjs" version = "0.5.0-alpha" diff --git a/Cargo.toml b/Cargo.toml index c66d0fe..cdf51ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ env_logger = "0.10.0" rustls = { version = "0.21.0", optional = true } tokio-rustls-wasi = { version = "0.24.1", optional = true } webpki-roots = { version = "0.25.0", optional = true } +crypto-wasi = { version = "0.1.1", optional = true } [features] default = ["tls"] @@ -38,3 +39,4 @@ img = ["image", "imageproc"] tensorflow = ["img"] wasi_nn = ["img"] cjs = [] +nodejs_crypto = ["crypto-wasi"] diff --git a/modules/assert.js b/modules/assert.js index b3d0754..5498f15 100644 --- a/modules/assert.js +++ b/modules/assert.js @@ -248,6 +248,7 @@ function parseCode(code, offset) { } function getErrMessage(message, fn) { + return "assert.getErrMessage unsupported"; const tmpLimit = Error.stackTraceLimit; const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable(); // Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it diff --git a/modules/buffer.js b/modules/buffer.js index bc9c1e9..0534a2d 100644 --- a/modules/buffer.js +++ b/modules/buffer.js @@ -2328,4 +2328,13 @@ var kMaxLength = exports.kMaxLength; globalThis.Buffer = Buffer -export { Buffer, INSPECT_MAX_BYTES, exports as default, kMaxLength }; +class FastBuffer extends Uint8Array { + // Using an explicit constructor here is necessary to avoid relying on + // `Array.prototype[Symbol.iterator]`, which can be mutated by users. + // eslint-disable-next-line no-useless-constructor + constructor(bufferOrLength, byteOffset, length) { + super(bufferOrLength, byteOffset, length); + } +} + +export { Buffer, INSPECT_MAX_BYTES, exports as default, kMaxLength, FastBuffer }; diff --git a/modules/crypto.js b/modules/crypto.js new file mode 100644 index 0000000..279891f --- /dev/null +++ b/modules/crypto.js @@ -0,0 +1,285 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { ERR_CRYPTO_FIPS_FORCED, ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH, ERR_INVALID_ARG_TYPE } from "./internal/errors"; +import { crypto as constants } from "./internal_binding/constants"; +import { getOptionValue } from "./internal/options"; +import { isAnyArrayBuffer, isArrayBufferView } from "./internal/util/types"; +import { + timing_safe_equal, +} from "_node:crypto"; +function timingSafeEqual(a, b) { + if (!isAnyArrayBuffer(a) && !isArrayBufferView(a)) { + throw new ERR_INVALID_ARG_TYPE("buf1", ["ArrayBuffer", "Buffer", "TypedArray", "DataView"], a); + } + if (!isAnyArrayBuffer(b) && !isArrayBufferView(b)) { + throw new ERR_INVALID_ARG_TYPE("buf2", ["ArrayBuffer", "Buffer", "TypedArray", "DataView"], b); + } + if (a.byteLength != b.byteLength) { + throw new ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH(); + } + return timing_safe_equal(a.buffer, b.buffer); +} + +import { + checkPrime, + checkPrimeSync, + generatePrime, + generatePrimeSync, + randomBytes, + randomFill, + randomFillSync, + randomInt, + randomUUID, +} from "./internal/crypto/random"; +import { pbkdf2, pbkdf2Sync } from "./internal/crypto/pbkdf2"; +import { scrypt, scryptSync } from "./internal/crypto/scrypt"; +import { hkdf, hkdfSync } from "./internal/crypto/hkdf"; +/*import { + generateKey, + generateKeyPair, + generateKeyPairSync, + generateKeySync, +} from "./internal/crypto/keygen";*/ +import { + createPrivateKey, + createPublicKey, + createSecretKey, + KeyObject, +} from "./internal/crypto/keys";/* +import { + DiffieHellman, + diffieHellman, + DiffieHellmanGroup, + ECDH, +} from "./internal/crypto/diffiehellman";*/ +import { + Cipheriv, + Decipheriv, + getCipherInfo, + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt, +} from "./internal/crypto/cipher"; +/* +import { + Sign, + signOneShot, + Verify, + verifyOneShot, +} from "./internal/crypto/sig";*/ +import { Hash, Hmac } from "./internal/crypto/hash";/* +import { X509Certificate } from "./internal/crypto/x509"; +*/import { + getCiphers, + getCurves, + getHashes, + secureHeapUsed, + setEngine, +} from "./internal/crypto/util";/* +import Certificate from "./internal/crypto/certificate"; +*/ +const webcrypto = undefined; +const fipsForced = getOptionValue("--force-fips"); + +function createCipheriv(cipher, key, iv, options) { + return new Cipheriv(cipher, key, iv, options); +} + +function createDecipheriv(algorithm, key, iv, options) { + return new Decipheriv(algorithm, key, iv, options); +} +/* +function createDiffieHellman(sizeOrKey, keyEncoding, generator, generatorEncoding) { + return new DiffieHellman( + sizeOrKey, + keyEncoding, + generator, + generatorEncoding, + ); +} + +function createDiffieHellmanGroup(name) { + return new DiffieHellmanGroup(name); +} + +function createECDH(curve) { + return new ECDH(curve); +} +*/ +function createHash(hash, options) { + return new Hash(hash, options); +} + +function createHmac(hmac, key, options) { + return new Hmac(hmac, key, options); +} +/* +function createSign(algorithm, options) { + return new Sign(algorithm, options); +} + +function createVerify(algorithm, options) { + return new Verify(algorithm, options); +} +*/ +function setFipsForced(val) { + if (val) { + return; + } + + throw new ERR_CRYPTO_FIPS_FORCED(); +} + +function getFipsForced() { + return 1; +} + +Object.defineProperty(constants, "defaultCipherList", { + value: getOptionValue("--tls-cipher-list"), +}); +/* +const getDiffieHellman = createDiffieHellmanGroup; +*/ +function getFipsCrypto() { + throw new Error("crypto.getFipsCrypto is unimplemented") +} +function setFipsCrypto(_val) { + throw new Error("crypto.setFipsCrypto is unimplemented") +} +const getFips = fipsForced ? getFipsForced : getFipsCrypto; +const setFips = fipsForced ? setFipsForced : setFipsCrypto; +/* +const sign = signOneShot; +const verify = verifyOneShot; +*/ +export default { + /*Certificate,*/ + checkPrime, + checkPrimeSync, + Cipheriv, + constants, + createCipheriv, + createDecipheriv,/* + createDiffieHellman, + createDiffieHellmanGroup, + createECDH,*/ + createHash, + createHmac, + createPrivateKey, + createPublicKey, + createSecretKey,/* + createSign, + createVerify,*/ + Decipheriv,/* + DiffieHellman, + diffieHellman, + DiffieHellmanGroup, + ECDH, + generateKey, + generateKeyPair, + generateKeyPairSync, + generateKeySync,*/ + generatePrime, + generatePrimeSync, + getCipherInfo, + getCiphers, + getCurves,/* + getDiffieHellman,*/ + getFips, + getHashes, + Hash, + hkdf, + hkdfSync, + Hmac,/* + KeyObject,*/ + pbkdf2, + pbkdf2Sync, + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt, + randomBytes, + randomFill, + randomFillSync, + randomInt, + randomUUID, + scrypt, + scryptSync, + secureHeapUsed, + setEngine, + setFips,/* + Sign, + sign,*/ + timingSafeEqual, + /*Verify, + verify, + webcrypto, + X509Certificate,*/ +}; + +export { + /*Certificate,*/ + checkPrime, + checkPrimeSync, + Cipheriv, + constants, + createCipheriv, + createDecipheriv, + /*createDiffieHellman, + createDiffieHellmanGroup, + createECDH,*/ + createHash, + createHmac, + createPrivateKey, + createPublicKey, + createSecretKey,/* + createSign, + createVerify,*/ + Decipheriv,/* + DiffieHellman, + diffieHellman, + DiffieHellmanGroup, + ECDH, + generateKey, + generateKeyPair, + generateKeyPairSync, + generateKeySync,*/ + generatePrime, + generatePrimeSync, + getCipherInfo, + getCiphers, + getCurves,/* + getDiffieHellman,*/ + getFips, + getHashes, + Hash, + hkdf, + hkdfSync, + Hmac,/* + KeyObject,*/ + pbkdf2, + pbkdf2Sync, + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt, + randomBytes, + randomFill, + randomFillSync, + randomInt, + randomUUID, + scrypt, + scryptSync, + secureHeapUsed, + setEngine, + setFips, + /*Sign, + sign,*/ + timingSafeEqual, + /*Verify, + verify,*/ + webcrypto, + /*X509Certificate,*/ +}; diff --git a/modules/internal/crypto/certificate.js b/modules/internal/crypto/certificate.js new file mode 100644 index 0000000..c51b54a --- /dev/null +++ b/modules/internal/crypto/certificate.js @@ -0,0 +1,53 @@ +'use strict'; + +const { + certExportChallenge, + certExportPublicKey, + certVerifySpkac, +} = internalBinding('crypto'); + +const { + getArrayBufferOrView, +} = require('internal/crypto/util'); + +// The functions contained in this file cover the SPKAC format +// (also referred to as Netscape SPKI). A general description of +// the format can be found at https://en.wikipedia.org/wiki/SPKAC + +function verifySpkac(spkac, encoding) { + return certVerifySpkac( + getArrayBufferOrView(spkac, 'spkac', encoding)); +} + +function exportPublicKey(spkac, encoding) { + return certExportPublicKey( + getArrayBufferOrView(spkac, 'spkac', encoding)); +} + +function exportChallenge(spkac, encoding) { + return certExportChallenge( + getArrayBufferOrView(spkac, 'spkac', encoding)); +} + +// The legacy implementation of this exposed the Certificate +// object and required that users create an instance before +// calling the member methods. This API pattern has been +// deprecated, however, as the method implementations do not +// rely on any object state. + +// For backwards compatibility reasons, this cannot be converted into a +// ES6 Class. +function Certificate() { + if (!(this instanceof Certificate)) + return new Certificate(); +} + +Certificate.prototype.verifySpkac = verifySpkac; +Certificate.prototype.exportPublicKey = exportPublicKey; +Certificate.prototype.exportChallenge = exportChallenge; + +Certificate.exportChallenge = exportChallenge; +Certificate.exportPublicKey = exportPublicKey; +Certificate.verifySpkac = verifySpkac; + +module.exports = Certificate; diff --git a/modules/internal/crypto/cipher.js b/modules/internal/crypto/cipher.js new file mode 100644 index 0000000..378656c --- /dev/null +++ b/modules/internal/crypto/cipher.js @@ -0,0 +1,342 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +'use strict'; + +// TODO +const _privateDecrypt = () => { } +const _privateEncrypt = () => { } +const _publicDecrypt = () => { } +const _publicEncrypt = () => { } +const _getCipherInfo = () => { } + +import { crypto as crypto_constants } from "../../internal_binding/constants"; + +const { + RSA_PKCS1_OAEP_PADDING, + RSA_PKCS1_PADDING, +} = crypto_constants; + +import { + ERR_CRYPTO_INVALID_STATE, + ERR_CRYPTO_UNKNOWN_CIPHER, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, +} from '../errors'; + +import { + validateEncoding, + validateInt32, + validateObject, + validateString, +} from '../validators'; + +import { + isKeyObject, + preparePrivateKey, + preparePublicOrPrivateKey, + prepareSecretKey, +} from './keys'; + +import { + getDefaultEncoding, + getArrayBufferOrView, + getStringOption, + kHandle, + getCiphers, +} from './util'; + +import { + isArrayBufferView, +} from '../util/types'; + +import { assert } from '../assert'; + +import { LazyTransform } from '../streams/lazy_transform'; + +import { normalizeEncoding } from '../util'; + +import { StringDecoder } from 'string_decoder'; + +import { JsCipher as CipherBase } from "_node:crypto"; + +function rsaFunctionFor(method, defaultPadding, keyType) { + return (options, buffer) => { + const { format, type, data, passphrase } = + keyType === 'private' ? + preparePrivateKey(options) : + preparePublicOrPrivateKey(options); + const padding = options.padding || defaultPadding; + const { oaepHash, encoding } = options; + let { oaepLabel } = options; + if (oaepHash !== undefined) + validateString(oaepHash, 'key.oaepHash'); + if (oaepLabel !== undefined) + oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding); + buffer = getArrayBufferOrView(buffer, 'buffer', encoding); + return method(data, format, type, passphrase, buffer, padding, oaepHash, + oaepLabel); + }; +} + +const publicEncrypt = rsaFunctionFor(_publicEncrypt, RSA_PKCS1_OAEP_PADDING, + 'public'); +const publicDecrypt = rsaFunctionFor(_publicDecrypt, RSA_PKCS1_PADDING, + 'public'); +const privateEncrypt = rsaFunctionFor(_privateEncrypt, RSA_PKCS1_PADDING, + 'private'); +const privateDecrypt = rsaFunctionFor(_privateDecrypt, RSA_PKCS1_OAEP_PADDING, + 'private'); + +function getDecoder(decoder, encoding) { + encoding = normalizeEncoding(encoding); + decoder = decoder || new StringDecoder(encoding); + assert(decoder.encoding === encoding, 'Cannot change encoding'); + return decoder; +} + +function getUIntOption(options, key) { + let value; + if (options && (value = options[key]) != null) { + if (value >>> 0 !== value) + throw new ERR_INVALID_ARG_VALUE(`options.${key}`, value); + return value; + } + return -1; +} + +function createCipherBase(cipher, credential, options, decipher, iv) { + const authTagLength = getUIntOption(options, 'authTagLength'); + if (iv === undefined) { + // this[kHandle].init(cipher, credential, authTagLength); + } else { + this[kHandle] = new CipherBase(cipher, credential.buffer ?? credential, iv.buffer ?? iv, authTagLength, decipher); + } + this._decoder = null; + + Reflect.apply(LazyTransform, this, [options]); +} + +function createCipher(cipher, password, options, decipher) { + validateString(cipher, 'cipher'); + password = getArrayBufferOrView(password, 'password'); + + Reflect.apply(createCipherBase, this, [cipher, password, options, decipher]); +} + +function createCipherWithIV(cipher, key, options, decipher, iv) { + validateString(cipher, 'cipher'); + const encoding = getStringOption(options, 'encoding'); + key = prepareSecretKey(key, encoding); + if (isKeyObject(key)) { + key = key.export(); + } + iv = iv === null ? null : getArrayBufferOrView(iv, 'iv'); + if (!getCiphers().includes(cipher)) { + throw new ERR_CRYPTO_UNKNOWN_CIPHER(); + } + // Zero-sized IV should be rejected in GCM mode. + // Wasi-crypto current implemention only support GCM mode, + // so always check + if (iv.byteLength === 0) { + throw new Error("Invalid initialization vector"); + } + Reflect.apply(createCipherBase, this, [cipher, key, options, decipher, iv]); +} + +// The Cipher class is part of the legacy Node.js crypto API. It exposes +// a stream-based encryption/decryption model. For backwards compatibility +// the Cipher class is defined using the legacy function syntax rather than +// ES6 classes. + +function Cipher(cipher, password, options) { + if (!(this instanceof Cipher)) + return new Cipher(cipher, password, options); + + Reflect.apply(createCipher, this, [cipher, password, options, true]); +} + +Object.setPrototypeOf(Cipher.prototype, LazyTransform.prototype); +Object.setPrototypeOf(Cipher, LazyTransform); + +Cipher.prototype._transform = function _transform(chunk, encoding, callback) { + this.push(this.update(chunk, encoding)); + callback(); +}; + +Cipher.prototype._flush = function _flush(callback) { + try { + this.push(this.final()); + } catch (e) { + callback(e); + return; + } + callback(); +}; + +Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) { + const encoding = getDefaultEncoding(); + inputEncoding = inputEncoding || encoding; + outputEncoding = outputEncoding || encoding; + + if (typeof data === 'string') { + validateEncoding(data, inputEncoding); + } else if (!isArrayBufferView(data)) { + throw new ERR_INVALID_ARG_TYPE( + 'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data); + } + + let buf = getArrayBufferOrView(data, "data", inputEncoding); + const ret = this[kHandle].update(buf.buffer ?? buf); + + if (outputEncoding && outputEncoding !== 'buffer') { + return ""; // current implemented doesn't return anything from update + // this._decoder = getDecoder(this._decoder, outputEncoding); + // return this._decoder.write(ret); + } + + return ret; +}; + + +Cipher.prototype.final = function final(outputEncoding) { + outputEncoding = outputEncoding || getDefaultEncoding(); + const ret = this[kHandle].final(); + if (outputEncoding && outputEncoding !== 'buffer') { + return Buffer.from(ret).toString(outputEncoding); + // this._decoder = getDecoder(this._decoder, outputEncoding); + // return this._decoder.end(ret); + } + + return ret; +}; + + +Cipher.prototype.setAutoPadding = function setAutoPadding(ap) { + if (!this[kHandle].setAutoPadding(!!ap)) + throw new ERR_CRYPTO_INVALID_STATE('setAutoPadding'); + return this; +}; + +Cipher.prototype.getAuthTag = function getAuthTag() { + const ret = this[kHandle].getAuthTag(); + if (ret === undefined) + throw new ERR_CRYPTO_INVALID_STATE('getAuthTag'); + return ret; +}; + + +function setAuthTag(tagbuf, encoding) { + tagbuf = getArrayBufferOrView(tagbuf, 'buffer', encoding); + if (!this[kHandle].setAuthTag(tagbuf.buffer ?? tagbuf)) + throw new ERR_CRYPTO_INVALID_STATE('setAuthTag'); + return this; +} + +Cipher.prototype.setAAD = function setAAD(aadbuf, options) { + const encoding = getStringOption(options, 'encoding'); + const plaintextLength = getUIntOption(options, 'plaintextLength'); + aadbuf = getArrayBufferOrView(aadbuf, 'aadbuf', encoding); + if (!this[kHandle].setAAD(aadbuf.buffer ?? aadbuf, plaintextLength)) + throw new ERR_CRYPTO_INVALID_STATE('setAAD'); + return this; +}; + +// The Cipheriv class is part of the legacy Node.js crypto API. It exposes +// a stream-based encryption/decryption model. For backwards compatibility +// the Cipheriv class is defined using the legacy function syntax rather than +// ES6 classes. + +function Cipheriv(cipher, key, iv, options) { + if (!(this instanceof Cipheriv)) + return new Cipheriv(cipher, key, iv, options); + + Reflect.apply(createCipherWithIV, this, [cipher, key, options, true, iv]); +} + +function addCipherPrototypeFunctions(constructor) { + constructor.prototype._transform = Cipher.prototype._transform; + constructor.prototype._flush = Cipher.prototype._flush; + constructor.prototype.update = Cipher.prototype.update; + constructor.prototype.final = Cipher.prototype.final; + constructor.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; + if (constructor === Cipheriv) { + constructor.prototype.getAuthTag = Cipher.prototype.getAuthTag; + } else { + constructor.prototype.setAuthTag = setAuthTag; + } + constructor.prototype.setAAD = Cipher.prototype.setAAD; +} + +Object.setPrototypeOf(Cipheriv.prototype, LazyTransform.prototype); +Object.setPrototypeOf(Cipheriv, LazyTransform); +addCipherPrototypeFunctions(Cipheriv); + +// The Decipher class is part of the legacy Node.js crypto API. It exposes +// a stream-based encryption/decryption model. For backwards compatibility +// the Decipher class is defined using the legacy function syntax rather than +// ES6 classes. + +function Decipher(cipher, password, options) { + if (!(this instanceof Decipher)) + return new Decipher(cipher, password, options); + + Reflect.apply(createCipher, this, [cipher, password, options, false]); +} + +Object.setPrototypeOf(Decipher.prototype, LazyTransform.prototype); +Object.setPrototypeOf(Decipher, LazyTransform); +addCipherPrototypeFunctions(Decipher); + +// The Decipheriv class is part of the legacy Node.js crypto API. It exposes +// a stream-based encryption/decryption model. For backwards compatibility +// the Decipheriv class is defined using the legacy function syntax rather than +// ES6 classes. + +function Decipheriv(cipher, key, iv, options) { + if (!(this instanceof Decipheriv)) + return new Decipheriv(cipher, key, iv, options); + + Reflect.apply(createCipherWithIV, this, [cipher, key, options, false, iv]); +} + +Object.setPrototypeOf(Decipheriv.prototype, LazyTransform.prototype); +Object.setPrototypeOf(Decipheriv, LazyTransform); +addCipherPrototypeFunctions(Decipheriv); + +function getCipherInfo(nameOrNid, options) { + if (typeof nameOrNid !== 'string' && typeof nameOrNid !== 'number') { + throw new ERR_INVALID_ARG_TYPE( + 'nameOrNid', + ['string', 'number'], + nameOrNid); + } + if (typeof nameOrNid === 'number') + validateInt32(nameOrNid, 'nameOrNid'); + let keyLength, ivLength; + if (options !== undefined) { + validateObject(options, 'options'); + ({ keyLength, ivLength } = options); + if (keyLength !== undefined) + validateInt32(keyLength, 'options.keyLength'); + if (ivLength !== undefined) + validateInt32(ivLength, 'options.ivLength'); + } + + const ret = _getCipherInfo({}, nameOrNid, keyLength, ivLength); + if (ret !== undefined) { + if (ret.name) ret.name = String.prototype.toLowerCase.call(ret.name); + if (ret.type) ret.type = String.prototype.toLowerCase.call(ret.type); + } + return ret; +} + +export { + // Cipher, + Cipheriv, + // Decipher, + Decipheriv, + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt, + getCipherInfo, +}; diff --git a/modules/internal/crypto/diffiehellman.js b/modules/internal/crypto/diffiehellman.js new file mode 100644 index 0000000..094c0aa --- /dev/null +++ b/modules/internal/crypto/diffiehellman.js @@ -0,0 +1,412 @@ +'use strict'; + +const { + ArrayBufferPrototypeSlice, + FunctionPrototypeCall, + MathCeil, + ObjectDefineProperty, + Promise, + SafeSet, +} = primordials; + +const { Buffer } = require('buffer'); + +const { + DiffieHellman: _DiffieHellman, + DiffieHellmanGroup: _DiffieHellmanGroup, + ECDH: _ECDH, + ECDHBitsJob, + ECDHConvertKey: _ECDHConvertKey, + statelessDH, + kCryptoJobAsync, +} = internalBinding('crypto'); + +const { + codes: { + ERR_CRYPTO_ECDH_INVALID_FORMAT, + ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY, + ERR_CRYPTO_INCOMPATIBLE_KEY, + ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + } +} = require('internal/errors'); + +const { + validateFunction, + validateInt32, + validateObject, + validateString, + validateUint32, +} = require('internal/validators'); + +const { + isArrayBufferView, + isAnyArrayBuffer, +} = require('internal/util/types'); + +const { + lazyDOMException, +} = require('internal/util'); + +const { + KeyObject, + isCryptoKey, +} = require('internal/crypto/keys'); + +const { + getArrayBufferOrView, + getDefaultEncoding, + toBuf, + kHandle, + kKeyObject, +} = require('internal/crypto/util'); + +const { + crypto: { + POINT_CONVERSION_COMPRESSED, + POINT_CONVERSION_HYBRID, + POINT_CONVERSION_UNCOMPRESSED, + } +} = internalBinding('constants'); + +const DH_GENERATOR = 2; + +function DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) { + if (!(this instanceof DiffieHellman)) + return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding); + + if (typeof sizeOrKey !== 'number' && + typeof sizeOrKey !== 'string' && + !isArrayBufferView(sizeOrKey) && + !isAnyArrayBuffer(sizeOrKey)) { + throw new ERR_INVALID_ARG_TYPE( + 'sizeOrKey', + ['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'], + sizeOrKey + ); + } + + // Sizes < 0 don't make sense but they _are_ accepted (and subsequently + // rejected with ERR_OSSL_BN_BITS_TOO_SMALL) by OpenSSL. The glue code + // in node_crypto.cc accepts values that are IsInt32() for that reason + // and that's why we do that here too. + if (typeof sizeOrKey === 'number') + validateInt32(sizeOrKey, 'sizeOrKey'); + + if (keyEncoding && !Buffer.isEncoding(keyEncoding) && + keyEncoding !== 'buffer') { + genEncoding = generator; + generator = keyEncoding; + keyEncoding = false; + } + + const encoding = getDefaultEncoding(); + keyEncoding = keyEncoding || encoding; + genEncoding = genEncoding || encoding; + + if (typeof sizeOrKey !== 'number') + sizeOrKey = toBuf(sizeOrKey, keyEncoding); + + if (!generator) { + generator = DH_GENERATOR; + } else if (typeof generator === 'number') { + validateInt32(generator, 'generator'); + } else if (typeof generator === 'string') { + generator = toBuf(generator, genEncoding); + } else if (!isArrayBufferView(generator) && !isAnyArrayBuffer(generator)) { + throw new ERR_INVALID_ARG_TYPE( + 'generator', + ['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'], + generator + ); + } + + + this[kHandle] = new _DiffieHellman(sizeOrKey, generator); + ObjectDefineProperty(this, 'verifyError', { + __proto__: null, + enumerable: true, + value: this[kHandle].verifyError, + writable: false + }); +} + + +function DiffieHellmanGroup(name) { + if (!(this instanceof DiffieHellmanGroup)) + return new DiffieHellmanGroup(name); + this[kHandle] = new _DiffieHellmanGroup(name); + ObjectDefineProperty(this, 'verifyError', { + __proto__: null, + enumerable: true, + value: this[kHandle].verifyError, + writable: false + }); +} + + +DiffieHellmanGroup.prototype.generateKeys = + DiffieHellman.prototype.generateKeys = + dhGenerateKeys; + +function dhGenerateKeys(encoding) { + const keys = this[kHandle].generateKeys(); + encoding = encoding || getDefaultEncoding(); + return encode(keys, encoding); +} + + +DiffieHellmanGroup.prototype.computeSecret = + DiffieHellman.prototype.computeSecret = + dhComputeSecret; + +function dhComputeSecret(key, inEnc, outEnc) { + const encoding = getDefaultEncoding(); + inEnc = inEnc || encoding; + outEnc = outEnc || encoding; + key = getArrayBufferOrView(key, 'key', inEnc); + const ret = this[kHandle].computeSecret(key); + if (typeof ret === 'string') + throw new ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY(); + return encode(ret, outEnc); +} + + +DiffieHellmanGroup.prototype.getPrime = + DiffieHellman.prototype.getPrime = + dhGetPrime; + +function dhGetPrime(encoding) { + const prime = this[kHandle].getPrime(); + encoding = encoding || getDefaultEncoding(); + return encode(prime, encoding); +} + + +DiffieHellmanGroup.prototype.getGenerator = + DiffieHellman.prototype.getGenerator = + dhGetGenerator; + +function dhGetGenerator(encoding) { + const generator = this[kHandle].getGenerator(); + encoding = encoding || getDefaultEncoding(); + return encode(generator, encoding); +} + + +DiffieHellmanGroup.prototype.getPublicKey = + DiffieHellman.prototype.getPublicKey = + dhGetPublicKey; + +function dhGetPublicKey(encoding) { + const key = this[kHandle].getPublicKey(); + encoding = encoding || getDefaultEncoding(); + return encode(key, encoding); +} + + +DiffieHellmanGroup.prototype.getPrivateKey = + DiffieHellman.prototype.getPrivateKey = + dhGetPrivateKey; + +function dhGetPrivateKey(encoding) { + const key = this[kHandle].getPrivateKey(); + encoding = encoding || getDefaultEncoding(); + return encode(key, encoding); +} + + +DiffieHellman.prototype.setPublicKey = function setPublicKey(key, encoding) { + encoding = encoding || getDefaultEncoding(); + key = getArrayBufferOrView(key, 'key', encoding); + this[kHandle].setPublicKey(key); + return this; +}; + + +DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) { + encoding = encoding || getDefaultEncoding(); + key = getArrayBufferOrView(key, 'key', encoding); + this[kHandle].setPrivateKey(key); + return this; +}; + + +function ECDH(curve) { + if (!(this instanceof ECDH)) + return new ECDH(curve); + + validateString(curve, 'curve'); + this[kHandle] = new _ECDH(curve); +} + +ECDH.prototype.computeSecret = DiffieHellman.prototype.computeSecret; +ECDH.prototype.setPrivateKey = DiffieHellman.prototype.setPrivateKey; +ECDH.prototype.setPublicKey = DiffieHellman.prototype.setPublicKey; +ECDH.prototype.getPrivateKey = DiffieHellman.prototype.getPrivateKey; + +ECDH.prototype.generateKeys = function generateKeys(encoding, format) { + this[kHandle].generateKeys(); + + return this.getPublicKey(encoding, format); +}; + +ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) { + const f = getFormat(format); + const key = this[kHandle].getPublicKey(f); + encoding = encoding || getDefaultEncoding(); + return encode(key, encoding); +}; + +ECDH.convertKey = function convertKey(key, curve, inEnc, outEnc, format) { + validateString(curve, 'curve'); + const encoding = inEnc || getDefaultEncoding(); + key = getArrayBufferOrView(key, 'key', encoding); + outEnc = outEnc || encoding; + const f = getFormat(format); + const convertedKey = _ECDHConvertKey(key, curve, f); + return encode(convertedKey, outEnc); +}; + +function encode(buffer, encoding) { + if (encoding && encoding !== 'buffer') + buffer = buffer.toString(encoding); + return buffer; +} + +function getFormat(format) { + if (format) { + if (format === 'compressed') + return POINT_CONVERSION_COMPRESSED; + if (format === 'hybrid') + return POINT_CONVERSION_HYBRID; + if (format !== 'uncompressed') + throw new ERR_CRYPTO_ECDH_INVALID_FORMAT(format); + } + return POINT_CONVERSION_UNCOMPRESSED; +} + +const dhEnabledKeyTypes = new SafeSet(['dh', 'ec', 'x448', 'x25519']); + +function diffieHellman(options) { + validateObject(options, 'options'); + + const { privateKey, publicKey } = options; + if (!(privateKey instanceof KeyObject)) + throw new ERR_INVALID_ARG_VALUE('options.privateKey', privateKey); + + if (!(publicKey instanceof KeyObject)) + throw new ERR_INVALID_ARG_VALUE('options.publicKey', publicKey); + + if (privateKey.type !== 'private') + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(privateKey.type, 'private'); + + if (publicKey.type !== 'public' && publicKey.type !== 'private') { + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(publicKey.type, + 'private or public'); + } + + const privateType = privateKey.asymmetricKeyType; + const publicType = publicKey.asymmetricKeyType; + if (privateType !== publicType || !dhEnabledKeyTypes.has(privateType)) { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY('key types for Diffie-Hellman', + `${privateType} and ${publicType}`); + } + + return statelessDH(privateKey[kHandle], publicKey[kHandle]); +} + +// The deriveBitsECDH function is part of the Web Crypto API and serves both +// deriveKeys and deriveBits functions. +function deriveBitsECDH(name, publicKey, privateKey, callback) { + validateString(name, 'name'); + validateObject(publicKey, 'publicKey'); + validateObject(privateKey, 'privateKey'); + validateFunction(callback, 'callback'); + const job = new ECDHBitsJob(kCryptoJobAsync, name, publicKey, privateKey); + job.ondone = (error, bits) => { + if (error) return FunctionPrototypeCall(callback, job, error); + FunctionPrototypeCall(callback, job, null, bits); + }; + job.run(); +} + +async function asyncDeriveBitsECDH(algorithm, baseKey, length) { + const { 'public': key } = algorithm; + + // Null means that we're not asking for a specific number of bits, just + // give us everything that is generated. + if (length !== null) + validateUint32(length, 'length'); + if (!isCryptoKey(key)) + throw new ERR_INVALID_ARG_TYPE('algorithm.public', 'CryptoKey', key); + + if (key.type !== 'public') { + throw lazyDOMException( + 'algorithm.public must be a public key', 'InvalidAccessError'); + } + if (baseKey.type !== 'private') { + throw lazyDOMException( + 'baseKey must be a private key', 'InvalidAccessError'); + } + + if ( + key.algorithm.name !== 'ECDH' && + key.algorithm.name !== 'X25519' && + key.algorithm.name !== 'X448' + ) { + throw lazyDOMException('Keys must be ECDH, X25519, or X448 keys', 'InvalidAccessError'); + } + + if (key.algorithm.name !== baseKey.algorithm.name) { + throw lazyDOMException( + 'The public and private keys must be of the same type', + 'InvalidAccessError'); + } + + if ( + key.algorithm.name === 'ECDH' && + key.algorithm.namedCurve !== baseKey.algorithm.namedCurve + ) { + throw lazyDOMException('Named curve mismatch', 'InvalidAccessError'); + } + + const bits = await new Promise((resolve, reject) => { + deriveBitsECDH( + key.algorithm.name === 'ECDH' ? baseKey.algorithm.namedCurve : baseKey.algorithm.name, + key[kKeyObject][kHandle], + baseKey[kKeyObject][kHandle], (err, bits) => { + if (err) return reject(err); + resolve(bits); + }); + }); + + // If a length is not specified, return the full derived secret + if (length === null) + return bits; + + // If the length is not a multiple of 8 the nearest ceiled + // multiple of 8 is sliced. + length = MathCeil(length / 8); + const { byteLength } = bits; + + // If the length is larger than the derived secret, throw. + // Otherwise, we either return the secret or a truncated + // slice. + if (byteLength < length) + throw lazyDOMException('derived bit length is too small', 'OperationError'); + + return length === byteLength ? + bits : + ArrayBufferPrototypeSlice(bits, 0, length); +} + +module.exports = { + DiffieHellman, + DiffieHellmanGroup, + ECDH, + diffieHellman, + deriveBitsECDH, + asyncDeriveBitsECDH, +}; diff --git a/modules/internal/crypto/hash.js b/modules/internal/crypto/hash.js new file mode 100644 index 0000000..972c56b --- /dev/null +++ b/modules/internal/crypto/hash.js @@ -0,0 +1,174 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +'use strict'; + +import { + getArrayBufferOrView, + getDefaultEncoding, + getStringOption, + jobPromise, + normalizeAlgorithm, + normalizeHashName, + validateMaxBufferLength, + kHandle, + getHashes, +} from '../crypto/util'; + +import { + prepareSecretKey, +} from '../crypto/keys'; + +import { + lazyDOMException, +} from '../util'; + +import { + Buffer, +} from '../../buffer'; + +import { + ERR_CRYPTO_HASH_FINALIZED, + ERR_CRYPTO_HASH_UPDATE_FAILED, + ERR_INVALID_ARG_TYPE, +} from '../errors'; + +import { + validateEncoding, + validateString, + validateUint32, +} from '../validators'; + +import { + isArrayBufferView, +} from '../util/types'; + +import { LazyTransform } from '../streams/lazy_transform'; + +const kState = Symbol('kState'); +const kFinalized = Symbol('kFinalized'); + +import { + JsHash as _Hash, + JsHmac as _Hmac, +} from "_node:crypto"; + +function Hash(algorithm, options) { + if (!(this instanceof Hash)) + return new Hash(algorithm, options); + if (!(algorithm instanceof _Hash)) { + validateString(algorithm, 'algorithm'); + if (!getHashes().includes(algorithm.toLowerCase())) { + throw new Error("Digest method not supported"); + } + } + const xofLen = typeof options === 'object' && options !== null ? + options.outputLength : undefined; + if (xofLen !== undefined) + validateUint32(xofLen, 'options.outputLength'); + this[kHandle] = new _Hash(algorithm, xofLen); + this[kState] = { + [kFinalized]: false + }; + Reflect.apply(LazyTransform, this, [options]); +} + +Object.setPrototypeOf(Hash.prototype, LazyTransform.prototype); +Object.setPrototypeOf(Hash, LazyTransform); + +Hash.prototype.copy = function copy(options) { + const state = this[kState]; + if (state[kFinalized]) + throw new ERR_CRYPTO_HASH_FINALIZED(); + + return new Hash(this[kHandle], options); +}; + +Hash.prototype._transform = function _transform(chunk, encoding, callback) { + this.update(chunk, encoding); + callback(); +}; + +Hash.prototype._flush = function _flush(callback) { + this.push(this.digest()); + callback(); +}; + +Hash.prototype.update = function update(data, encoding) { + encoding = encoding || getDefaultEncoding(); + + const state = this[kState]; + if (state[kFinalized]) + throw new ERR_CRYPTO_HASH_FINALIZED(); + + if (typeof data === 'string') { + validateEncoding(data, encoding); + } else if (!isArrayBufferView(data)) { + throw new ERR_INVALID_ARG_TYPE( + 'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data); + } + let buffer = getArrayBufferOrView(data, "data", encoding); + if (!this[kHandle].update(buffer.buffer ?? buffer)) + throw new ERR_CRYPTO_HASH_UPDATE_FAILED(); + return this; +}; + + +Hash.prototype.digest = function digest(outputEncoding) { + const state = this[kState]; + if (state[kFinalized]) + throw new ERR_CRYPTO_HASH_FINALIZED(); + outputEncoding = outputEncoding || getDefaultEncoding(); + + // Explicit conversion for backward compatibility. + const ret = Buffer.from(this[kHandle].digest()); + state[kFinalized] = true; + return outputEncoding === 'buffer' ? ret : ret.toString(outputEncoding); +}; + +function Hmac(hmac, key, options) { + if (!(this instanceof Hmac)) + return new Hmac(hmac, key, options); + validateString(hmac, 'hmac'); + if (!getHashes().includes(hmac.toLowerCase())) { + throw new Error("Digest method not supported"); + } + const encoding = getStringOption(options, 'encoding'); + key = prepareSecretKey(key, encoding); + if (key.export !== undefined) { + key = key.export(); + } + this[kHandle] = new _Hmac(hmac, key.buffer ?? key); + this[kState] = { + [kFinalized]: false + }; + Reflect.apply(LazyTransform, this, [options]); +} + +Object.setPrototypeOf(Hmac.prototype, LazyTransform.prototype); +Object.setPrototypeOf(Hmac, LazyTransform); + +Hmac.prototype.update = Hash.prototype.update; + +Hmac.prototype.digest = function digest(outputEncoding) { + const state = this[kState]; + outputEncoding = outputEncoding || getDefaultEncoding(); + + if (state[kFinalized]) { + const buf = Buffer.from(''); + return outputEncoding === 'buffer' ? buf : buf.toString(outputEncoding); + } + + // Explicit conversion for backward compatibility. + const ret = Buffer.from(this[kHandle].digest()); + state[kFinalized] = true; + return outputEncoding === 'buffer' ? ret : ret.toString(outputEncoding);; +}; + +Hmac.prototype._flush = Hash.prototype._flush; +Hmac.prototype._transform = Hash.prototype._transform; + +// Implementation for WebCrypto subtle.digest() + +export { + Hash, + Hmac, +}; diff --git a/modules/internal/crypto/hashnames.js b/modules/internal/crypto/hashnames.js new file mode 100644 index 0000000..5153f65 --- /dev/null +++ b/modules/internal/crypto/hashnames.js @@ -0,0 +1,80 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +'use strict'; + +const kHashContextNode = 1; +const kHashContextWebCrypto = 2; +const kHashContextJwkRsa = 3; +const kHashContextJwkRsaPss = 4; +const kHashContextJwkRsaOaep = 5; +const kHashContextJwkHmac = 6; + +// WebCrypto and JWK use a bunch of different names for the +// standard set of SHA-* digest algorithms... which is ... fun. +// Here we provide a utility for mapping between them in order +// make it easier in the code. + +const kHashNames = { + sha1: { + [kHashContextNode]: 'sha1', + [kHashContextWebCrypto]: 'SHA-1', + [kHashContextJwkRsa]: 'RS1', + [kHashContextJwkRsaPss]: 'PS1', + [kHashContextJwkRsaOaep]: 'RSA-OAEP', + [kHashContextJwkHmac]: 'HS1', + }, + sha256: { + [kHashContextNode]: 'sha256', + [kHashContextWebCrypto]: 'SHA-256', + [kHashContextJwkRsa]: 'RS256', + [kHashContextJwkRsaPss]: 'PS256', + [kHashContextJwkRsaOaep]: 'RSA-OAEP-256', + [kHashContextJwkHmac]: 'HS256', + }, + sha384: { + [kHashContextNode]: 'sha384', + [kHashContextWebCrypto]: 'SHA-384', + [kHashContextJwkRsa]: 'RS384', + [kHashContextJwkRsaPss]: 'PS384', + [kHashContextJwkRsaOaep]: 'RSA-OAEP-384', + [kHashContextJwkHmac]: 'HS384', + }, + sha512: { + [kHashContextNode]: 'sha512', + [kHashContextWebCrypto]: 'SHA-512', + [kHashContextJwkRsa]: 'RS512', + [kHashContextJwkRsaPss]: 'PS512', + [kHashContextJwkRsaOaep]: 'RSA-OAEP-512', + [kHashContextJwkHmac]: 'HS512', + } +}; + +{ + // Index the aliases + const keys = Object.keys(kHashNames); + for (let n = 0; n < keys.length; n++) { + const contexts = Object.keys(kHashNames[keys[n]]); + for (let i = 0; i < contexts.length; i++) { + const alias = + String.prototype.toLowerCase.call(kHashNames[keys[n]][contexts[i]]); + if (kHashNames[alias] === undefined) + kHashNames[alias] = kHashNames[keys[n]]; + } + } +} + +function normalizeHashName(name, context = kHashContextNode) { + if (typeof name !== 'string') + return name; + name = String.prototype.toLowerCase.call(name); + const alias = kHashNames[name] && kHashNames[name][context]; + return alias || name; +} + +normalizeHashName.kContextNode = kHashContextNode; +normalizeHashName.kContextWebCrypto = kHashContextWebCrypto; +normalizeHashName.kContextJwkRsa = kHashContextJwkRsa; +normalizeHashName.kContextJwkRsaPss = kHashContextJwkRsaPss; +normalizeHashName.kContextJwkRsaOaep = kHashContextJwkRsaOaep; +normalizeHashName.kContextJwkHmac = kHashContextJwkHmac; + +export default normalizeHashName; diff --git a/modules/internal/crypto/hkdf.js b/modules/internal/crypto/hkdf.js new file mode 100644 index 0000000..ef8e503 --- /dev/null +++ b/modules/internal/crypto/hkdf.js @@ -0,0 +1,176 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +'use strict'; + +import { + validateFunction, + validateInteger, + validateString, + validateUint32, +} from '../validators'; + +import { kMaxLength } from '../../buffer'; + +import { + getArrayBufferOrView, + normalizeHashName, + toBuf, + validateByteSource, + kKeyObject, + getHashes, +} from '../crypto/util'; + +import { + createSecretKey, + isKeyObject, +} from './keys'; + +import { + lazyDOMException, +} from '../util'; + +import { + isAnyArrayBuffer, + isArrayBufferView, +} from '../util/types'; + +import { + ERR_INVALID_ARG_TYPE, + ERR_OUT_OF_RANGE, + ERR_MISSING_OPTION, + hideStackFrames, + ERR_CRYPTO_INVALID_DIGEST, + ERR_CRYPTO_INVALID_KEYLEN, +} from '../errors'; + +import { hkdf_sync } from "_node:crypto"; + +const validateParameters = hideStackFrames((hash, key, salt, info, length) => { + validateString(hash, 'digest'); + + key = prepareKey(key).export(); + salt = validateByteSource(salt, 'salt'); + info = validateByteSource(info, 'info'); + + validateInteger(length, 'length', 0, kMaxLength); + if (info.byteLength > 1024) { + throw new ERR_OUT_OF_RANGE( + 'info', + 'must not contain more than 1024 bytes', + info.byteLength); + } + + if (!getHashes().includes(hash)) { + throw new ERR_CRYPTO_INVALID_DIGEST(hash); + } + + if (hash === "sha256" && length > 255 * 32) { + throw new ERR_CRYPTO_INVALID_KEYLEN() + } else if (hash === "sha512" && length > 255 * 64) { + throw new ERR_CRYPTO_INVALID_KEYLEN() + } + + return { + hash, + key, + salt, + info, + length, + }; +}); + +function prepareKey(key) { + if (isKeyObject(key)) + return key; + + if (isAnyArrayBuffer(key)) + return createSecretKey(key); + + key = toBuf(key); + + if (!isArrayBufferView(key)) { + throw new ERR_INVALID_ARG_TYPE( + 'ikm', + [ + 'string', + 'SecretKeyObject', + 'ArrayBuffer', + 'TypedArray', + 'DataView', + 'Buffer', + ], + key); + } + + return createSecretKey(key); +} + +function hkdf(hash, key, salt, info, length, callback) { + ({ + hash, + key, + salt, + info, + length, + } = validateParameters(hash, key, salt, info, length)); + + validateFunction(callback, 'callback'); + + setTimeout(() => { + let result = hkdf_sync(key.buffer ?? key, salt.buffer ?? salt, info.buffer ?? info, length, hash.toUpperCase()); + callback(null, result); + }, 0); +} + +function hkdfSync(hash, key, salt, info, length) { + ({ + hash, + key, + salt, + info, + length, + } = validateParameters(hash, key, salt, info, length)); + let result = hkdf_sync(key.buffer ?? key, salt.buffer ?? salt, info.buffer ?? info, length, hash.toUpperCase()); + return result; +} + +async function hkdfDeriveBits(algorithm, baseKey, length) { + const { hash } = algorithm; + const salt = getArrayBufferOrView(algorithm.salt, 'algorithm.salt'); + const info = getArrayBufferOrView(algorithm.info, 'algorithm.info'); + if (hash === undefined) + throw new ERR_MISSING_OPTION('algorithm.hash'); + + let byteLength = 512 / 8; + if (length !== undefined) { + if (length === 0) + throw lazyDOMException('length cannot be zero', 'OperationError'); + if (length === null) + throw lazyDOMException('length cannot be null', 'OperationError'); + validateUint32(length, 'length'); + if (length % 8) { + throw lazyDOMException( + 'length must be a multiple of 8', + 'OperationError'); + } + byteLength = length / 8; + } + + return new Promise((resolve, reject) => { + hkdf( + normalizeHashName(hash.name), + baseKey[kKeyObject], + salt, + info, + byteLength, + (err, bits) => { + if (err) return reject(err); + resolve(bits); + }); + }); +} + +export { + hkdf, + hkdfSync, + hkdfDeriveBits, +}; diff --git a/modules/internal/crypto/keygen.js b/modules/internal/crypto/keygen.js new file mode 100644 index 0000000..d30fe38 --- /dev/null +++ b/modules/internal/crypto/keygen.js @@ -0,0 +1,413 @@ +'use strict'; + +const { + FunctionPrototypeCall, + ObjectDefineProperty, + SafeArrayIterator, +} = primordials; + +const { + DhKeyPairGenJob, + DsaKeyPairGenJob, + EcKeyPairGenJob, + NidKeyPairGenJob, + RsaKeyPairGenJob, + SecretKeyGenJob, + kCryptoJobAsync, + kCryptoJobSync, + kKeyVariantRSA_PSS, + kKeyVariantRSA_SSA_PKCS1_v1_5, + EVP_PKEY_ED25519, + EVP_PKEY_ED448, + EVP_PKEY_X25519, + EVP_PKEY_X448, + OPENSSL_EC_NAMED_CURVE, + OPENSSL_EC_EXPLICIT_CURVE, +} = internalBinding('crypto'); + +const { + PublicKeyObject, + PrivateKeyObject, + SecretKeyObject, + parsePublicKeyEncoding, + parsePrivateKeyEncoding, +} = require('internal/crypto/keys'); + +const { + kAesKeyLengths, +} = require('internal/crypto/util'); + +const { + customPromisifyArgs, + kEmptyObject, +} = require('internal/util'); + +const { + validateFunction, + validateBuffer, + validateString, + validateInteger, + validateObject, + validateOneOf, + validateInt32, + validateUint32, +} = require('internal/validators'); + +const { + codes: { + ERR_INCOMPATIBLE_OPTION_PAIR, + ERR_INVALID_ARG_VALUE, + ERR_MISSING_OPTION, + } +} = require('internal/errors'); + +const { isArrayBufferView } = require('internal/util/types'); + +const { getOptionValue } = require('internal/options'); + +function isJwk(obj) { + return obj != null && obj.kty !== undefined; +} + +function wrapKey(key, ctor) { + if (typeof key === 'string' || + isArrayBufferView(key) || + isJwk(key)) + return key; + return new ctor(key); +} + +function generateKeyPair(type, options, callback) { + if (typeof options === 'function') { + callback = options; + options = undefined; + } + validateFunction(callback, 'callback'); + + const job = createJob(kCryptoJobAsync, type, options); + + job.ondone = (error, result) => { + if (error) return FunctionPrototypeCall(callback, job, error); + // If no encoding was chosen, return key objects instead. + let { 0: pubkey, 1: privkey } = result; + pubkey = wrapKey(pubkey, PublicKeyObject); + privkey = wrapKey(privkey, PrivateKeyObject); + FunctionPrototypeCall(callback, job, null, pubkey, privkey); + }; + + job.run(); +} + +ObjectDefineProperty(generateKeyPair, customPromisifyArgs, { + __proto__: null, + value: ['publicKey', 'privateKey'], + enumerable: false +}); + +function generateKeyPairSync(type, options) { + return handleError(createJob(kCryptoJobSync, type, options).run()); +} + +function handleError(ret) { + if (ret == null) + return; // async + + const { 0: err, 1: keys } = ret; + if (err !== undefined) + throw err; + + const { 0: publicKey, 1: privateKey } = keys; + + // If no encoding was chosen, return key objects instead. + return { + publicKey: wrapKey(publicKey, PublicKeyObject), + privateKey: wrapKey(privateKey, PrivateKeyObject) + }; +} + +function parseKeyEncoding(keyType, options = kEmptyObject) { + const { publicKeyEncoding, privateKeyEncoding } = options; + + let publicFormat, publicType; + if (publicKeyEncoding == null) { + publicFormat = publicType = undefined; + } else if (typeof publicKeyEncoding === 'object') { + ({ + format: publicFormat, + type: publicType + } = parsePublicKeyEncoding(publicKeyEncoding, keyType, + 'publicKeyEncoding')); + } else { + throw new ERR_INVALID_ARG_VALUE('options.publicKeyEncoding', + publicKeyEncoding); + } + + let privateFormat, privateType, cipher, passphrase; + if (privateKeyEncoding == null) { + privateFormat = privateType = undefined; + } else if (typeof privateKeyEncoding === 'object') { + ({ + format: privateFormat, + type: privateType, + cipher, + passphrase + } = parsePrivateKeyEncoding(privateKeyEncoding, keyType, + 'privateKeyEncoding')); + } else { + throw new ERR_INVALID_ARG_VALUE('options.privateKeyEncoding', + privateKeyEncoding); + } + + return [ + publicFormat, + publicType, + privateFormat, + privateType, + cipher, + passphrase, + ]; +} + +function createJob(mode, type, options) { + validateString(type, 'type'); + + const encoding = new SafeArrayIterator(parseKeyEncoding(type, options)); + + if (options !== undefined) + validateObject(options, 'options'); + + switch (type) { + case 'rsa': + case 'rsa-pss': + { + validateObject(options, 'options'); + const { modulusLength } = options; + validateUint32(modulusLength, 'options.modulusLength'); + + let { publicExponent } = options; + if (publicExponent == null) { + publicExponent = 0x10001; + } else { + validateUint32(publicExponent, 'options.publicExponent'); + } + + if (type === 'rsa') { + return new RsaKeyPairGenJob( + mode, + kKeyVariantRSA_SSA_PKCS1_v1_5, // Used also for RSA-OAEP + modulusLength, + publicExponent, + ...encoding); + } + + const { + hash, mgf1Hash, hashAlgorithm, mgf1HashAlgorithm, saltLength + } = options; + + const pendingDeprecation = getOptionValue('--pending-deprecation'); + + if (saltLength !== undefined) + validateInt32(saltLength, 'options.saltLength', 0); + if (hashAlgorithm !== undefined) + validateString(hashAlgorithm, 'options.hashAlgorithm'); + if (mgf1HashAlgorithm !== undefined) + validateString(mgf1HashAlgorithm, 'options.mgf1HashAlgorithm'); + if (hash !== undefined) { + pendingDeprecation && process.emitWarning( + '"options.hash" is deprecated, ' + + 'use "options.hashAlgorithm" instead.', + 'DeprecationWarning', + 'DEP0154'); + validateString(hash, 'options.hash'); + if (hashAlgorithm && hash !== hashAlgorithm) { + throw new ERR_INVALID_ARG_VALUE('options.hash', hash); + } + } + if (mgf1Hash !== undefined) { + pendingDeprecation && process.emitWarning( + '"options.mgf1Hash" is deprecated, ' + + 'use "options.mgf1HashAlgorithm" instead.', + 'DeprecationWarning', + 'DEP0154'); + validateString(mgf1Hash, 'options.mgf1Hash'); + if (mgf1HashAlgorithm && mgf1Hash !== mgf1HashAlgorithm) { + throw new ERR_INVALID_ARG_VALUE('options.mgf1Hash', mgf1Hash); + } + } + + return new RsaKeyPairGenJob( + mode, + kKeyVariantRSA_PSS, + modulusLength, + publicExponent, + hashAlgorithm || hash, + mgf1HashAlgorithm || mgf1Hash, + saltLength, + ...encoding); + } + case 'dsa': + { + validateObject(options, 'options'); + const { modulusLength } = options; + validateUint32(modulusLength, 'options.modulusLength'); + + let { divisorLength } = options; + if (divisorLength == null) { + divisorLength = -1; + } else + validateInt32(divisorLength, 'options.divisorLength', 0); + + return new DsaKeyPairGenJob( + mode, + modulusLength, + divisorLength, + ...encoding); + } + case 'ec': + { + validateObject(options, 'options'); + const { namedCurve } = options; + validateString(namedCurve, 'options.namedCurve'); + let { paramEncoding } = options; + if (paramEncoding == null || paramEncoding === 'named') + paramEncoding = OPENSSL_EC_NAMED_CURVE; + else if (paramEncoding === 'explicit') + paramEncoding = OPENSSL_EC_EXPLICIT_CURVE; + else + throw new ERR_INVALID_ARG_VALUE('options.paramEncoding', paramEncoding); + + return new EcKeyPairGenJob( + mode, + namedCurve, + paramEncoding, + ...encoding); + } + case 'ed25519': + case 'ed448': + case 'x25519': + case 'x448': + { + let id; + switch (type) { + case 'ed25519': + id = EVP_PKEY_ED25519; + break; + case 'ed448': + id = EVP_PKEY_ED448; + break; + case 'x25519': + id = EVP_PKEY_X25519; + break; + case 'x448': + id = EVP_PKEY_X448; + break; + } + return new NidKeyPairGenJob(mode, id, ...encoding); + } + case 'dh': + { + validateObject(options, 'options'); + const { group, primeLength, prime, generator } = options; + if (group != null) { + if (prime != null) + throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'prime'); + if (primeLength != null) + throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'primeLength'); + if (generator != null) + throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'generator'); + + validateString(group, 'options.group'); + + return new DhKeyPairGenJob(mode, group, ...encoding); + } + + if (prime != null) { + if (primeLength != null) + throw new ERR_INCOMPATIBLE_OPTION_PAIR('prime', 'primeLength'); + + validateBuffer(prime, 'options.prime'); + } else if (primeLength != null) { + validateInt32(primeLength, 'options.primeLength', 0); + } else { + throw new ERR_MISSING_OPTION( + 'At least one of the group, prime, or primeLength options'); + } + + if (generator != null) { + validateInt32(generator, 'options.generator', 0); + } + return new DhKeyPairGenJob( + mode, + prime != null ? prime : primeLength, + generator == null ? 2 : generator, + ...encoding); + } + default: + // Fall through + } + throw new ERR_INVALID_ARG_VALUE('type', type, 'must be a supported key type'); +} + +// Symmetric Key Generation + +function generateKeyJob(mode, keyType, options) { + validateString(keyType, 'type'); + validateObject(options, 'options'); + const { length } = options; + switch (keyType) { + case 'hmac': + validateInteger(length, 'options.length', 8, 2 ** 31 - 1); + break; + case 'aes': + validateOneOf(length, 'options.length', kAesKeyLengths); + break; + default: + throw new ERR_INVALID_ARG_VALUE( + 'type', + keyType, + 'must be a supported key type'); + } + + return new SecretKeyGenJob(mode, length); +} + +function handleGenerateKeyError(ret) { + if (ret === undefined) + return; // async + + const { 0: err, 1: key } = ret; + if (err !== undefined) + throw err; + + return wrapKey(key, SecretKeyObject); +} + +function generateKey(type, options, callback) { + if (typeof options === 'function') { + callback = options; + options = undefined; + } + + validateFunction(callback, 'callback'); + + const job = generateKeyJob(kCryptoJobAsync, type, options); + + job.ondone = (error, key) => { + if (error) return FunctionPrototypeCall(callback, job, error); + FunctionPrototypeCall(callback, job, null, wrapKey(key, SecretKeyObject)); + }; + + handleGenerateKeyError(job.run()); +} + +function generateKeySync(type, options) { + return handleGenerateKeyError( + generateKeyJob(kCryptoJobSync, type, options).run()); +} + +module.exports = { + generateKeyPair, + generateKeyPairSync, + generateKey, + generateKeySync, +}; diff --git a/modules/internal/crypto/keys.js b/modules/internal/crypto/keys.js new file mode 100644 index 0000000..8cadf03 --- /dev/null +++ b/modules/internal/crypto/keys.js @@ -0,0 +1,766 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +'use strict'; + +class SecretKeyHandle { + #keyBuffer + + constructor(key) { + this.#keyBuffer = Buffer.from(key); + } + + // base + equals(key) { + return this.export().equals(key.export()); + } + + // secretKey + getSymmetricKeySize() { + return this.#keyBuffer.byteLength; + } + + export() { + return Buffer.from(this.#keyBuffer) + } + + exportJwk(_obj, _bool) { + return { + kty: 'oct', + k: this.#keyBuffer.toString("base64").replace(/=+$/, '') + }; + } +} + +const kKeyTypeSecret = Symbol("kKeyTypeSecret"); +const kKeyTypePublic = Symbol("kKeyTypePublic"); +const kKeyTypePrivate = Symbol("kKeyTypePrivate"); +const kKeyFormatPEM = Symbol("kKeyFormatPEM"); +const kKeyFormatDER = Symbol("kKeyFormatDER"); +const kKeyFormatJWK = Symbol("kKeyFormatJWK"); +const kKeyEncodingPKCS1 = Symbol("kKeyEncodingPKCS1"); +const kKeyEncodingPKCS8 = Symbol("kKeyEncodingPKCS8"); +const kKeyEncodingSPKI = Symbol("kKeyEncodingSPKI"); +const kKeyEncodingSEC1 = Symbol("kKeyEncodingSEC1"); + +import { + validateObject, + validateOneOf, + validateString, +} from '../validators'; + +import { + ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, + ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, + ERR_CRYPTO_INVALID_JWK, + ERR_ILLEGAL_CONSTRUCTOR, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, +} from '../errors'; + +import { + kHandle, + kKeyObject, + getArrayBufferOrView, + bigIntArrayToUnsignedBigInt, +} from '../crypto/util'; + +import { + isAnyArrayBuffer, + isArrayBufferView, +} from '../util/types'; + +/*const { + JSTransferable, + kClone, + kDeserialize, +} = require('internal/worker/js_transferable');*/ + +const kClone = Symbol('kClone'); +const kDeserialize = Symbol('kDeserialize'); + +import { + customInspectSymbol as kInspect, +} from '../util'; + +import { inspect } from '../util/inspect'; + +import { Buffer } from '../../buffer'; + +const kAlgorithm = Symbol('kAlgorithm'); +const kExtractable = Symbol('kExtractable'); +const kKeyType = Symbol('kKeyType'); +const kKeyUsages = Symbol('kKeyUsages'); + +// Key input contexts. +const kConsumePublic = 0; +const kConsumePrivate = 1; +const kCreatePublic = 2; +const kCreatePrivate = 3; + +const encodingNames = []; +for (const m of [[kKeyEncodingPKCS1, 'pkcs1'], [kKeyEncodingPKCS8, 'pkcs8'], +[kKeyEncodingSPKI, 'spki'], [kKeyEncodingSEC1, 'sec1']]) + encodingNames[m[0]] = m[1]; + +// Creating the KeyObject class is a little complicated due to inheritance +// and the fact that KeyObjects should be transferrable between threads, +// which requires the KeyObject base class to be implemented in C++. +// The creation requires a callback to make sure that the NativeKeyObject +// base class cannot exist without the other KeyObject implementations. +/*const { + 0: KeyObject, + 1: SecretKeyObject, + 2: PublicKeyObject, + 3: PrivateKeyObject, +} = createNativeKeyObjectClass((NativeKeyObject) => {*/ +// Publicly visible KeyObject class. +class KeyObject/* extends NativeKeyObject*/ { + constructor(type, handle) { + if (type !== 'secret' && type !== 'public' && type !== 'private') + throw new ERR_INVALID_ARG_VALUE('type', type); + if (typeof handle !== 'object'/* || !(handle instanceof KeyObjectHandle)*/) + throw new ERR_INVALID_ARG_TYPE('handle', 'object', handle); + + // super(handle); + + this[kKeyType] = type; + + Object.defineProperty(this, kHandle, { + __proto__: null, + value: handle, + enumerable: false, + configurable: false, + writable: false + }); + } + + get type() { + return this[kKeyType]; + } + + static from(key) { + if (!isCryptoKey(key)) + throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key); + return key[kKeyObject]; + } + + equals(otherKeyObject) { + if (!isKeyObject(otherKeyObject)) { + throw new ERR_INVALID_ARG_TYPE( + 'otherKeyObject', 'KeyObject', otherKeyObject); + } + + return otherKeyObject.type === this.type && + this[kHandle].equals(otherKeyObject[kHandle]); + } +} + +class SecretKeyObject extends KeyObject { + constructor(handle) { + super('secret', handle); + } + + get symmetricKeySize() { + return this[kHandle].getSymmetricKeySize(); + } + + export(options) { + if (options !== undefined) { + validateObject(options, 'options'); + validateOneOf( + options.format, 'options.format', [undefined, 'buffer', 'jwk']); + if (options.format === 'jwk') { + return this[kHandle].exportJwk({}, false); + } + } + return this[kHandle].export(); + } +} + +const kAsymmetricKeyType = Symbol('kAsymmetricKeyType'); +const kAsymmetricKeyDetails = Symbol('kAsymmetricKeyDetails'); + +function normalizeKeyDetails(details = {}) { + if (details.publicExponent !== undefined) { + return { + ...details, + publicExponent: + bigIntArrayToUnsignedBigInt(new Uint8Array(details.publicExponent)) + }; + } + return details; +} + +class AsymmetricKeyObject extends KeyObject { + // eslint-disable-next-line no-useless-constructor + constructor(type, handle) { + super(type, handle); + } + + get asymmetricKeyType() { + return this[kAsymmetricKeyType] || + (this[kAsymmetricKeyType] = this[kHandle].getAsymmetricKeyType()); + } + + get asymmetricKeyDetails() { + switch (this.asymmetricKeyType) { + case 'rsa': + case 'rsa-pss': + case 'dsa': + case 'ec': + return this[kAsymmetricKeyDetails] || + (this[kAsymmetricKeyDetails] = normalizeKeyDetails( + this[kHandle].keyDetail({}) + )); + default: + return {}; + } + } +} + +class PublicKeyObject extends AsymmetricKeyObject { + constructor(handle) { + super('public', handle); + } + + export(options) { + if (options && options.format === 'jwk') { + return this[kHandle].exportJwk({}, false); + } + const { + format, + type + } = parsePublicKeyEncoding(options, this.asymmetricKeyType); + return this[kHandle].export(format, type); + } +} + +class PrivateKeyObject extends AsymmetricKeyObject { + constructor(handle) { + super('private', handle); + } + + export(options) { + if (options && options.format === 'jwk') { + if (options.passphrase !== undefined) { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + 'jwk', 'does not support encryption'); + } + return this[kHandle].exportJwk({}, false); + } + const { + format, + type, + cipher, + passphrase + } = parsePrivateKeyEncoding(options, this.asymmetricKeyType); + return this[kHandle].export(format, type, cipher, passphrase); + } +} +/* + return [KeyObject, SecretKeyObject, PublicKeyObject, PrivateKeyObject]; +});*/ + +function parseKeyFormat(formatStr, defaultFormat, optionName) { + if (formatStr === undefined && defaultFormat !== undefined) + return defaultFormat; + else if (formatStr === 'pem') + return kKeyFormatPEM; + else if (formatStr === 'der') + return kKeyFormatDER; + else if (formatStr === 'jwk') + return kKeyFormatJWK; + throw new ERR_INVALID_ARG_VALUE(optionName, formatStr); +} + +function parseKeyType(typeStr, required, keyType, isPublic, optionName) { + if (typeStr === undefined && !required) { + return undefined; + } else if (typeStr === 'pkcs1') { + if (keyType !== undefined && keyType !== 'rsa') { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + typeStr, 'can only be used for RSA keys'); + } + return kKeyEncodingPKCS1; + } else if (typeStr === 'spki' && isPublic !== false) { + return kKeyEncodingSPKI; + } else if (typeStr === 'pkcs8' && isPublic !== true) { + return kKeyEncodingPKCS8; + } else if (typeStr === 'sec1' && isPublic !== true) { + if (keyType !== undefined && keyType !== 'ec') { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + typeStr, 'can only be used for EC keys'); + } + return kKeyEncodingSEC1; + } + + throw new ERR_INVALID_ARG_VALUE(optionName, typeStr); +} + +function option(name, objName) { + return objName === undefined ? + `options.${name}` : `options.${objName}.${name}`; +} + +function parseKeyFormatAndType(enc, keyType, isPublic, objName) { + const { format: formatStr, type: typeStr } = enc; + + const isInput = keyType === undefined; + const format = parseKeyFormat(formatStr, + isInput ? kKeyFormatPEM : undefined, + option('format', objName)); + + const isRequired = (!isInput || + format === kKeyFormatDER) && + format !== kKeyFormatJWK; + const type = parseKeyType(typeStr, + isRequired, + keyType, + isPublic, + option('type', objName)); + return { format, type }; +} + +function isStringOrBuffer(val) { + return typeof val === 'string' || + isArrayBufferView(val) || + isAnyArrayBuffer(val); +} + +function parseKeyEncoding(enc, keyType, isPublic, objName) { + validateObject(enc, 'options'); + + const isInput = keyType === undefined; + + const { + format, + type + } = parseKeyFormatAndType(enc, keyType, isPublic, objName); + + let cipher, passphrase, encoding; + if (isPublic !== true) { + ({ cipher, passphrase, encoding } = enc); + + if (!isInput) { + if (cipher != null) { + if (typeof cipher !== 'string') + throw new ERR_INVALID_ARG_VALUE(option('cipher', objName), cipher); + if (format === kKeyFormatDER && + (type === kKeyEncodingPKCS1 || + type === kKeyEncodingSEC1)) { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + encodingNames[type], 'does not support encryption'); + } + } else if (passphrase !== undefined) { + throw new ERR_INVALID_ARG_VALUE(option('cipher', objName), cipher); + } + } + + if ((isInput && passphrase !== undefined && + !isStringOrBuffer(passphrase)) || + (!isInput && cipher != null && !isStringOrBuffer(passphrase))) { + throw new ERR_INVALID_ARG_VALUE(option('passphrase', objName), + passphrase); + } + } + + if (passphrase !== undefined) + passphrase = getArrayBufferOrView(passphrase, 'key.passphrase', encoding); + + return { format, type, cipher, passphrase }; +} + +// Parses the public key encoding based on an object. keyType must be undefined +// when this is used to parse an input encoding and must be a valid key type if +// used to parse an output encoding. +function parsePublicKeyEncoding(enc, keyType, objName) { + return parseKeyEncoding(enc, keyType, keyType ? true : undefined, objName); +} + +// Parses the private key encoding based on an object. keyType must be undefined +// when this is used to parse an input encoding and must be a valid key type if +// used to parse an output encoding. +function parsePrivateKeyEncoding(enc, keyType, objName) { + return parseKeyEncoding(enc, keyType, false, objName); +} + +function getKeyObjectHandle(key, ctx) { + if (ctx === kCreatePrivate) { + throw new ERR_INVALID_ARG_TYPE( + 'key', + ['string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'], + key + ); + } + + if (key.type !== 'private') { + if (ctx === kConsumePrivate || ctx === kCreatePublic) + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'private'); + if (key.type !== 'public') { + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, + 'private or public'); + } + } + + return key[kHandle]; +} + +function getKeyTypes(allowKeyObject, bufferOnly = false) { + const types = [ + 'ArrayBuffer', + 'Buffer', + 'TypedArray', + 'DataView', + 'string', // Only if bufferOnly == false + 'KeyObject', // Only if allowKeyObject == true && bufferOnly == false + 'CryptoKey', // Only if allowKeyObject == true && bufferOnly == false + ]; + if (bufferOnly) { + return Array.prototype.slice.call(types, 0, 4); + } else if (!allowKeyObject) { + return Array.prototype.slice.call(types, 0, 5); + } + return types; +} + +function getKeyObjectHandleFromJwk(key, ctx) { + validateObject(key, 'key'); + validateOneOf( + key.kty, 'key.kty', ['RSA', 'EC', 'OKP']); + const isPublic = ctx === kConsumePublic || ctx === kCreatePublic; + + if (key.kty === 'OKP') { + validateString(key.crv, 'key.crv'); + validateOneOf( + key.crv, 'key.crv', ['Ed25519', 'Ed448', 'X25519', 'X448']); + validateString(key.x, 'key.x'); + + if (!isPublic) + validateString(key.d, 'key.d'); + + let keyData; + if (isPublic) + keyData = Buffer.from(key.x, 'base64'); + else + keyData = Buffer.from(key.d, 'base64'); + + switch (key.crv) { + case 'Ed25519': + case 'X25519': + if (keyData.byteLength !== 32) { + throw new ERR_CRYPTO_INVALID_JWK(); + } + break; + case 'Ed448': + if (keyData.byteLength !== 57) { + throw new ERR_CRYPTO_INVALID_JWK(); + } + break; + case 'X448': + if (keyData.byteLength !== 56) { + throw new ERR_CRYPTO_INVALID_JWK(); + } + break; + } + + const handle = new KeyObjectHandle(); + + const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; + if (!handle.initEDRaw(key.crv, keyData, keyType)) { + throw new ERR_CRYPTO_INVALID_JWK(); + } + + return handle; + } + + if (key.kty === 'EC') { + validateString(key.crv, 'key.crv'); + validateOneOf( + key.crv, 'key.crv', ['P-256', 'secp256k1', 'P-384', 'P-521']); + validateString(key.x, 'key.x'); + validateString(key.y, 'key.y'); + + const jwk = { + kty: key.kty, + crv: key.crv, + x: key.x, + y: key.y + }; + + if (!isPublic) { + validateString(key.d, 'key.d'); + jwk.d = key.d; + } + + const handle = new KeyObjectHandle(); + const type = handle.initJwk(jwk, jwk.crv); + if (type === undefined) + throw new ERR_CRYPTO_INVALID_JWK(); + + return handle; + } + + // RSA + validateString(key.n, 'key.n'); + validateString(key.e, 'key.e'); + + const jwk = { + kty: key.kty, + n: key.n, + e: key.e + }; + + if (!isPublic) { + validateString(key.d, 'key.d'); + validateString(key.p, 'key.p'); + validateString(key.q, 'key.q'); + validateString(key.dp, 'key.dp'); + validateString(key.dq, 'key.dq'); + validateString(key.qi, 'key.qi'); + jwk.d = key.d; + jwk.p = key.p; + jwk.q = key.q; + jwk.dp = key.dp; + jwk.dq = key.dq; + jwk.qi = key.qi; + } + + const handle = new KeyObjectHandle(); + const type = handle.initJwk(jwk); + if (type === undefined) + throw new ERR_CRYPTO_INVALID_JWK(); + + return handle; +} + +function prepareAsymmetricKey(key, ctx) { + if (isKeyObject(key)) { + // Best case: A key object, as simple as that. + return { data: getKeyObjectHandle(key, ctx) }; + } else if (isCryptoKey(key)) { + return { data: getKeyObjectHandle(key[kKeyObject], ctx) }; + } else if (isStringOrBuffer(key)) { + // Expect PEM by default, mostly for backward compatibility. + return { format: kKeyFormatPEM, data: getArrayBufferOrView(key, 'key') }; + } else if (typeof key === 'object') { + const { key: data, encoding, format } = key; + + // The 'key' property can be a KeyObject as well to allow specifying + // additional options such as padding along with the key. + if (isKeyObject(data)) + return { data: getKeyObjectHandle(data, ctx) }; + else if (isCryptoKey(data)) + return { data: getKeyObjectHandle(data[kKeyObject], ctx) }; + else if (format === 'jwk') { + validateObject(data, 'key.key'); + return { data: getKeyObjectHandleFromJwk(data, ctx), format: 'jwk' }; + } + + // Either PEM or DER using PKCS#1 or SPKI. + if (!isStringOrBuffer(data)) { + throw new ERR_INVALID_ARG_TYPE( + 'key.key', + getKeyTypes(ctx !== kCreatePrivate), + data); + } + + const isPublic = + (ctx === kConsumePrivate || ctx === kCreatePrivate) ? false : undefined; + return { + data: getArrayBufferOrView(data, 'key', encoding), + ...parseKeyEncoding(key, undefined, isPublic) + }; + } + throw new ERR_INVALID_ARG_TYPE( + 'key', + getKeyTypes(ctx !== kCreatePrivate), + key); +} + +function preparePrivateKey(key) { + return prepareAsymmetricKey(key, kConsumePrivate); +} + +function preparePublicOrPrivateKey(key) { + return prepareAsymmetricKey(key, kConsumePublic); +} + +function prepareSecretKey(key, encoding, bufferOnly = false) { + if (!bufferOnly) { + if (isKeyObject(key)) { + if (key.type !== 'secret') + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret'); + return key[kHandle]; + } else if (isCryptoKey(key)) { + if (key.type !== 'secret') + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret'); + return key[kKeyObject][kHandle]; + } + } + if (typeof key !== 'string' && + !isArrayBufferView(key) && + !isAnyArrayBuffer(key)) { + throw new ERR_INVALID_ARG_TYPE( + 'key', + getKeyTypes(!bufferOnly, bufferOnly), + key); + } + return getArrayBufferOrView(key, 'key', encoding); +} + +function createSecretKey(key, encoding) { + key = prepareSecretKey(key, encoding, true); + const handle = new SecretKeyHandle(key); + // handle.init(kKeyTypeSecret, key); + return new SecretKeyObject(handle); +} + +function createPublicKey(key) { + const { format, type, data, passphrase } = + prepareAsymmetricKey(key, kCreatePublic); + let handle; + if (format === 'jwk') { + handle = data; + } else { + handle = new KeyObjectHandle(); + handle.init(kKeyTypePublic, data, format, type, passphrase); + } + return new PublicKeyObject(handle); +} + +function createPrivateKey(key) { + const { format, type, data, passphrase } = + prepareAsymmetricKey(key, kCreatePrivate); + let handle; + if (format === 'jwk') { + handle = data; + } else { + handle = new KeyObjectHandle(); + handle.init(kKeyTypePrivate, data, format, type, passphrase); + } + return new PrivateKeyObject(handle); +} + +function isKeyObject(obj) { + return obj != null && obj[kKeyType] !== undefined; +} + +// Our implementation of CryptoKey is a simple wrapper around a KeyObject +// that adapts it to the standard interface. This implementation also +// extends the JSTransferable class, allowing the CryptoKey to be cloned +// to Workers. +// TODO(@jasnell): Embedder environments like electron may have issues +// here similar to other things like URL. A chromium provided CryptoKey +// will not be recognized as a Node.js CryptoKey, and vice versa. It +// would be fantastic if we could find a way of making those interop. +class CryptoKey /*extends JSTransferable*/ { + constructor() { + throw new ERR_ILLEGAL_CONSTRUCTOR(); + } + + [kInspect](depth, options) { + if (depth < 0) + return this; + + const opts = { + ...options, + depth: options.depth == null ? null : options.depth - 1 + }; + + return `CryptoKey ${inspect({ + type: this.type, + extractable: this.extractable, + algorithm: this.algorithm, + usages: this.usages + }, opts)}`; + } + + get type() { + return this[kKeyObject].type; + } + + get extractable() { + return this[kExtractable]; + } + + get algorithm() { + return this[kAlgorithm]; + } + + get usages() { + return Array.from(this[kKeyUsages]); + } + + [kClone]() { + const keyObject = this[kKeyObject]; + const algorithm = this.algorithm; + const extractable = this.extractable; + const usages = this.usages; + + return { + data: { + keyObject, + algorithm, + usages, + extractable, + }, + deserializeInfo: 'internal/crypto/keys:InternalCryptoKey' + }; + } + + [kDeserialize]({ keyObject, algorithm, usages, extractable }) { + this[kKeyObject] = keyObject; + this[kAlgorithm] = algorithm; + this[kKeyUsages] = usages; + this[kExtractable] = extractable; + } +} + +// All internal code must use new InternalCryptoKey to create +// CryptoKey instances. The CryptoKey class is exposed to end +// user code but is not permitted to be constructed directly. +class InternalCryptoKey /*extends JSTransferable*/ { + constructor( + keyObject, + algorithm, + keyUsages, + extractable) { + // super(); + // Using symbol properties here currently instead of private + // properties because (for now) the performance penalty of + // private fields is still too high. + this[kKeyObject] = keyObject; + this[kAlgorithm] = algorithm; + this[kExtractable] = extractable; + this[kKeyUsages] = keyUsages; + } +} + +InternalCryptoKey.prototype.constructor = CryptoKey; +Object.setPrototypeOf(InternalCryptoKey.prototype, CryptoKey.prototype); + +function isCryptoKey(obj) { + return obj != null && obj[kKeyObject] !== undefined; +} + +export { + // Public API. + createSecretKey, + createPublicKey, + createPrivateKey, + KeyObject, + CryptoKey, + InternalCryptoKey, + + // These are designed for internal use only and should not be exposed. + parsePublicKeyEncoding, + parsePrivateKeyEncoding, + parseKeyEncoding, + preparePrivateKey, + preparePublicOrPrivateKey, + prepareSecretKey, + SecretKeyObject, + PublicKeyObject, + PrivateKeyObject, + isKeyObject, + isCryptoKey, +}; diff --git a/modules/internal/crypto/pbkdf2.js b/modules/internal/crypto/pbkdf2.js new file mode 100644 index 0000000..e6b54ca --- /dev/null +++ b/modules/internal/crypto/pbkdf2.js @@ -0,0 +1,114 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +'use strict'; + + +import { Buffer } from 'buffer'; + +import { validateFunction, validateInteger, validateString, validateUint32 } from '../validators'; + +import { ERR_CRYPTO_INVALID_DIGEST, ERR_MISSING_OPTION } from '../errors'; + +import { getArrayBufferOrView, getDefaultEncoding, normalizeHashName, kKeyObject } from './util'; + +import { lazyDOMException } from '../util'; + +import { pbkdf2_sync } from "_node:crypto"; + +export function pbkdf2(password, salt, iterations, keylen, digest, callback) { + if (typeof digest === 'function') { + callback = digest; + digest = undefined; + } + + ({ password, salt, iterations, keylen, digest } = + check(password, salt, iterations, keylen, digest)); + + validateFunction(callback, 'callback'); + + if (!["SHA256", "SHA512"].includes(digest.toUpperCase())) { + throw new ERR_CRYPTO_INVALID_DIGEST(digest); + } + + const encoding = getDefaultEncoding(); + + setTimeout(() => { + let result = pbkdf2_sync(password.buffer ?? password, salt.buffer ?? salt, iterations, keylen, digest.toUpperCase()); + const buf = Buffer.from(result); + if (encoding === 'buffer') { + callback(null, buf); + } else { + callback(null, buf.toString(encoding)); + } + }, 0); +} + +export function pbkdf2Sync(password, salt, iterations, keylen, digest) { + ({ password, salt, iterations, keylen, digest } = + check(password, salt, iterations, keylen, digest)); + + if (!["SHA256", "SHA512"].includes(digest.toUpperCase())) { + throw new ERR_CRYPTO_INVALID_DIGEST(digest); + } + + let result = pbkdf2_sync(password.buffer ?? password, salt.buffer ?? salt, iterations, keylen, digest.toUpperCase()); + + const buf = Buffer.from(result); + const encoding = getDefaultEncoding(); + return encoding === 'buffer' ? buf : buf.toString(encoding); +} + +function check(password, salt, iterations, keylen, digest) { + validateString(digest, 'digest'); + + password = getArrayBufferOrView(password, 'password'); + salt = getArrayBufferOrView(salt, 'salt'); + validateUint32(iterations, 'iterations', true); + validateUint32(keylen, 'keylen'); + + return { password, salt, iterations, keylen, digest }; +} + +export async function pbkdf2DeriveBits(algorithm, baseKey, length) { + const { iterations } = algorithm; + let { hash } = algorithm; + const salt = getArrayBufferOrView(algorithm.salt, 'algorithm.salt'); + if (hash === undefined) + throw new ERR_MISSING_OPTION('algorithm.hash'); + validateInteger(iterations, 'algorithm.iterations'); + if (iterations === 0) + throw lazyDOMException( + 'iterations cannot be zero', + 'OperationError'); + + hash = normalizeHashName(hash.name); + + const raw = baseKey[kKeyObject].export(); + + let byteLength = 64; // the default + if (length !== undefined) { + if (length === 0) + throw lazyDOMException('length cannot be zero', 'OperationError'); + if (length === null) + throw lazyDOMException('length cannot be null', 'OperationError'); + validateUint32(length, 'length'); + if (length % 8) { + throw lazyDOMException( + 'length must be a multiple of 8', + 'OperationError'); + } + byteLength = length / 8; + } + + return new Promise((resolve, reject) => { + pbkdf2(raw, salt, iterations, byteLength, hash, (err, result) => { + if (err) return reject(err); + resolve(result.buffer); + }); + }); +} + +export default { + pbkdf2, + pbkdf2Sync, + pbkdf2DeriveBits, +}; diff --git a/modules/internal/crypto/random.js b/modules/internal/crypto/random.js new file mode 100644 index 0000000..e1ba6f8 --- /dev/null +++ b/modules/internal/crypto/random.js @@ -0,0 +1,462 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +'use strict'; + +import { kEmptyObject } from '../util'; + +import { Buffer, kMaxLength, FastBuffer } from '../../buffer'; + +import { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE, ERR_OPERATION_FAILED } from '../errors'; + +import { validateNumber, validateBoolean, validateFunction, validateInt32, validateObject, validateUint32 } from '../validators'; + +import { isArrayBufferView, isAnyArrayBuffer, isTypedArray, isFloat32Array, isFloat64Array } from '../util/types'; + +import { lazyDOMException } from '../util'; + +import { random_fill } from "_node:crypto"; + +import process from "process"; + +const kMaxInt32 = 2 ** 31 - 1; +const kMaxPossibleLength = Math.min(kMaxLength, kMaxInt32); + +function assertOffset(offset, elementSize, length) { + validateNumber(offset, 'offset'); + offset *= elementSize; + + const maxLength = Math.min(length, kMaxPossibleLength); + if (Number.isNaN(offset) || offset > maxLength || offset < 0) { + throw new ERR_OUT_OF_RANGE('offset', `>= 0 && <= ${maxLength}`, offset); + } + + return offset >>> 0; // Convert to uint32. +} + +function assertSize(size, elementSize, offset, length) { + validateNumber(size, 'size'); + size *= elementSize; + + if (Number.isNaN(size) || size > kMaxPossibleLength || size < 0) { + throw new ERR_OUT_OF_RANGE('size', + `>= 0 && <= ${kMaxPossibleLength}`, size); + } + + if (size + offset > length) { + throw new ERR_OUT_OF_RANGE('size + offset', `<= ${length}`, size + offset); + } + + return size >>> 0; // Convert to uint32. +} + +function randomBytes(size, callback) { + size = assertSize(size, 1, 0, Infinity); + if (callback !== undefined) { + validateFunction(callback, 'callback'); + } + + const buf = new Buffer(size); + + if (callback === undefined) { + randomFillSync(buf.buffer, 0, size); + return buf; + } + + // Keep the callback as a regular function so this is propagated. + randomFill(buf.buffer, 0, size, function (error) { + if (error) return Function.prototype.call.call(callback, this, error); + Function.prototype.call.call(callback, this, null, buf); + }); +} + +function randomFillSync(buf, offset = 0, size) { + if (!isAnyArrayBuffer(buf) && !isArrayBufferView(buf)) { + throw new ERR_INVALID_ARG_TYPE( + 'buf', + ['ArrayBuffer', 'ArrayBufferView'], + buf); + } + + const elementSize = buf.BYTES_PER_ELEMENT || 1; + + offset = assertOffset(offset, elementSize, buf.byteLength); + + if (size === undefined) { + size = buf.byteLength - offset; + } else { + size = assertSize(size, elementSize, offset, buf.byteLength); + } + + if (size === 0) + return buf; + + random_fill(buf.buffer ?? buf, offset + (buf.byteOffset ?? 0), size); + return buf; +} + +function randomFill(buf, offset, size, callback) { + if (!isAnyArrayBuffer(buf) && !isArrayBufferView(buf)) { + throw new ERR_INVALID_ARG_TYPE( + 'buf', + ['ArrayBuffer', 'ArrayBufferView'], + buf); + } + + const elementSize = buf.BYTES_PER_ELEMENT || 1; + + if (typeof offset === 'function') { + callback = offset; + offset = 0; + // Size is a length here, assertSize() call turns it into a number of bytes + size = buf.length; + } else if (typeof size === 'function') { + callback = size; + size = buf.length - offset; + } else { + validateFunction(callback, 'callback'); + } + + offset = assertOffset(offset, elementSize, buf.byteLength); + + if (size === undefined) { + size = buf.byteLength - offset; + } else { + size = assertSize(size, elementSize, offset, buf.byteLength); + } + + if (size === 0) { + callback(null, buf); + return; + } + + setTimeout(() => { + random_fill(buf.buffer ?? buf, offset + (buf.byteOffset ?? 0), size); + callback(null, buf); + }, 0); +} + +// Largest integer we can read from a buffer. +// e.g.: Buffer.from("ff".repeat(6), "hex").readUIntBE(0, 6); +const RAND_MAX = 0xFFFF_FFFF_FFFF; + +// Cache random data to use in randomInt. The cache size must be evenly +// divisible by 6 because each attempt to obtain a random int uses 6 bytes. +const randomCache = new Buffer(6 * 1024); +let randomCacheOffset = randomCache.length; +let asyncCacheFillInProgress = false; +const asyncCachePendingTasks = []; + +// Generates an integer in [min, max) range where min is inclusive and max is +// exclusive. +function randomInt(min, max, callback) { + // Detect optional min syntax + // randomInt(max) + // randomInt(max, callback) + const minNotSpecified = typeof max === 'undefined' || + typeof max === 'function'; + + if (minNotSpecified) { + callback = max; + max = min; + min = 0; + } + + const isSync = typeof callback === 'undefined'; + if (!isSync) { + validateFunction(callback, 'callback'); + } + if (!Number.isSafeInteger(min)) { + throw new ERR_INVALID_ARG_TYPE('min', 'a safe integer', min); + } + if (!Number.isSafeInteger(max)) { + throw new ERR_INVALID_ARG_TYPE('max', 'a safe integer', max); + } + if (max <= min) { + throw new ERR_OUT_OF_RANGE( + 'max', `greater than the value of "min" (${min})`, max + ); + } + + // First we generate a random int between [0..range) + const range = max - min; + + if (!(range <= RAND_MAX)) { + throw new ERR_OUT_OF_RANGE(`max${minNotSpecified ? '' : ' - min'}`, + `<= ${RAND_MAX}`, range); + } + + // For (x % range) to produce an unbiased value greater than or equal to 0 and + // less than range, x must be drawn randomly from the set of integers greater + // than or equal to 0 and less than randLimit. + const randLimit = RAND_MAX - (RAND_MAX % range); + + // If we don't have a callback, or if there is still data in the cache, we can + // do this synchronously, which is super fast. + while (isSync || (randomCacheOffset < randomCache.length)) { + if (randomCacheOffset === randomCache.length) { + // This might block the thread for a bit, but we are in sync mode. + randomFillSync(randomCache); + randomCacheOffset = 0; + } + + const x = randomCache.readUIntBE(randomCacheOffset, 6); + randomCacheOffset += 6; + + if (x < randLimit) { + const n = (x % range) + min; + if (isSync) return n; + process.nextTick(callback, undefined, n); + return; + } + } + + // At this point, we are in async mode with no data in the cache. We cannot + // simply refill the cache, because another async call to randomInt might + // already be doing that. Instead, queue this call for when the cache has + // been refilled. + Array.prototype.push.call(asyncCachePendingTasks, { min, max, callback }); + asyncRefillRandomIntCache(); +} + +function asyncRefillRandomIntCache() { + if (asyncCacheFillInProgress) + return; + + asyncCacheFillInProgress = true; + randomFill(randomCache, (err) => { + asyncCacheFillInProgress = false; + + const tasks = asyncCachePendingTasks; + const errorReceiver = err && Array.prototype.shift.call(tasks); + if (!err) + randomCacheOffset = 0; + + // Restart all pending tasks. If an error occurred, we only notify a single + // callback (errorReceiver) about it. This way, every async call to + // randomInt has a chance of being successful, and it avoids complex + // exception handling here. + Array.prototype.forEach.call(Array.prototype.splice.call(tasks, 0), (task) => { + randomInt(task.min, task.max, task.callback); + }); + + // This is the only call that might throw, and is therefore done at the end. + if (errorReceiver) + errorReceiver.callback(err); + }); +} + +// Really just the Web Crypto API alternative +// to require('crypto').randomFillSync() with an +// additional limitation that the input buffer is +// not allowed to exceed 65536 bytes, and can only +// be an integer-type TypedArray. +function getRandomValues(data) { + if (!isTypedArray(data) || + isFloat32Array(data) || + isFloat64Array(data)) { + // Ordinarily this would be an ERR_INVALID_ARG_TYPE. However, + // the Web Crypto API and web platform tests expect this to + // be a DOMException with type TypeMismatchError. + throw lazyDOMException( + 'The data argument must be an integer-type TypedArray', + 'TypeMismatchError'); + } + if (data.byteLength > 65536) { + throw lazyDOMException( + 'The requested length exceeds 65,536 bytes', + 'QuotaExceededError'); + } + randomFillSync(data, 0); + return data; +} + +// Implements an RFC 4122 version 4 random UUID. +// To improve performance, random data is generated in batches +// large enough to cover kBatchSize UUID's at a time. The uuidData +// buffer is reused. Each call to randomUUID() consumes 16 bytes +// from the buffer. + +const kBatchSize = 128; +let uuidData; +let uuidNotBuffered; +let uuidBatch = 0; + +let hexBytesCache; +function getHexBytes() { + if (hexBytesCache === undefined) { + hexBytesCache = new Array(256); + for (let i = 0; i < hexBytesCache.length; i++) { + const hex = Number.prototype.toString.call(i, 16); + hexBytesCache[i] = String.prototype.padStart.call(hex, 2, '0'); + } + } + return hexBytesCache; +} + +function serializeUUID(buf, offset = 0) { + const kHexBytes = getHexBytes(); + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + return kHexBytes[buf[offset]] + + kHexBytes[buf[offset + 1]] + + kHexBytes[buf[offset + 2]] + + kHexBytes[buf[offset + 3]] + + '-' + + kHexBytes[buf[offset + 4]] + + kHexBytes[buf[offset + 5]] + + '-' + + kHexBytes[(buf[offset + 6] & 0x0f) | 0x40] + + kHexBytes[buf[offset + 7]] + + '-' + + kHexBytes[(buf[offset + 8] & 0x3f) | 0x80] + + kHexBytes[buf[offset + 9]] + + '-' + + kHexBytes[buf[offset + 10]] + + kHexBytes[buf[offset + 11]] + + kHexBytes[buf[offset + 12]] + + kHexBytes[buf[offset + 13]] + + kHexBytes[buf[offset + 14]] + + kHexBytes[buf[offset + 15]]; +} + +function getBufferedUUID() { + // uuidData ??= secureBuffer(16 * kBatchSize); + uuidData ??= new Uint8Array(16 * kBatchSize); + if (uuidData === undefined) + throw new ERR_OPERATION_FAILED('Out of memory'); + + if (uuidBatch === 0) randomFillSync(uuidData); + uuidBatch = (uuidBatch + 1) % kBatchSize; + return serializeUUID(uuidData, uuidBatch * 16); +} + +function getUnbufferedUUID() { + // uuidNotBuffered ??= secureBuffer(16); + uuidNotBuffered ??= new Uint8Array(16); + if (uuidNotBuffered === undefined) + throw new ERR_OPERATION_FAILED('Out of memory'); + randomFillSync(uuidNotBuffered); + return serializeUUID(uuidNotBuffered); +} + +function randomUUID(options) { + if (options !== undefined) + validateObject(options, 'options'); + const { + disableEntropyCache = false, + } = options || kEmptyObject; + + validateBoolean(disableEntropyCache, 'options.disableEntropyCache'); + + return disableEntropyCache ? getUnbufferedUUID() : getBufferedUUID(); +} + +function generatePrime(size, options, callback) { + validateInt32(size, 'size', 1); + if (typeof options === 'function') { + callback = options; + options = kEmptyObject; + } + validateFunction(callback, 'callback'); + + throw new Error("crypto.generatePrime is unimplemented"); +} + +function generatePrimeSync(size, options = kEmptyObject) { + validateInt32(size, 'size', 1); + + throw new Error("crypto.generatePrimeSync is unimplemented"); + +} + +function unsignedBigIntToBuffer(bigint, name) { + if (bigint < 0) { + throw new ERR_OUT_OF_RANGE(name, '>= 0', bigint); + } + + const hex = bigint.toString(16); + const padded = hex.padStart(hex.length + (hex.length % 2), 0); + return Buffer.from(padded, 'hex'); +} + +function checkPrime(candidate, options = kEmptyObject, callback) { + if (typeof candidate === 'bigint') + candidate = unsignedBigIntToBuffer(candidate, 'candidate'); + if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) { + throw new ERR_INVALID_ARG_TYPE( + 'candidate', + [ + 'ArrayBuffer', + 'TypedArray', + 'Buffer', + 'DataView', + 'bigint', + ], + candidate + ); + } + if (typeof options === 'function') { + callback = options; + options = kEmptyObject; + } + validateFunction(callback, 'callback'); + validateObject(options, 'options'); + const { + checks = 0, + } = options; + + validateUint32(checks, 'options.checks'); + + throw new Error("crypto.checkPrime is unimplemented"); + +} + +function checkPrimeSync(candidate, options = kEmptyObject) { + if (typeof candidate === 'bigint') + candidate = unsignedBigIntToBuffer(candidate, 'candidate'); + if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) { + throw new ERR_INVALID_ARG_TYPE( + 'candidate', + [ + 'ArrayBuffer', + 'TypedArray', + 'Buffer', + 'DataView', + 'bigint', + ], + candidate + ); + } + validateObject(options, 'options'); + const { + checks = 0, + } = options; + + validateUint32(checks, 'options.checks'); + + throw new Error("crypto.checkPrimeSync is unimplemented"); +} + +export { + checkPrime, + checkPrimeSync, + randomBytes, + randomFill, + randomFillSync, + randomInt, + getRandomValues, + randomUUID, + generatePrime, + generatePrimeSync, +} + +export default { + checkPrime, + checkPrimeSync, + randomBytes, + randomFill, + randomFillSync, + randomInt, + getRandomValues, + randomUUID, + generatePrime, + generatePrimeSync, +}; diff --git a/modules/internal/crypto/scrypt.js b/modules/internal/crypto/scrypt.js new file mode 100644 index 0000000..1525b87 --- /dev/null +++ b/modules/internal/crypto/scrypt.js @@ -0,0 +1,133 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +'use strict'; + +import { Buffer } from 'buffer'; + +import { + validateFunction, + validateInteger, + validateInt32, + validateUint32, +} from '../validators'; + +import { + ERR_CRYPTO_SCRYPT_INVALID_PARAMETER, + ERR_CRYPTO_SCRYPT_NOT_SUPPORTED, +} from '../errors'; + +import { + getArrayBufferOrView, + getDefaultEncoding, +} from './util'; + +import { scrypt_sync } from "_node:crypto"; + +const defaults = { + N: 16384, + r: 8, + p: 1, + maxmem: 32 << 20, // 32 MiB, matches SCRYPT_MAX_MEM. +}; + +function scrypt(password, salt, keylen, options, callback = defaults) { + if (callback === defaults) { + callback = options; + options = defaults; + } + + options = check(password, salt, keylen, options); + const { N, r, p, maxmem } = options; + ({ password, salt, keylen } = options); + + validateFunction(callback, 'callback'); + const encoding = getDefaultEncoding(); + setTimeout(() => { + let result = scrypt_sync(password.buffer ?? password, salt.buffer ?? salt, N, r, p, keylen); + const buf = Buffer.from(result); + if (encoding === 'buffer') { + callback(null, buf); + } else { + callback(null, buf.toString(encoding)); + } + }, 0); +} + +function scryptSync(password, salt, keylen, options = defaults) { + options = check(password, salt, keylen, options); + const { N, r, p, maxmem } = options; + ({ password, salt, keylen } = options); + + let result = scrypt_sync(password.buffer ?? password, salt.buffer ?? salt, N, r, p, keylen); + + const buf = Buffer.from(result); + const encoding = getDefaultEncoding(); + return encoding === 'buffer' ? buf : buf.toString(encoding); +} + +function check(password, salt, keylen, options) { + /*if (ScryptJob === undefined) + throw new ERR_CRYPTO_SCRYPT_NOT_SUPPORTED();*/ + + password = getArrayBufferOrView(password, 'password'); + salt = getArrayBufferOrView(salt, 'salt'); + validateInt32(keylen, 'keylen', 0); + + let { N, r, p, maxmem } = defaults; + if (options && options !== defaults) { + const has_N = options.N !== undefined; + if (has_N) { + N = options.N; + validateUint32(N, 'N'); + } + if (options.cost !== undefined) { + if (has_N) throw new ERR_CRYPTO_SCRYPT_INVALID_PARAMETER(); + N = options.cost; + validateUint32(N, 'cost'); + } + const has_r = (options.r !== undefined); + if (has_r) { + r = options.r; + validateUint32(r, 'r'); + } + if (options.blockSize !== undefined) { + if (has_r) throw new ERR_CRYPTO_SCRYPT_INVALID_PARAMETER(); + r = options.blockSize; + validateUint32(r, 'blockSize'); + } + const has_p = options.p !== undefined; + if (has_p) { + p = options.p; + validateUint32(p, 'p'); + } + if (options.parallelization !== undefined) { + if (has_p) throw new ERR_CRYPTO_SCRYPT_INVALID_PARAMETER(); + p = options.parallelization; + validateUint32(p, 'parallelization'); + } + if (options.maxmem !== undefined) { + maxmem = options.maxmem; + validateInteger(maxmem, 'maxmem', 0); + } + if (N === 0) N = defaults.N; + if (r === 0) r = defaults.r; + if (p === 0) p = defaults.p; + if (maxmem === 0) maxmem = defaults.maxmem; + } + + if (Math.log2(N) % 1 !== 0 || N <= 1) { + throw new ERR_CRYPTO_SCRYPT_INVALID_PARAMETER(); + } + + let blen = p * 128 * r + let vlen = 32 * r * (N + 2) * 4 + if (vlen + blen > maxmem || 128 * N * r > maxmem || N >= 2 ** (r * 16) || p > (2 ** 30 - 1) / r) { + throw new ERR_CRYPTO_SCRYPT_INVALID_PARAMETER(); + } + + return { password, salt, keylen, N, r, p, maxmem }; +} + +export { + scrypt, + scryptSync, +}; diff --git a/modules/internal/crypto/sig.js b/modules/internal/crypto/sig.js new file mode 100644 index 0000000..44784ea --- /dev/null +++ b/modules/internal/crypto/sig.js @@ -0,0 +1,307 @@ +'use strict'; + +const { + FunctionPrototypeCall, + ObjectSetPrototypeOf, + ReflectApply, +} = primordials; + +const { + codes: { + ERR_CRYPTO_SIGN_KEY_REQUIRED, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + } +} = require('internal/errors'); + +const { + validateFunction, + validateEncoding, + validateString, +} = require('internal/validators'); + +const { + Sign: _Sign, + SignJob, + Verify: _Verify, + kCryptoJobAsync, + kCryptoJobSync, + kSigEncDER, + kSigEncP1363, + kSignJobModeSign, + kSignJobModeVerify, +} = internalBinding('crypto'); + +const { + getArrayBufferOrView, + getDefaultEncoding, + kHandle, +} = require('internal/crypto/util'); + +const { + preparePrivateKey, + preparePublicOrPrivateKey, +} = require('internal/crypto/keys'); + +const { Writable } = require('stream'); + +const { Buffer } = require('buffer'); + +const { + isArrayBufferView, +} = require('internal/util/types'); + +function Sign(algorithm, options) { + if (!(this instanceof Sign)) + return new Sign(algorithm, options); + validateString(algorithm, 'algorithm'); + this[kHandle] = new _Sign(); + this[kHandle].init(algorithm); + + ReflectApply(Writable, this, [options]); +} + +ObjectSetPrototypeOf(Sign.prototype, Writable.prototype); +ObjectSetPrototypeOf(Sign, Writable); + +Sign.prototype._write = function _write(chunk, encoding, callback) { + this.update(chunk, encoding); + callback(); +}; + +Sign.prototype.update = function update(data, encoding) { + encoding = encoding || getDefaultEncoding(); + + if (typeof data === 'string') { + validateEncoding(data, encoding); + } else if (!isArrayBufferView(data)) { + throw new ERR_INVALID_ARG_TYPE( + 'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data); + } + + this[kHandle].update(data, encoding); + return this; +}; + +function getPadding(options) { + return getIntOption('padding', options); +} + +function getSaltLength(options) { + return getIntOption('saltLength', options); +} + +function getDSASignatureEncoding(options) { + if (typeof options === 'object') { + const { dsaEncoding = 'der' } = options; + if (dsaEncoding === 'der') + return kSigEncDER; + else if (dsaEncoding === 'ieee-p1363') + return kSigEncP1363; + throw new ERR_INVALID_ARG_VALUE('options.dsaEncoding', dsaEncoding); + } + + return kSigEncDER; +} + +function getIntOption(name, options) { + const value = options[name]; + if (value !== undefined) { + if (value === value >> 0) { + return value; + } + throw new ERR_INVALID_ARG_VALUE(`options.${name}`, value); + } + return undefined; +} + +Sign.prototype.sign = function sign(options, encoding) { + if (!options) + throw new ERR_CRYPTO_SIGN_KEY_REQUIRED(); + + const { data, format, type, passphrase } = preparePrivateKey(options, true); + + // Options specific to RSA + const rsaPadding = getPadding(options); + const pssSaltLength = getSaltLength(options); + + // Options specific to (EC)DSA + const dsaSigEnc = getDSASignatureEncoding(options); + + const ret = this[kHandle].sign(data, format, type, passphrase, rsaPadding, + pssSaltLength, dsaSigEnc); + + encoding = encoding || getDefaultEncoding(); + if (encoding && encoding !== 'buffer') + return ret.toString(encoding); + + return ret; +}; + +function signOneShot(algorithm, data, key, callback) { + if (algorithm != null) + validateString(algorithm, 'algorithm'); + + if (callback !== undefined) + validateFunction(callback, 'callback'); + + data = getArrayBufferOrView(data, 'data'); + + if (!key) + throw new ERR_CRYPTO_SIGN_KEY_REQUIRED(); + + // Options specific to RSA + const rsaPadding = getPadding(key); + const pssSaltLength = getSaltLength(key); + + // Options specific to (EC)DSA + const dsaSigEnc = getDSASignatureEncoding(key); + + const { + data: keyData, + format: keyFormat, + type: keyType, + passphrase: keyPassphrase + } = preparePrivateKey(key); + + const job = new SignJob( + callback ? kCryptoJobAsync : kCryptoJobSync, + kSignJobModeSign, + keyData, + keyFormat, + keyType, + keyPassphrase, + data, + algorithm, + pssSaltLength, + rsaPadding, + dsaSigEnc); + + if (!callback) { + const { 0: err, 1: signature } = job.run(); + if (err !== undefined) + throw err; + + return Buffer.from(signature); + } + + job.ondone = (error, signature) => { + if (error) return FunctionPrototypeCall(callback, job, error); + FunctionPrototypeCall(callback, job, null, Buffer.from(signature)); + }; + job.run(); +} + +function Verify(algorithm, options) { + if (!(this instanceof Verify)) + return new Verify(algorithm, options); + validateString(algorithm, 'algorithm'); + this[kHandle] = new _Verify(); + this[kHandle].init(algorithm); + + ReflectApply(Writable, this, [options]); +} + +ObjectSetPrototypeOf(Verify.prototype, Writable.prototype); +ObjectSetPrototypeOf(Verify, Writable); + +Verify.prototype._write = Sign.prototype._write; +Verify.prototype.update = Sign.prototype.update; + +Verify.prototype.verify = function verify(options, signature, sigEncoding) { + const { + data, + format, + type, + passphrase + } = preparePublicOrPrivateKey(options, true); + + sigEncoding = sigEncoding || getDefaultEncoding(); + + // Options specific to RSA + const rsaPadding = getPadding(options); + const pssSaltLength = getSaltLength(options); + + // Options specific to (EC)DSA + const dsaSigEnc = getDSASignatureEncoding(options); + + signature = getArrayBufferOrView(signature, 'signature', sigEncoding); + + return this[kHandle].verify(data, format, type, passphrase, signature, + rsaPadding, pssSaltLength, dsaSigEnc); +}; + +function verifyOneShot(algorithm, data, key, signature, callback) { + if (algorithm != null) + validateString(algorithm, 'algorithm'); + + if (callback !== undefined) + validateFunction(callback, 'callback'); + + data = getArrayBufferOrView(data, 'data'); + + if (!isArrayBufferView(data)) { + throw new ERR_INVALID_ARG_TYPE( + 'data', + ['Buffer', 'TypedArray', 'DataView'], + data + ); + } + + // Options specific to RSA + const rsaPadding = getPadding(key); + const pssSaltLength = getSaltLength(key); + + // Options specific to (EC)DSA + const dsaSigEnc = getDSASignatureEncoding(key); + + if (!isArrayBufferView(signature)) { + throw new ERR_INVALID_ARG_TYPE( + 'signature', + ['Buffer', 'TypedArray', 'DataView'], + signature + ); + } + + const { + data: keyData, + format: keyFormat, + type: keyType, + passphrase: keyPassphrase + } = preparePublicOrPrivateKey(key); + + const job = new SignJob( + callback ? kCryptoJobAsync : kCryptoJobSync, + kSignJobModeVerify, + keyData, + keyFormat, + keyType, + keyPassphrase, + data, + algorithm, + pssSaltLength, + rsaPadding, + dsaSigEnc, + signature); + + if (!callback) { + const { 0: err, 1: result } = job.run(); + if (err !== undefined) + throw err; + + return result; + } + + job.ondone = (error, result) => { + if (error) return FunctionPrototypeCall(callback, job, error); + FunctionPrototypeCall(callback, job, null, result); + }; + job.run(); +} + +module.exports = { + Sign, + signOneShot, + Verify, + verifyOneShot, +}; diff --git a/modules/internal/crypto/util.js b/modules/internal/crypto/util.js new file mode 100644 index 0000000..e0d88ff --- /dev/null +++ b/modules/internal/crypto/util.js @@ -0,0 +1,411 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +'use strict'; + +/*const { + getCiphers: _getCiphers, + getCurves: _getCurves, + getHashes: _getHashes, + setEngine: _setEngine, + secureHeapUsed: _secureHeapUsed, +} = internalBinding('crypto');*/ + +import { getOptionValue } from '../options'; + +import { crypto } from '../../internal_binding/constants'; +const { ENGINE_METHOD_ALL } = crypto; +import normalizeHashName from './hashnames'; + +import { + hideStackFrames, + ERR_CRYPTO_ENGINE_UNKNOWN, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_OUT_OF_RANGE, +} from '../errors'; + +import { + validateArray, + validateNumber, + validateString +} from '../validators'; + +import { Buffer } from 'buffer'; + +import { + cachedResult, + filterDuplicateStrings, + lazyDOMException, +} from '../util'; + +import { + isArrayBufferView, + isAnyArrayBuffer, +} from '../util/types'; + +const kHandle = Symbol('kHandle'); +const kKeyObject = Symbol('kKeyObject'); + +let defaultEncoding = 'buffer'; + +function setDefaultEncoding(val) { + defaultEncoding = val; +} + +function getDefaultEncoding() { + return defaultEncoding; +} + +// This is here because many functions accepted binary strings without +// any explicit encoding in older versions of node, and we don't want +// to break them unnecessarily. +function toBuf(val, encoding) { + if (typeof val === 'string') { + if (encoding === 'buffer') + encoding = 'utf8'; + return Buffer.from(val, encoding); + } + return val; +} + +const getCiphers = () => ["aes-128-gcm", "aes-256-gcm"]; +const getHashes = () => ["sha256", "sha512", "sha512-256"]; +const getCurves = () => []; + +function setEngine(id, flags) { + validateString(id, 'id'); + if (flags) + validateNumber(flags, 'flags'); + flags = flags >>> 0; + + // Use provided engine for everything by default + if (flags === 0) + flags = ENGINE_METHOD_ALL; + + /*if (!_setEngine(id, flags)) + throw new ERR_CRYPTO_ENGINE_UNKNOWN(id);*/ +} + +const getArrayBufferOrView = hideStackFrames((buffer, name, encoding) => { + if (isAnyArrayBuffer(buffer)) + return buffer; + if (typeof buffer === 'string') { + if (encoding === 'buffer') + encoding = 'utf8'; + return Buffer.from(buffer, encoding); + } + if (!isArrayBufferView(buffer)) { + throw new ERR_INVALID_ARG_TYPE( + name, + [ + 'string', + 'ArrayBuffer', + 'Buffer', + 'TypedArray', + 'DataView', + ], + buffer + ); + } + return buffer; +}); + +// The maximum buffer size that we'll support in the WebCrypto impl +const kMaxBufferLength = (2 ** 31) - 1; + +// The EC named curves that we currently support via the Web Crypto API. +const kNamedCurveAliases = { + 'P-256': 'prime256v1', + 'P-384': 'secp384r1', + 'P-521': 'secp521r1', +}; + +const kAesKeyLengths = [128, 192, 256]; + +// These are the only algorithms we currently support +// via the Web Crypto API +const kAlgorithms = { + 'rsassa-pkcs1-v1_5': 'RSASSA-PKCS1-v1_5', + 'rsa-pss': 'RSA-PSS', + 'rsa-oaep': 'RSA-OAEP', + 'ecdsa': 'ECDSA', + 'ecdh': 'ECDH', + 'aes-ctr': 'AES-CTR', + 'aes-cbc': 'AES-CBC', + 'aes-gcm': 'AES-GCM', + 'aes-kw': 'AES-KW', + 'hmac': 'HMAC', + 'sha-1': 'SHA-1', + 'sha-256': 'SHA-256', + 'sha-384': 'SHA-384', + 'sha-512': 'SHA-512', + 'hkdf': 'HKDF', + 'pbkdf2': 'PBKDF2', + 'ed25519': 'Ed25519', + 'ed448': 'Ed448', + 'x25519': 'X25519', + 'x448': 'X448', +}; +const kAlgorithmsKeys = Object.keys(kAlgorithms); + +// These are the only export and import formats we currently +// support via the Web Crypto API +const kExportFormats = [ + 'raw', + 'pkcs8', + 'spki', + 'jwk']; + +// These are the only hash algorithms we currently support via +// the Web Crypto API. +const kHashTypes = [ + 'SHA-1', + 'SHA-256', + 'SHA-384', + 'SHA-512', +]; + +function validateMaxBufferLength(data, name) { + if (data.byteLength > kMaxBufferLength) { + throw lazyDOMException( + `${name} must be less than ${kMaxBufferLength + 1} bits`, + 'OperationError'); + } +} + +function normalizeAlgorithm(algorithm) { + if (algorithm != null) { + if (typeof algorithm === 'string') + algorithm = { name: algorithm }; + + if (typeof algorithm === 'object') { + const { name } = algorithm; + if (typeof name !== 'string' || + !Array.prototype.includes.call( + kAlgorithmsKeys, + String.prototype.toLowerCase.call(name))) { + throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); + } + let { hash } = algorithm; + if (hash !== undefined) { + hash = normalizeAlgorithm(hash); + if (!Array.prototype.includes.call(kHashTypes, hash.name)) + throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); + } + const normalized = { + ...algorithm, + name: kAlgorithms[String.prototype.toLowerCase.call(name)], + }; + if (hash) { + normalized.hash = hash; + } + return normalized; + } + } + throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); +} + +function hasAnyNotIn(set, checks) { + for (const s of set) + if (!Array.prototype.includes.call(checks, s)) + return true; + return false; +} + +function validateBitLength(length, name, required = false) { + if (length !== undefined || required) { + validateNumber(length, name); + if (length < 0) + throw new ERR_OUT_OF_RANGE(name, '> 0'); + if (length % 8) { + throw new ERR_INVALID_ARG_VALUE( + name, + length, + 'must be a multiple of 8'); + } + } +} + +function validateByteLength(buf, name, target) { + if (buf.byteLength !== target) { + throw lazyDOMException( + `${name} must contain exactly ${target} bytes`, + 'OperationError'); + } +} + +const validateByteSource = hideStackFrames((val, name) => { + val = toBuf(val); + + if (isAnyArrayBuffer(val) || isArrayBufferView(val)) + return val; + + throw new ERR_INVALID_ARG_TYPE( + name, + [ + 'string', + 'ArrayBuffer', + 'TypedArray', + 'DataView', + 'Buffer', + ], + val); +}); + +function onDone(resolve, reject, err, result) { + if (err) { + // TODO(@panva): add err as cause to DOMException + return reject(lazyDOMException( + 'The operation failed for an operation-specific reason', + 'OperationError')); + } + resolve(result); +} + +function jobPromise(job) { + return new Promise((resolve, reject) => { + job.ondone = Function.prototype.bind.call(onDone, job, resolve, reject); + job.run(); + }); +} + +// In WebCrypto, the publicExponent option in RSA is represented as a +// WebIDL "BigInteger"... that is, a Uint8Array that allows an arbitrary +// number of leading zero bits. Our conventional APIs for reading +// an unsigned int from a Buffer are not adequate. The implementation +// here is adapted from the chromium implementation here: +// https://github.com/chromium/chromium/blob/HEAD/third_party/blink/public/platform/web_crypto_algorithm_params.h, but ported to JavaScript +// Returns undefined if the conversion was unsuccessful. +function bigIntArrayToUnsignedInt(input) { + let result = 0; + + for (let n = 0; n < input.length; ++n) { + const n_reversed = input.length - n - 1; + if (n_reversed >= 4 && input[n]) + return; // Too large + result |= input[n] << 8 * n_reversed; + } + + return result; +} + +function bigIntArrayToUnsignedBigInt(input) { + let result = 0n; + + for (let n = 0; n < input.length; ++n) { + const n_reversed = input.length - n - 1; + result |= BigInt(input[n]) << 8n * BigInt(n_reversed); + } + + return result; +} + +function getStringOption(options, key) { + let value; + if (options && (value = options[key]) != null) + validateString(value, `options.${key}`); + return value; +} + +function getUsagesUnion(usageSet, ...usages) { + const newset = []; + for (let n = 0; n < usages.length; n++) { + if (usageSet.has(usages[n])) + Array.prototype.push.call(newset, usages[n]); + } + return newset; +} + +function getHashLength(name) { + switch (name) { + case 'SHA-1': return 160; + case 'SHA-256': return 256; + case 'SHA-384': return 384; + case 'SHA-512': return 512; + } +} + +const kKeyOps = { + sign: 1, + verify: 2, + encrypt: 3, + decrypt: 4, + wrapKey: 5, + unwrapKey: 6, + deriveKey: 7, + deriveBits: 8, +}; + +function validateKeyOps(keyOps, usagesSet) { + if (keyOps === undefined) return; + validateArray(keyOps, 'keyData.key_ops'); + let flags = 0; + for (let n = 0; n < keyOps.length; n++) { + const op = keyOps[n]; + const op_flag = kKeyOps[op]; + // Skipping unknown key ops + if (op_flag === undefined) + continue; + // Have we seen it already? if so, error + if (flags & (1 << op_flag)) + throw lazyDOMException('Duplicate key operation', 'DataError'); + flags |= (1 << op_flag); + + // TODO(@jasnell): RFC7517 section 4.3 strong recommends validating + // key usage combinations. Specifically, it says that unrelated key + // ops SHOULD NOT be used together. We're not yet validating that here. + } + + if (usagesSet !== undefined) { + for (const use of usagesSet) { + if (!Array.prototype.includes.call(keyOps, use)) { + throw lazyDOMException( + 'Key operations and usage mismatch', + 'DataError'); + } + } + } +} + +function secureHeapUsed() { + /*const val = _secureHeapUsed(); + if (val === undefined) + return { total: 0, used: 0, utilization: 0, min: 0 }; + const used = Number(_secureHeapUsed()); + const total = Number(getOptionValue('--secure-heap')); + const min = Number(getOptionValue('--secure-heap-min')); + const utilization = used / total; + return { total, used, utilization, min };*/ +} + +export { + getArrayBufferOrView, + getCiphers, + getCurves, + getDefaultEncoding, + getHashes, + kHandle, + kKeyObject, + setDefaultEncoding, + setEngine, + toBuf, + + kHashTypes, + kNamedCurveAliases, + kAesKeyLengths, + kExportFormats, + normalizeAlgorithm, + normalizeHashName, + hasAnyNotIn, + validateBitLength, + validateByteLength, + validateByteSource, + validateKeyOps, + jobPromise, + validateMaxBufferLength, + bigIntArrayToUnsignedBigInt, + bigIntArrayToUnsignedInt, + getStringOption, + getUsagesUnion, + getHashLength, + secureHeapUsed, +}; diff --git a/modules/internal/crypto/x509.js b/modules/internal/crypto/x509.js new file mode 100644 index 0000000..97177e4 --- /dev/null +++ b/modules/internal/crypto/x509.js @@ -0,0 +1,368 @@ +'use strict'; + +const { + ObjectSetPrototypeOf, + SafeMap, + Symbol, +} = primordials; + +const { + parseX509, + X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT, + X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, + X509_CHECK_FLAG_NO_WILDCARDS, + X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS, + X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS, + X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS, +} = internalBinding('crypto'); + +const { + PublicKeyObject, + isKeyObject, +} = require('internal/crypto/keys'); + +const { + customInspectSymbol: kInspect, + kEmptyObject, +} = require('internal/util'); + +const { + validateBoolean, + validateObject, + validateString, +} = require('internal/validators'); + +const { inspect } = require('internal/util/inspect'); + +const { Buffer } = require('buffer'); + +const { + isArrayBufferView, +} = require('internal/util/types'); + +const { + codes: { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + } +} = require('internal/errors'); + +const { + JSTransferable, + kClone, + kDeserialize, +} = require('internal/worker/js_transferable'); + +const { + kHandle, +} = require('internal/crypto/util'); + +let lazyTranslatePeerCertificate; + +const kInternalState = Symbol('kInternalState'); + +function isX509Certificate(value) { + return value[kInternalState] !== undefined; +} + +function getFlags(options = kEmptyObject) { + validateObject(options, 'options'); + const { + subject = 'default', // Can be 'default', 'always', or 'never' + wildcards = true, + partialWildcards = true, + multiLabelWildcards = false, + singleLabelSubdomains = false, + } = { ...options }; + let flags = 0; + validateString(subject, 'options.subject'); + validateBoolean(wildcards, 'options.wildcards'); + validateBoolean(partialWildcards, 'options.partialWildcards'); + validateBoolean(multiLabelWildcards, 'options.multiLabelWildcards'); + validateBoolean(singleLabelSubdomains, 'options.singleLabelSubdomains'); + switch (subject) { + case 'default': /* Matches OpenSSL's default, no flags. */ break; + case 'always': flags |= X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT; break; + case 'never': flags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT; break; + default: + throw new ERR_INVALID_ARG_VALUE('options.subject', subject); + } + if (!wildcards) flags |= X509_CHECK_FLAG_NO_WILDCARDS; + if (!partialWildcards) flags |= X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS; + if (multiLabelWildcards) flags |= X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS; + if (singleLabelSubdomains) flags |= X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS; + return flags; +} + +class InternalX509Certificate extends JSTransferable { + [kInternalState] = new SafeMap(); + + constructor(handle) { + super(); + this[kHandle] = handle; + } +} + +class X509Certificate extends JSTransferable { + [kInternalState] = new SafeMap(); + + constructor(buffer) { + if (typeof buffer === 'string') + buffer = Buffer.from(buffer); + if (!isArrayBufferView(buffer)) { + throw new ERR_INVALID_ARG_TYPE( + 'buffer', + ['string', 'Buffer', 'TypedArray', 'DataView'], + buffer); + } + super(); + this[kHandle] = parseX509(buffer); + } + + [kInspect](depth, options) { + if (depth < 0) + return this; + + const opts = { + ...options, + depth: options.depth == null ? null : options.depth - 1 + }; + + return `X509Certificate ${inspect({ + subject: this.subject, + subjectAltName: this.subjectAltName, + issuer: this.issuer, + infoAccess: this.infoAccess, + validFrom: this.validFrom, + validTo: this.validTo, + fingerprint: this.fingerprint, + fingerprint256: this.fingerprint256, + fingerprint512: this.fingerprint512, + keyUsage: this.keyUsage, + serialNumber: this.serialNumber, + }, opts)}`; + } + + [kClone]() { + const handle = this[kHandle]; + return { + data: { handle }, + deserializeInfo: 'internal/crypto/x509:InternalX509Certificate' + }; + } + + [kDeserialize]({ handle }) { + this[kHandle] = handle; + } + + get subject() { + let value = this[kInternalState].get('subject'); + if (value === undefined) { + value = this[kHandle].subject(); + this[kInternalState].set('subject', value); + } + return value; + } + + get subjectAltName() { + let value = this[kInternalState].get('subjectAltName'); + if (value === undefined) { + value = this[kHandle].subjectAltName(); + this[kInternalState].set('subjectAltName', value); + } + return value; + } + + get issuer() { + let value = this[kInternalState].get('issuer'); + if (value === undefined) { + value = this[kHandle].issuer(); + this[kInternalState].set('issuer', value); + } + return value; + } + + get issuerCertificate() { + let value = this[kInternalState].get('issuerCertificate'); + if (value === undefined) { + const cert = this[kHandle].getIssuerCert(); + if (cert) + value = new InternalX509Certificate(this[kHandle].getIssuerCert()); + this[kInternalState].set('issuerCertificate', value); + } + return value; + } + + get infoAccess() { + let value = this[kInternalState].get('infoAccess'); + if (value === undefined) { + value = this[kHandle].infoAccess(); + this[kInternalState].set('infoAccess', value); + } + return value; + } + + get validFrom() { + let value = this[kInternalState].get('validFrom'); + if (value === undefined) { + value = this[kHandle].validFrom(); + this[kInternalState].set('validFrom', value); + } + return value; + } + + get validTo() { + let value = this[kInternalState].get('validTo'); + if (value === undefined) { + value = this[kHandle].validTo(); + this[kInternalState].set('validTo', value); + } + return value; + } + + get fingerprint() { + let value = this[kInternalState].get('fingerprint'); + if (value === undefined) { + value = this[kHandle].fingerprint(); + this[kInternalState].set('fingerprint', value); + } + return value; + } + + get fingerprint256() { + let value = this[kInternalState].get('fingerprint256'); + if (value === undefined) { + value = this[kHandle].fingerprint256(); + this[kInternalState].set('fingerprint256', value); + } + return value; + } + + get fingerprint512() { + let value = this[kInternalState].get('fingerprint512'); + if (value === undefined) { + value = this[kHandle].fingerprint512(); + this[kInternalState].set('fingerprint512', value); + } + return value; + } + + get keyUsage() { + let value = this[kInternalState].get('keyUsage'); + if (value === undefined) { + value = this[kHandle].keyUsage(); + this[kInternalState].set('keyUsage', value); + } + return value; + } + + get serialNumber() { + let value = this[kInternalState].get('serialNumber'); + if (value === undefined) { + value = this[kHandle].serialNumber(); + this[kInternalState].set('serialNumber', value); + } + return value; + } + + get raw() { + let value = this[kInternalState].get('raw'); + if (value === undefined) { + value = this[kHandle].raw(); + this[kInternalState].set('raw', value); + } + return value; + } + + get publicKey() { + let value = this[kInternalState].get('publicKey'); + if (value === undefined) { + value = new PublicKeyObject(this[kHandle].publicKey()); + this[kInternalState].set('publicKey', value); + } + return value; + } + + toString() { + let value = this[kInternalState].get('pem'); + if (value === undefined) { + value = this[kHandle].pem(); + this[kInternalState].set('pem', value); + } + return value; + } + + // There's no standardized JSON encoding for X509 certs so we + // fallback to providing the PEM encoding as a string. + toJSON() { return this.toString(); } + + get ca() { + let value = this[kInternalState].get('ca'); + if (value === undefined) { + value = this[kHandle].checkCA(); + this[kInternalState].set('ca', value); + } + return value; + } + + checkHost(name, options) { + validateString(name, 'name'); + return this[kHandle].checkHost(name, getFlags(options)); + } + + checkEmail(email, options) { + validateString(email, 'email'); + return this[kHandle].checkEmail(email, getFlags(options)); + } + + checkIP(ip, options) { + validateString(ip, 'ip'); + // The options argument is currently undocumented since none of the options + // have any effect on the behavior of this function. However, we still parse + // the options argument in case OpenSSL adds flags in the future that do + // affect the behavior of X509_check_ip. This ensures that no invalid values + // are passed as the second argument in the meantime. + return this[kHandle].checkIP(ip, getFlags(options)); + } + + checkIssued(otherCert) { + if (!isX509Certificate(otherCert)) + throw new ERR_INVALID_ARG_TYPE('otherCert', 'X509Certificate', otherCert); + return this[kHandle].checkIssued(otherCert[kHandle]); + } + + checkPrivateKey(pkey) { + if (!isKeyObject(pkey)) + throw new ERR_INVALID_ARG_TYPE('pkey', 'KeyObject', pkey); + if (pkey.type !== 'private') + throw new ERR_INVALID_ARG_VALUE('pkey', pkey); + return this[kHandle].checkPrivateKey(pkey[kHandle]); + } + + verify(pkey) { + if (!isKeyObject(pkey)) + throw new ERR_INVALID_ARG_TYPE('pkey', 'KeyObject', pkey); + if (pkey.type !== 'public') + throw new ERR_INVALID_ARG_VALUE('pkey', pkey); + return this[kHandle].verify(pkey[kHandle]); + } + + toLegacyObject() { + // TODO(tniessen): do not depend on translatePeerCertificate here, return + // the correct legacy representation from the binding + lazyTranslatePeerCertificate ??= + require('_tls_common').translatePeerCertificate; + return lazyTranslatePeerCertificate(this[kHandle].toLegacy()); + } +} + +InternalX509Certificate.prototype.constructor = X509Certificate; +ObjectSetPrototypeOf( + InternalX509Certificate.prototype, + X509Certificate.prototype); + +module.exports = { + X509Certificate, + InternalX509Certificate, + isX509Certificate, +}; diff --git a/modules/internal/errors.js b/modules/internal/errors.js index acc4b89..be833cd 100644 --- a/modules/internal/errors.js +++ b/modules/internal/errors.js @@ -816,3 +816,123 @@ export class ERR_FS_INVALID_SYMLINK_TYPE extends Error { this.code = "ERR_FS_INVALID_SYMLINK_TYPE"; } } + +export class ERR_CRYPTO_FIPS_FORCED extends Error { + constructor() { + super( + 'Cannot set FIPS mode, it was forced with --force-fips at startup.', + ); + this.code = "ERR_CRYPTO_FIPS_FORCED"; + } +} + +export class ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH extends RangeError { + constructor() { + super( + 'Input buffers must have the same byte length', + ); + this.code = "ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH"; + } +} + +export class ERR_OPERATION_FAILED extends Error { + constructor(x) { + super( + `Operation failed: ${x}`, + ); + this.code = "ERR_OPERATION_FAILED"; + } +} + +export class ERR_CRYPTO_ENGINE_UNKNOWN extends Error { + constructor(x) { + super( + `Engine "${x}" was not found`, + ); + this.code = "ERR_CRYPTO_ENGINE_UNKNOWN"; + } +} + +export class ERR_CRYPTO_INVALID_DIGEST extends TypeError { + constructor(x) { + super(`Invalid digest: ${x}`); + this.code = "ERR_CRYPTO_INVALID_DIGEST"; + } +} + +export class ERR_CRYPTO_SCRYPT_INVALID_PARAMETER extends Error { + constructor() { + super(`Invalid scrypt parameter`); + this.code = "ERR_CRYPTO_SCRYPT_INVALID_PARAMETER"; + } +} + +export class ERR_CRYPTO_SCRYPT_NOT_SUPPORTED extends Error { + constructor() { + super(`Scrypt algorithm not supported`); + this.code = "ERR_CRYPTO_SCRYPT_NOT_SUPPORTED"; + } +} + +export class ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS extends Error { + constructor(a, b) { + super(`The selected key encoding ${a} ${b}.`); + this.code = "ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS"; + } +} + +export class ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE extends TypeError { + constructor(t, e) { + super(`Invalid key object type ${t}, expected ${e}.`); + this.code = "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE"; + } +} + +export class ERR_CRYPTO_INVALID_JWK extends TypeError { + constructor() { + super(`Invalid JWK data`); + this.code = "ERR_CRYPTO_INVALID_JWK"; + } +} + +export class ERR_ILLEGAL_CONSTRUCTOR extends TypeError { + constructor() { + super(`Illegal constructor`); + this.code = "ERR_ILLEGAL_CONSTRUCTOR"; + } +} + +export class ERR_CRYPTO_INVALID_KEYLEN extends RangeError { + constructor() { + super(`Invalid key length`); + this.code = "ERR_CRYPTO_INVALID_KEYLEN"; + } +} + +export class ERR_CRYPTO_HASH_FINALIZED extends Error { + constructor() { + super(`Digest already called`); + this.code = "ERR_CRYPTO_HASH_FINALIZED"; + } +} + +export class ERR_CRYPTO_HASH_UPDATE_FAILED extends Error { + constructor() { + super(`Hash update failed`); + this.code = "ERR_CRYPTO_HASH_UPDATE_FAILED"; + } +} + +export class ERR_CRYPTO_INVALID_STATE extends Error { + constructor() { + super(`Invalid state`); + this.code = "ERR_CRYPTO_INVALID_STATE"; + } +} + +export class ERR_CRYPTO_UNKNOWN_CIPHER extends Error { + constructor() { + super(`Unknown cipher`); + this.code = "ERR_CRYPTO_UNKNOWN_CIPHER"; + } +} diff --git a/modules/internal/options.js b/modules/internal/options.js index 809e3ed..6543edd 100644 --- a/modules/internal/options.js +++ b/modules/internal/options.js @@ -2,10 +2,14 @@ 'use strict'; -const { - getCLIOptions, - getEmbedderOptions: getEmbedderOptionsFromBinding, -} = internalBinding('options'); +function getCLIOptions() { + let options = new Map(); + let aliases = new Map(); + return { options, aliases }; +} +function getEmbedderOptionsFromBinding() { + return new Map(); +} let warnOnAllowUnauthorized = true; diff --git a/modules/internal/streams/lazy_transform.js b/modules/internal/streams/lazy_transform.js new file mode 100644 index 0000000..8c10a78 --- /dev/null +++ b/modules/internal/streams/lazy_transform.js @@ -0,0 +1,58 @@ +// LazyTransform is a special type of Transform stream that is lazily loaded. +// This is used for performance with bi-API-ship: when two APIs are available +// for the stream, one conventional and one non-conventional. +'use strict'; + +import Transform from './transform'; + +import { + getDefaultEncoding +} from '../crypto/util'; + +export function LazyTransform(options) { + this._options = options; +} +Object.setPrototypeOf(LazyTransform.prototype, Transform.prototype); +Object.setPrototypeOf(LazyTransform, Transform); + +function makeGetter(name) { + return function() { + Transform.call(this, this._options); + this._writableState.decodeStrings = false; + + if (!this._options || !this._options.defaultEncoding) { + this._writableState.defaultEncoding = getDefaultEncoding(); + } + + return this[name]; + }; +} + +function makeSetter(name) { + return function(val) { + Object.defineProperty(this, name, { + __proto__: null, + value: val, + enumerable: true, + configurable: true, + writable: true + }); + }; +} + +Object.defineProperties(LazyTransform.prototype, { + _readableState: { + __proto__: null, + get: makeGetter('_readableState'), + set: makeSetter('_readableState'), + configurable: true, + enumerable: true + }, + _writableState: { + __proto__: null, + get: makeGetter('_writableState'), + set: makeSetter('_writableState'), + configurable: true, + enumerable: true + } +}); diff --git a/modules/internal/util.js b/modules/internal/util.js index d9dc172..40b527e 100644 --- a/modules/internal/util.js +++ b/modules/internal/util.js @@ -160,6 +160,36 @@ export function isError(e) { return e instanceof Error; } +export const kEmptyObject = Object.freeze(Object.create(null)); + +export function lazyDOMException(msg, name) { + let e = new Error(msg) + e.name = name; + return e; +} + +export function filterDuplicateStrings(items, low) { + const map = new SafeMap(); + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const key = String.prototype.toLowerCase.call(item); + if (low) { + map.set(key, key); + } else { + map.set(key, item); + } + } + return Array.prototype.sort.call(Array.from(map.values())); +} + +export function cachedResult(fn) { + let result; + return () => { + if (result === undefined) + result = fn(); + return Array.prototype.slice.call(result); + }; +} export default { createDeferredPromise, @@ -170,5 +200,8 @@ export default { deprecate, promisify, removeColors, - isError + isError, + kEmptyObject, + cachedResult, + filterDuplicateStrings }; \ No newline at end of file diff --git a/modules/internal/validators.js b/modules/internal/validators.js index 33cf96b..35ca8c2 100644 --- a/modules/internal/validators.js +++ b/modules/internal/validators.js @@ -3,7 +3,8 @@ import { ERR_INVALID_ARG_TYPE, ERR_INVALID_CALLBACK, ERR_OUT_OF_RANGE, - hideStackFrames + hideStackFrames, + ERR_INVALID_ARG_VALUE } from './errors' export function validatePort(port, name = "Port", allowZero = true) { @@ -191,6 +192,145 @@ export const getValidMode = hideStackFrames((mode, type) => { ); }); +/** + * @callback validateNumber + * @param {*} value + * @param {string} name + * @param {number} [min] + * @param {number} [max] + * @returns {asserts value is number} + */ + +/** @type {validateNumber} */ +export function validateNumber(value, name, min = undefined, max) { + if (typeof value !== 'number') + throw new ERR_INVALID_ARG_TYPE(name, 'number', value); + + if ((min != null && value < min) || (max != null && value > max) || + ((min != null || max != null) && Number.isNaN(value))) { + throw new ERR_OUT_OF_RANGE( + name, + `${min != null ? `>= ${min}` : ''}${min != null && max != null ? ' && ' : ''}${max != null ? `<= ${max}` : ''}`, + value); + } +} + +/** + * @callback validateArray + * @param {*} value + * @param {string} name + * @param {number} [minLength] + * @returns {asserts value is any[]} + */ + +/** @type {validateArray} */ +export const validateArray = hideStackFrames((value, name, minLength = 0) => { + if (!Array.isArray(value)) { + throw new ERR_INVALID_ARG_TYPE(name, 'Array', value); + } + if (value.length < minLength) { + const reason = `must be longer than ${minLength}`; + throw new ERR_INVALID_ARG_VALUE(name, value, reason); + } +}); + +/** + * @callback validateOneOf + * @template T + * @param {T} value + * @param {string} name + * @param {T[]} oneOf + */ + +/** @type {validateOneOf} */ +export const validateOneOf = hideStackFrames((value, name, oneOf) => { + if (!Array.prototype.includes.call(oneOf, value)) { + const allowed = Array.prototype.join.call( + Array.prototype.map.call(oneOf, (v) => + (typeof v === 'string' ? `'${v}'` : String(v))), + ', '); + const reason = 'must be one of: ' + allowed; + throw new ERR_INVALID_ARG_VALUE(name, value, reason); + } +}); + +// Return undefined if there is no match. +// Move the "slow cases" to a separate function to make sure this function gets +// inlined properly. That prioritizes the common case. +function normalizeEncoding(enc) { + if (enc == null || enc === 'utf8' || enc === 'utf-8') return 'utf8'; + return slowCases(enc); +} + +function slowCases(enc) { + switch (enc.length) { + case 4: + if (enc === 'UTF8') return 'utf8'; + if (enc === 'ucs2' || enc === 'UCS2') return 'utf16le'; + enc = `${enc}`.toLowerCase(); + if (enc === 'utf8') return 'utf8'; + if (enc === 'ucs2') return 'utf16le'; + break; + case 3: + if (enc === 'hex' || enc === 'HEX' || + `${enc}`.toLowerCase() === 'hex') + return 'hex'; + break; + case 5: + if (enc === 'ascii') return 'ascii'; + if (enc === 'ucs-2') return 'utf16le'; + if (enc === 'UTF-8') return 'utf8'; + if (enc === 'ASCII') return 'ascii'; + if (enc === 'UCS-2') return 'utf16le'; + enc = `${enc}`.toLowerCase(); + if (enc === 'utf-8') return 'utf8'; + if (enc === 'ascii') return 'ascii'; + if (enc === 'ucs-2') return 'utf16le'; + break; + case 6: + if (enc === 'base64') return 'base64'; + if (enc === 'latin1' || enc === 'binary') return 'latin1'; + if (enc === 'BASE64') return 'base64'; + if (enc === 'LATIN1' || enc === 'BINARY') return 'latin1'; + enc = `${enc}`.toLowerCase(); + if (enc === 'base64') return 'base64'; + if (enc === 'latin1' || enc === 'binary') return 'latin1'; + break; + case 7: + if (enc === 'utf16le' || enc === 'UTF16LE' || + `${enc}`.toLowerCase() === 'utf16le') + return 'utf16le'; + break; + case 8: + if (enc === 'utf-16le' || enc === 'UTF-16LE' || + `${enc}`.toLowerCase() === 'utf-16le') + return 'utf16le'; + break; + case 9: + if (enc === 'base64url' || enc === 'BASE64URL' || + `${enc}`.toLowerCase() === 'base64url') + return 'base64url'; + break; + default: + if (enc === '') return 'utf8'; + } +} + + +/** + * @param {string} data + * @param {string} encoding + */ +export function validateEncoding(data, encoding) { + const normalizedEncoding = normalizeEncoding(encoding); + const length = data.length; + + if (normalizedEncoding === 'hex' && length % 2 !== 0) { + throw new ERR_INVALID_ARG_VALUE('encoding', encoding, + `is invalid for data of length ${length}`); + } +} + export default { validatePort, validateFunction, @@ -200,5 +340,9 @@ export default { validateAbortSignal, validateCallback, validateInteger, - getValidMode + validateNumber, + validateArray, + getValidMode, + validateOneOf, + validateEncoding } \ No newline at end of file diff --git a/src/internal_module/crypto.rs b/src/internal_module/crypto.rs new file mode 100644 index 0000000..be2e77a --- /dev/null +++ b/src/internal_module/crypto.rs @@ -0,0 +1,597 @@ +use crate::event_loop::wasi_fs::{Errno, Size}; +use crate::quickjs_sys::*; +use crate::EventLoop; +use core::arch; +use crypto_wasi::{ + generate_key_pair, hkdf_hmac, pbkdf2, raw, scrypt, Cipheriv, Decipheriv, Hash, Hmac, + KeyEncodingFormat, PrivateKey, PrivateKeyEncodingType, PublicKey, PublicKeyEncodingType, +}; + +mod wasi_snapshot_preview1 { + #[link(wasm_import_module = "wasi_snapshot_preview1")] + extern "C" { + /// Write high-quality random data into a buffer. + /// This function blocks when the implementation is unable to immediately + /// provide sufficient high-quality random data. + /// This function may execute slowly, so when large mounts of random data are + /// required, it's advisable to use this function to seed a pseudo-random + /// number generator, rather than to provide the random data directly. + pub fn random_get(arg0: i32, arg1: i32) -> i32; + } +} + +/// Write high-quality random data into a buffer. +/// This function blocks when the implementation is unable to immediately +/// provide sufficient high-quality random data. +/// This function may execute slowly, so when large mounts of random data are +/// required, it's advisable to use this function to seed a pseudo-random +/// number generator, rather than to provide the random data directly. +/// +/// ## Parameters +/// +/// * `buf` - The buffer to fill with random data. +unsafe fn random_get(buf: *mut u8, buf_len: Size) -> Result<(), Errno> { + let ret = wasi_snapshot_preview1::random_get(buf as i32, buf_len as i32); + match ret { + 0 => Ok(()), + _ => Err(Errno(ret as u16)), + } +} + +macro_rules! get_arg { + ($argv:ident, $m:path, $i:expr) => { + if let Some($m(val)) = $argv.get($i) { + val + } else { + return JsValue::UnDefined; + } + }; +} + +fn timing_safe_equal(_ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { + let a = get_arg!(argv, JsValue::ArrayBuffer, 0); + let b = get_arg!(argv, JsValue::ArrayBuffer, 1); + let buf1 = a.as_ref(); + let buf2 = b.as_ref(); + let mut eq = true; + for i in 0..buf1.len() { + eq &= buf1[i] == buf2[i]; + } + eq.into() +} + +fn random_fill(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { + let buf = get_arg!(argv, JsValue::ArrayBuffer, 0); + let offset = get_arg!(argv, JsValue::Int, 1); + let size = get_arg!(argv, JsValue::Int, 2); + return match unsafe { + let (ptr, buf_len) = buf.get_mut_ptr(); + random_get( + ptr.offset(*offset as isize), + (buf_len - *offset as usize).min(*size as usize), + ) + } { + Ok(()) => JsValue::UnDefined, + Err(e) => { + let err = super::fs::errno_to_js_object(ctx, e); + JsValue::Exception(ctx.throw_error(err)) + } + }; +} + +pub fn errno_to_js_object(ctx: &mut Context, e: raw::CryptoErrno) -> JsValue { + let mut res = ctx.new_object(); + res.set("message", JsValue::String(ctx.new_string(e.message()))); + res.set("code", JsValue::String(ctx.new_string(e.name()))); + res.set("errno", JsValue::Int(e.raw() as i32)); + JsValue::Object(res) +} + +fn pbkdf2_sync(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { + let password = get_arg!(argv, JsValue::ArrayBuffer, 0); + let salt = get_arg!(argv, JsValue::ArrayBuffer, 1); + let iters = get_arg!(argv, JsValue::Int, 2); + let key_len = get_arg!(argv, JsValue::Int, 3); + let alg = get_arg!(argv, JsValue::String, 4); + match { + pbkdf2( + password.as_ref(), + salt.as_ref(), + *iters as usize, + *key_len as usize, + alg.as_str(), + ) + } { + Ok(res) => ctx.new_array_buffer(res.as_slice()).into(), + Err(e) => { + let err = errno_to_js_object(ctx, e); + JsValue::Exception(ctx.throw_error(err)) + } + } +} + +fn scrypt_sync(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { + let password = get_arg!(argv, JsValue::ArrayBuffer, 0); + let salt = get_arg!(argv, JsValue::ArrayBuffer, 1); + let n = *get_arg!(argv, JsValue::Int, 2); + let r = *get_arg!(argv, JsValue::Int, 3); + let p = *get_arg!(argv, JsValue::Int, 4); + let key_len = *get_arg!(argv, JsValue::Int, 5); + if key_len == 0 { + return ctx.new_array_buffer(&vec![0; 0]).into(); + } + match { + scrypt( + password.as_ref(), + salt.as_ref(), + n as usize, + r as usize, + p as usize, + key_len as usize, + ) + } { + Ok(res) => ctx.new_array_buffer(res.as_slice()).into(), + Err(e) => { + let err = errno_to_js_object(ctx, e); + JsValue::Exception(ctx.throw_error(err)) + } + } +} + +fn hkdf_sync(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { + let key = get_arg!(argv, JsValue::ArrayBuffer, 0); + let salt = get_arg!(argv, JsValue::ArrayBuffer, 1); + let info = get_arg!(argv, JsValue::ArrayBuffer, 2); + let key_len = get_arg!(argv, JsValue::Int, 3); + let alg = get_arg!(argv, JsValue::String, 4); + match { + hkdf_hmac( + alg.as_str(), + key.as_ref(), + salt.as_ref(), + info.as_ref(), + *key_len as usize, + ) + } { + Ok(res) => ctx.new_array_buffer(res.as_slice()).into(), + Err(e) => { + let err = errno_to_js_object(ctx, e); + JsValue::Exception(ctx.throw_error(err)) + } + } +} + +fn gen_keypair(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { + let alg = get_arg!(argv, JsValue::String, 0); + match { generate_key_pair(alg.as_str()) } { + Ok((pk, sk)) => { + let js_pk = JsKeyObjectHandle::PubKey(pk); + let js_sk = JsKeyObjectHandle::PriKey(sk); + let mut arr = ctx.new_array(); + arr.put(0, JsKeyObjectHandle::wrap_obj(ctx, js_pk)); + arr.put(1, JsKeyObjectHandle::wrap_obj(ctx, js_sk)); + JsValue::Array(arr) + } + Err(e) => { + let err = errno_to_js_object(ctx, e); + JsValue::Exception(ctx.throw_error(err)) + } + } +} + +struct JsHash { + handle: Hash, +} + +impl JsHash { + pub fn js_update( + &mut self, + _this: &mut JsObject, + _ctx: &mut Context, + argv: &[JsValue], + ) -> JsValue { + let data = get_arg!(argv, JsValue::ArrayBuffer, 0); + if let Ok(()) = self.handle.update(data.as_ref()) { + JsValue::Bool(true) + } else { + JsValue::Bool(false) + } + } + + pub fn js_digest( + &mut self, + _this: &mut JsObject, + ctx: &mut Context, + _argv: &[JsValue], + ) -> JsValue { + if let Ok(res) = self.handle.digest() { + ctx.new_array_buffer(&res).into() + } else { + JsValue::UnDefined + } + } + + fn copy(&self) -> Result { + self.handle.copy().map(|h| JsHash { handle: h }) + } +} + +impl JsClassDef for JsHash { + type RefType = JsHash; + + const CLASS_NAME: &'static str = "JsHash"; + + const CONSTRUCTOR_ARGC: u8 = 1; + + const FIELDS: &'static [JsClassField] = &[]; + + const METHODS: &'static [JsClassMethod] = &[ + ("update", 1, Self::js_update), + ("digest", 0, Self::js_digest), + ]; + + unsafe fn mut_class_id_ptr() -> &'static mut u32 { + static mut CLASS_ID: u32 = 0; + &mut CLASS_ID + } + + fn constructor_fn(ctx: &mut Context, argv: &[JsValue]) -> Result { + match argv.get(0) { + Some(JsValue::String(alg)) => Hash::create(alg.as_str()) + .or_else(|e| { + let err = errno_to_js_object(ctx, e); + Err(JsValue::Exception(ctx.throw_error(err))) + }) + .map(|h| JsHash { handle: h }), + Some(obj) => JsHash::opaque(obj).ok_or(JsValue::UnDefined).and_then(|h| { + h.copy().or_else(|e| { + let err = errno_to_js_object(ctx, e); + Err(JsValue::Exception(ctx.throw_error(err))) + }) + }), + _ => Err(JsValue::UnDefined), + } + } +} + +struct JsHmac { + handle: Hmac, +} + +impl JsHmac { + pub fn js_update( + &mut self, + _this: &mut JsObject, + _ctx: &mut Context, + argv: &[JsValue], + ) -> JsValue { + let data = get_arg!(argv, JsValue::ArrayBuffer, 0); + if let Ok(()) = self.handle.update(data.as_ref()) { + JsValue::Bool(true) + } else { + JsValue::Bool(false) + } + } + + pub fn js_digest( + &mut self, + _this: &mut JsObject, + ctx: &mut Context, + _argv: &[JsValue], + ) -> JsValue { + if let Ok(res) = self.handle.digest() { + ctx.new_array_buffer(&res).into() + } else { + JsValue::UnDefined + } + } +} + +impl JsClassDef for JsHmac { + type RefType = JsHmac; + + const CLASS_NAME: &'static str = "JsHmac"; + + const CONSTRUCTOR_ARGC: u8 = 2; + + const FIELDS: &'static [JsClassField] = &[]; + + const METHODS: &'static [JsClassMethod] = &[ + ("update", 1, Self::js_update), + ("digest", 0, Self::js_digest), + ]; + + unsafe fn mut_class_id_ptr() -> &'static mut u32 { + static mut CLASS_ID: u32 = 0; + &mut CLASS_ID + } + + fn constructor_fn(ctx: &mut Context, argv: &[JsValue]) -> Result { + match (argv.get(0), argv.get(1)) { + (Some(JsValue::String(alg)), Some(JsValue::ArrayBuffer(key))) => { + Hmac::create(alg.as_str(), key.as_ref()) + .or_else(|e| { + let err = errno_to_js_object(ctx, e); + Err(JsValue::Exception(ctx.throw_error(err))) + }) + .map(|h| JsHmac { handle: h }) + } + _ => Err(JsValue::UnDefined), + } + } +} + +enum JsCipher { + Cipher(Cipheriv), + Decipher(Decipheriv), +} + +impl JsCipher { + pub fn js_update( + &mut self, + _this: &mut JsObject, + ctx: &mut Context, + argv: &[JsValue], + ) -> JsValue { + if let Some(JsValue::ArrayBuffer(buf)) = argv.get(0) { + match self { + JsCipher::Cipher(c) => c.update(buf.as_ref()), + JsCipher::Decipher(d) => d.update(buf.as_ref()), + } + .map_or(JsValue::UnDefined, |()| ctx.new_array_buffer(&[]).into()) + } else { + JsValue::UnDefined + } + } + + pub fn js_set_aad( + &mut self, + _this: &mut JsObject, + _ctx: &mut Context, + argv: &[JsValue], + ) -> JsValue { + if let Some(JsValue::ArrayBuffer(buf)) = argv.get(0) { + match self { + JsCipher::Cipher(c) => c.set_aad(buf.as_ref()), + JsCipher::Decipher(d) => d.set_aad(buf.as_ref()), + } + .map_or(JsValue::UnDefined, |()| JsValue::Bool(true)) + } else { + JsValue::UnDefined + } + } + + pub fn js_set_auth_tag( + &mut self, + _this: &mut JsObject, + _ctx: &mut Context, + argv: &[JsValue], + ) -> JsValue { + if let Some(JsValue::ArrayBuffer(buf)) = argv.get(0) { + match self { + JsCipher::Cipher(_) => JsValue::UnDefined, + JsCipher::Decipher(d) => d + .set_auth_tag(buf.as_ref()) + .map_or(JsValue::UnDefined, |()| JsValue::Bool(true)), + } + } else { + JsValue::UnDefined + } + } + + pub fn js_get_auth_tag( + &mut self, + _this: &mut JsObject, + ctx: &mut Context, + _argv: &[JsValue], + ) -> JsValue { + match self { + JsCipher::Cipher(c) => c + .get_auth_tag() + .map_or(JsValue::UnDefined, |tag| ctx.new_array_buffer(&tag).into()), + JsCipher::Decipher(_) => JsValue::UnDefined, + } + } + + pub fn js_final( + &mut self, + _this: &mut JsObject, + ctx: &mut Context, + _argv: &[JsValue], + ) -> JsValue { + match self { + JsCipher::Cipher(c) => c.fin(), + JsCipher::Decipher(d) => d.fin(), + } + .map_or(JsValue::UnDefined, |res| ctx.new_array_buffer(&res).into()) + } + + pub fn js_set_auto_padding( + &mut self, + _this: &mut JsObject, + _ctx: &mut Context, + _argv: &[JsValue], + ) -> JsValue { + true.into() + } +} + +impl JsClassDef for JsCipher { + type RefType = Self; + + const CLASS_NAME: &'static str = "JsCipher"; + + const CONSTRUCTOR_ARGC: u8 = 5; + + const FIELDS: &'static [JsClassField] = &[]; + + const METHODS: &'static [JsClassMethod] = &[ + ("update", 1, Self::js_update), + ("final", 0, Self::js_final), + ("setAAD", 0, Self::js_set_aad), + ("setAuthTag", 0, Self::js_set_auth_tag), + ("getAuthTag", 0, Self::js_get_auth_tag), + ("setAutoPadding", 0, Self::js_set_auto_padding), + ]; + + unsafe fn mut_class_id_ptr() -> &'static mut u32 { + static mut CLASS_ID: u32 = 0; + &mut CLASS_ID + } + + fn constructor_fn(ctx: &mut Context, argv: &[JsValue]) -> Result { + if let ( + Some(JsValue::String(alg)), + Some(JsValue::ArrayBuffer(key)), + Some(JsValue::ArrayBuffer(iv)), + Some(JsValue::Bool(is_encrypt)), + ) = (argv.get(0), argv.get(1), argv.get(2), argv.get(4)) + { + if *is_encrypt { + Cipheriv::create(alg.as_str(), key.as_ref(), iv.as_ref()) + .or_else(|e| { + let err = errno_to_js_object(ctx, e); + Err(JsValue::Exception(ctx.throw_error(err))) + }) + .map(|c| JsCipher::Cipher(c)) + } else { + Decipheriv::create(alg.as_str(), key.as_ref(), iv.as_ref()) + .or_else(|e| { + let err = errno_to_js_object(ctx, e); + Err(JsValue::Exception(ctx.throw_error(err))) + }) + .map(|c| JsCipher::Decipher(c)) + } + } else { + Err(JsValue::UnDefined) + } + } +} + +enum JsKeyObjectHandle { + PubKey(PublicKey), + PriKey(PrivateKey), +} + +impl JsKeyObjectHandle { + pub fn js_export( + &mut self, + _this: &mut JsObject, + ctx: &mut Context, + argv: &[JsValue], + ) -> JsValue { + let skenc_enums = [ + PrivateKeyEncodingType::Pkcs1, + PrivateKeyEncodingType::Pkcs8, + PrivateKeyEncodingType::Sec1, + ]; + let pkenc_enums = [PublicKeyEncodingType::Pkcs1, PublicKeyEncodingType::Spki]; + let format_enums = [ + KeyEncodingFormat::Der, + KeyEncodingFormat::Pem, + KeyEncodingFormat::Jwk, + ]; + let enc = get_arg!(argv, JsValue::Int, 0); + let format = get_arg!(argv, JsValue::Int, 0); + match self { + JsKeyObjectHandle::PriKey(sk) => { + return match sk.export(skenc_enums[*enc as usize], format_enums[*format as usize]) { + Ok(res) => ctx.new_array_buffer(res.as_slice()).into(), + Err(e) => { + let err = errno_to_js_object(ctx, e); + JsValue::Exception(ctx.throw_error(err)) + } + } + } + JsKeyObjectHandle::PubKey(pk) => { + return match pk.export(pkenc_enums[*enc as usize], format_enums[*format as usize]) { + Ok(res) => ctx.new_array_buffer(res.as_slice()).into(), + Err(e) => { + let err = errno_to_js_object(ctx, e); + JsValue::Exception(ctx.throw_error(err)) + } + } + } + }; + } +} + +impl JsClassDef for JsKeyObjectHandle { + type RefType = Self; + + const CLASS_NAME: &'static str = "JsKeyObjectHandle"; + + const CONSTRUCTOR_ARGC: u8 = 0; + + const FIELDS: &'static [JsClassField] = &[]; + + const METHODS: &'static [JsClassMethod] = &[("export", 2, Self::js_export)]; + + unsafe fn mut_class_id_ptr() -> &'static mut u32 { + static mut CLASS_ID: u32 = 0; + &mut CLASS_ID + } + + // can't construct by user + fn constructor_fn(_ctx: &mut Context, _argv: &[JsValue]) -> Result { + Err(JsValue::UnDefined) + } +} + +struct Crypto; + +impl ModuleInit for Crypto { + fn init_module(ctx: &mut Context, m: &mut JsModuleDef) { + m.add_export( + "timing_safe_equal\0", + ctx.wrap_function("timing_safe_equal", timing_safe_equal) + .into(), + ); + m.add_export( + "random_fill\0", + ctx.wrap_function("random_fill", random_fill).into(), + ); + m.add_export( + "pbkdf2_sync\0", + ctx.wrap_function("pbkdf2_sync", pbkdf2_sync).into(), + ); + m.add_export( + "scrypt_sync\0", + ctx.wrap_function("scrypt_sync", scrypt_sync).into(), + ); + m.add_export( + "hkdf_sync\0", + ctx.wrap_function("hkdf_sync", hkdf_sync).into(), + ); + m.add_export( + "gen_keypair\0", + ctx.wrap_function("gen_keypair", gen_keypair).into(), + ); + m.add_export(JsHash::CLASS_NAME, register_class::(ctx)); + m.add_export(JsHmac::CLASS_NAME, register_class::(ctx)); + m.add_export(JsCipher::CLASS_NAME, register_class::(ctx)); + m.add_export( + JsKeyObjectHandle::CLASS_NAME, + register_class::(ctx), + ); + } +} + +pub fn init_module(ctx: &mut Context) { + ctx.register_module( + "_node:crypto\0", + Crypto, + &[ + "timing_safe_equal\0", + "random_fill\0", + "pbkdf2_sync\0", + "scrypt_sync\0", + "hkdf_sync\0", + "gen_keypair\0", + JsHash::CLASS_NAME, + JsHmac::CLASS_NAME, + JsCipher::CLASS_NAME, + JsKeyObjectHandle::CLASS_NAME, + ], + ) +} diff --git a/src/internal_module/fs.rs b/src/internal_module/fs.rs index ee57a07..9337c7a 100644 --- a/src/internal_module/fs.rs +++ b/src/internal_module/fs.rs @@ -75,7 +75,7 @@ fn err_to_js_object(ctx: &mut Context, e: io::Error) -> JsValue { errno_to_js_object(ctx, wasi_fs::Errno(e.raw_os_error().unwrap() as u16)) } -fn errno_to_js_object(ctx: &mut Context, e: wasi_fs::Errno) -> JsValue { +pub fn errno_to_js_object(ctx: &mut Context, e: wasi_fs::Errno) -> JsValue { let mut res = ctx.new_object(); res.set("message", JsValue::String(ctx.new_string(e.message()))); res.set("code", JsValue::String(ctx.new_string(e.name()))); diff --git a/src/internal_module/mod.rs b/src/internal_module/mod.rs index 9a722f5..ae739a8 100644 --- a/src/internal_module/mod.rs +++ b/src/internal_module/mod.rs @@ -1,4 +1,6 @@ pub mod core; +#[cfg(feature = "nodejs_crypto")] +pub mod crypto; pub mod encoding; pub mod fs; pub mod httpx; diff --git a/src/quickjs_sys/mod.rs b/src/quickjs_sys/mod.rs index cff2634..dabe6fe 100644 --- a/src/quickjs_sys/mod.rs +++ b/src/quickjs_sys/mod.rs @@ -398,6 +398,11 @@ impl Context { super::internal_module::os::init_module(&mut ctx); super::internal_module::fs::init_module(&mut ctx); + #[cfg(feature = "nodejs_crypto")] + { + super::internal_module::crypto::init_module(&mut ctx); + } + ctx } diff --git a/test/common.js b/test/common.js index 80c2966..7664a89 100644 --- a/test/common.js +++ b/test/common.js @@ -37,7 +37,7 @@ const isOSX = process.platform === 'darwin'; const isPi = false; const isMainThread = true; const isDumbTerminal = process.env.TERM === 'dumb'; - +const hasOpenSSL3 = true; const mustCallChecks = []; function runCallChecks() { @@ -283,6 +283,8 @@ export function getArrayBufferViews(buf) { return out; } +export const hasCrypto = true; +export const hasFipsCrypto = true const common = { isDumbTerminal, isFreeBSD, @@ -294,6 +296,9 @@ const common = { isWindows, isAIX, isMainThread, + hasCrypto, + hasOpenSSL3, + hasFipsCrypto, mustCall, mustCallAtLeast, mustNotCall, diff --git a/test/crypto/test-crypto-aes-wrap.js b/test/crypto/test-crypto-aes-wrap.js new file mode 100644 index 0000000..33d1806 --- /dev/null +++ b/test/crypto/test-crypto-aes-wrap.js @@ -0,0 +1,64 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +const test = [ + { + algorithm: 'aes128-wrap', + key: 'b26f309fbe57e9b3bb6ae5ef31d54450', + iv: '3fd838af4093d749', + text: '12345678123456781234567812345678' + }, + { + algorithm: 'id-aes128-wrap-pad', + key: 'b26f309fbe57e9b3bb6ae5ef31d54450', + iv: '3fd838af', + text: '12345678123456781234567812345678123' + }, + { + algorithm: 'aes192-wrap', + key: '40978085d68091f7dfca0d7dfc7a5ee76d2cc7f2f345a304', + iv: '3fd838af4093d749', + text: '12345678123456781234567812345678' + }, + { + algorithm: 'id-aes192-wrap-pad', + key: '40978085d68091f7dfca0d7dfc7a5ee76d2cc7f2f345a304', + iv: '3fd838af', + text: '12345678123456781234567812345678123' + }, + { + algorithm: 'aes256-wrap', + key: '29c9eab5ed5ad44134a1437fe2e673b4d88a5b7c72e68454fea08721392b7323', + iv: '3fd838af4093d749', + text: '12345678123456781234567812345678' + }, + { + algorithm: 'id-aes256-wrap-pad', + key: '29c9eab5ed5ad44134a1437fe2e673b4d88a5b7c72e68454fea08721392b7323', + iv: '3fd838af', + text: '12345678123456781234567812345678123' + }, +]; + +test.forEach((data) => { + const cipher = crypto.createCipheriv( + data.algorithm, + Buffer.from(data.key, 'hex'), + Buffer.from(data.iv, 'hex')); + const ciphertext = cipher.update(data.text, 'utf8'); + + const decipher = crypto.createDecipheriv( + data.algorithm, + Buffer.from(data.key, 'hex'), + Buffer.from(data.iv, 'hex')); + const msg = decipher.update(ciphertext, 'buffer', 'utf8'); + + assert.strictEqual(msg, data.text, `${data.algorithm} test case failed`); +}); diff --git a/test/crypto/test-crypto-async-sign-verify.js b/test/crypto/test-crypto-async-sign-verify.js new file mode 100644 index 0000000..859a95c --- /dev/null +++ b/test/crypto/test-crypto-async-sign-verify.js @@ -0,0 +1,145 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const util = require('util'); +const crypto = require('crypto'); +const fixtures = require('../common/fixtures'); + +function test( + publicFixture, + privateFixture, + algorithm, + deterministic, + options +) { + let publicPem = fixtures.readKey(publicFixture); + let privatePem = fixtures.readKey(privateFixture); + let privateKey = crypto.createPrivateKey(privatePem); + let publicKey = crypto.createPublicKey(publicPem); + const privateDer = { + key: privateKey.export({ format: 'der', type: 'pkcs8' }), + format: 'der', + type: 'pkcs8', + ...options + }; + const publicDer = { + key: publicKey.export({ format: 'der', type: 'spki' }), + format: 'der', + type: 'spki', + ...options + }; + + if (options) { + publicPem = { ...options, key: publicPem }; + privatePem = { ...options, key: privatePem }; + privateKey = { ...options, key: privateKey }; + publicKey = { ...options, key: publicKey }; + } + + const data = Buffer.from('Hello world'); + const expected = crypto.sign(algorithm, data, privateKey); + + for (const key of [privatePem, privateKey, privateDer]) { + crypto.sign(algorithm, data, key, common.mustSucceed((actual) => { + if (deterministic) { + assert.deepStrictEqual(actual, expected); + } + + assert.strictEqual( + crypto.verify(algorithm, data, key, actual), true); + })); + } + + const verifyInputs = [ + publicPem, publicKey, publicDer, privatePem, privateKey, privateDer]; + for (const key of verifyInputs) { + crypto.verify(algorithm, data, key, expected, common.mustSucceed( + (verified) => assert.strictEqual(verified, true))); + + crypto.verify(algorithm, data, key, Buffer.from(''), common.mustSucceed( + (verified) => assert.strictEqual(verified, false))); + } +} + +// RSA w/ default padding +test('rsa_public.pem', 'rsa_private.pem', 'sha256', true); +test('rsa_public.pem', 'rsa_private.pem', 'sha256', true, + { padding: crypto.constants.RSA_PKCS1_PADDING }); + +// RSA w/ PSS_PADDING and default saltLength +test('rsa_public.pem', 'rsa_private.pem', 'sha256', false, + { padding: crypto.constants.RSA_PKCS1_PSS_PADDING }); +test('rsa_public.pem', 'rsa_private.pem', 'sha256', false, + { + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN + }); + +// RSA w/ PSS_PADDING and PSS_SALTLEN_DIGEST +test('rsa_public.pem', 'rsa_private.pem', 'sha256', false, + { + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST + }); + +// ED25519 +test('ed25519_public.pem', 'ed25519_private.pem', undefined, true); +// ED448 +test('ed448_public.pem', 'ed448_private.pem', undefined, true); + +// ECDSA w/ der signature encoding +test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384', + false); +test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384', + false, { dsaEncoding: 'der' }); + +// ECDSA w/ ieee-p1363 signature encoding +test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384', false, + { dsaEncoding: 'ieee-p1363' }); + +// DSA w/ der signature encoding +test('dsa_public.pem', 'dsa_private.pem', 'sha256', + false); +test('dsa_public.pem', 'dsa_private.pem', 'sha256', + false, { dsaEncoding: 'der' }); + +// DSA w/ ieee-p1363 signature encoding +test('dsa_public.pem', 'dsa_private.pem', 'sha256', false, + { dsaEncoding: 'ieee-p1363' }); + +// Test Parallel Execution w/ KeyObject is threadsafe in openssl3 +{ + const publicKey = { + key: crypto.createPublicKey( + fixtures.readKey('ec_p256_public.pem')), + dsaEncoding: 'ieee-p1363', + }; + const privateKey = { + key: crypto.createPrivateKey( + fixtures.readKey('ec_p256_private.pem')), + dsaEncoding: 'ieee-p1363', + }; + + const sign = util.promisify(crypto.sign); + const verify = util.promisify(crypto.verify); + + const data = Buffer.from('hello world'); + + Promise.all([ + sign('sha256', data, privateKey), + sign('sha256', data, privateKey), + sign('sha256', data, privateKey), + ]).then(([signature]) => { + return Promise.all([ + verify('sha256', data, publicKey, signature), + verify('sha256', data, publicKey, signature), + verify('sha256', data, publicKey, signature), + ]).then(common.mustCall()); + }) + .catch(common.mustNotCall()); +} diff --git a/test/crypto/test-crypto-authenticated-stream.js b/test/crypto/test-crypto-authenticated-stream.js new file mode 100644 index 0000000..8235baa --- /dev/null +++ b/test/crypto/test-crypto-authenticated-stream.js @@ -0,0 +1,144 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +// Refs: https://github.com/nodejs/node/issues/31733 +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const fs = require('fs'); +const path = require('path'); +const stream = require('stream'); +const tmpdir = require('../common/tmpdir'); + +class Sink extends stream.Writable { + constructor() { + super(); + this.chunks = []; + } + + _write(chunk, encoding, cb) { + this.chunks.push(chunk); + cb(); + } +} + +function direct(config) { + const { cipher, key, iv, aad, authTagLength, plaintextLength } = config; + const expected = Buffer.alloc(plaintextLength); + + const c = crypto.createCipheriv(cipher, key, iv, { authTagLength }); + c.setAAD(aad, { plaintextLength }); + const ciphertext = Buffer.concat([c.update(expected), c.final()]); + + const d = crypto.createDecipheriv(cipher, key, iv, { authTagLength }); + d.setAAD(aad, { plaintextLength }); + d.setAuthTag(c.getAuthTag()); + const actual = Buffer.concat([d.update(ciphertext), d.final()]); + + assert.deepStrictEqual(expected, actual); +} + +function mstream(config) { + const { cipher, key, iv, aad, authTagLength, plaintextLength } = config; + const expected = Buffer.alloc(plaintextLength); + + const c = crypto.createCipheriv(cipher, key, iv, { authTagLength }); + c.setAAD(aad, { plaintextLength }); + + const plain = new stream.PassThrough(); + const crypt = new Sink(); + const chunks = crypt.chunks; + plain.pipe(c).pipe(crypt); + plain.end(expected); + + crypt.on('close', common.mustCall(() => { + const d = crypto.createDecipheriv(cipher, key, iv, { authTagLength }); + d.setAAD(aad, { plaintextLength }); + d.setAuthTag(c.getAuthTag()); + + const crypt = new stream.PassThrough(); + const plain = new Sink(); + crypt.pipe(d).pipe(plain); + for (const chunk of chunks) crypt.write(chunk); + crypt.end(); + + plain.on('close', common.mustCall(() => { + const actual = Buffer.concat(plain.chunks); + assert.deepStrictEqual(expected, actual); + })); + })); +} + +function fstream(config) { + const count = fstream.count++; + const filename = (name) => path.join(tmpdir.path, `${name}${count}`); + + const { cipher, key, iv, aad, authTagLength, plaintextLength } = config; + const expected = Buffer.alloc(plaintextLength); + fs.writeFileSync(filename('a'), expected); + + const c = crypto.createCipheriv(cipher, key, iv, { authTagLength }); + c.setAAD(aad, { plaintextLength }); + + const plain = fs.createReadStream(filename('a')); + const crypt = fs.createWriteStream(filename('b')); + plain.pipe(c).pipe(crypt); + + // Observation: 'close' comes before 'end' on |c|, which definitely feels + // wrong. Switching to `c.on('end', ...)` doesn't fix the test though. + crypt.on('close', common.mustCall(() => { + // Just to drive home the point that decryption does actually work: + // reading the file synchronously, then decrypting it, works. + { + const ciphertext = fs.readFileSync(filename('b')); + const d = crypto.createDecipheriv(cipher, key, iv, { authTagLength }); + d.setAAD(aad, { plaintextLength }); + d.setAuthTag(c.getAuthTag()); + const actual = Buffer.concat([d.update(ciphertext), d.final()]); + assert.deepStrictEqual(expected, actual); + } + + const d = crypto.createDecipheriv(cipher, key, iv, { authTagLength }); + d.setAAD(aad, { plaintextLength }); + d.setAuthTag(c.getAuthTag()); + + const crypt = fs.createReadStream(filename('b')); + const plain = fs.createWriteStream(filename('c')); + crypt.pipe(d).pipe(plain); + + plain.on('close', common.mustCall(() => { + const actual = fs.readFileSync(filename('c')); + assert.deepStrictEqual(expected, actual); + })); + })); +} +fstream.count = 0; + +function test(config) { + direct(config); + mstream(config); + fstream(config); +} + +tmpdir.refresh(); + +test({ + cipher: 'aes-128-ccm', + aad: Buffer.alloc(1), + iv: Buffer.alloc(8), + key: Buffer.alloc(16), + authTagLength: 16, + plaintextLength: 32768, +}); + +test({ + cipher: 'aes-128-ccm', + aad: Buffer.alloc(1), + iv: Buffer.alloc(8), + key: Buffer.alloc(16), + authTagLength: 16, + plaintextLength: 32769, +}); diff --git a/test/crypto/test-crypto-authenticated.js b/test/crypto/test-crypto-authenticated.js new file mode 100644 index 0000000..14a7b72 --- /dev/null +++ b/test/crypto/test-crypto-authenticated.js @@ -0,0 +1,788 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +// Flags: --no-warnings +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const { inspect } = require('util'); +const fixtures = require('../common/fixtures'); + +crypto.DEFAULT_ENCODING = 'buffer'; + +// +// Test authenticated encryption modes. +// +// !NEVER USE STATIC IVs IN REAL LIFE! +// + +const TEST_CASES = require(fixtures.path('aead-vectors.js')); + +const errMessages = { + auth: / auth/, + state: / state/, + FIPS: /not supported in FIPS mode/, + length: /Invalid initialization vector/, + authTagLength: /Invalid authentication tag length/ +}; + +const ciphers = crypto.getCiphers(); + +const expectedWarnings = common.hasFipsCrypto ? + [] : [ + ['Use Cipheriv for counter mode of aes-192-gcm'], + ['Use Cipheriv for counter mode of aes-192-ccm'], + ['Use Cipheriv for counter mode of aes-192-ccm'], + ['Use Cipheriv for counter mode of aes-128-ccm'], + ['Use Cipheriv for counter mode of aes-128-ccm'], + ['Use Cipheriv for counter mode of aes-128-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-256-ccm'], + ['Use Cipheriv for counter mode of aes-128-ccm'], + ]; + +const expectedDeprecationWarnings = [ + ['crypto.DEFAULT_ENCODING is deprecated.', 'DEP0091'], + ['crypto.createCipher is deprecated.', 'DEP0106'], +]; + +common.expectWarning({ + Warning: expectedWarnings, + DeprecationWarning: expectedDeprecationWarnings +}); + +for (const test of TEST_CASES) { + if (!ciphers.includes(test.algo)) { + common.printSkipMessage(`unsupported ${test.algo} test`); + continue; + } + + if (common.hasFipsCrypto && test.iv.length < 24) { + common.printSkipMessage('IV len < 12 bytes unsupported in FIPS mode'); + continue; + } + + const isCCM = /^aes-(128|192|256)-ccm$/.test(test.algo); + const isOCB = /^aes-(128|192|256)-ocb$/.test(test.algo); + + let options; + if (isCCM || isOCB) + options = { authTagLength: test.tag.length / 2 }; + + const inputEncoding = test.plainIsHex ? 'hex' : 'ascii'; + + let aadOptions; + if (isCCM) { + aadOptions = { + plaintextLength: Buffer.from(test.plain, inputEncoding).length + }; + } + + { + const encrypt = crypto.createCipheriv(test.algo, + Buffer.from(test.key, 'hex'), + Buffer.from(test.iv, 'hex'), + options); + + if (test.aad) + encrypt.setAAD(Buffer.from(test.aad, 'hex'), aadOptions); + + let hex = encrypt.update(test.plain, inputEncoding, 'hex'); + hex += encrypt.final('hex'); + + const auth_tag = encrypt.getAuthTag(); + // Only test basic encryption run if output is marked as tampered. + if (!test.tampered) { + assert.strictEqual(hex, test.ct); + assert.strictEqual(auth_tag.toString('hex'), test.tag); + } + } + + { + if (isCCM && common.hasFipsCrypto) { + assert.throws(() => { + crypto.createDecipheriv(test.algo, + Buffer.from(test.key, 'hex'), + Buffer.from(test.iv, 'hex'), + options); + }, errMessages.FIPS); + } else { + const decrypt = crypto.createDecipheriv(test.algo, + Buffer.from(test.key, 'hex'), + Buffer.from(test.iv, 'hex'), + options); + decrypt.setAuthTag(Buffer.from(test.tag, 'hex')); + if (test.aad) + decrypt.setAAD(Buffer.from(test.aad, 'hex'), aadOptions); + + const outputEncoding = test.plainIsHex ? 'hex' : 'ascii'; + + let msg = decrypt.update(test.ct, 'hex', outputEncoding); + if (!test.tampered) { + msg += decrypt.final(outputEncoding); + assert.strictEqual(msg, test.plain); + } else { + // Assert that final throws if input data could not be verified! + assert.throws(function() { decrypt.final('hex'); }, errMessages.auth); + } + } + } + + if (test.password) { + if (common.hasFipsCrypto) { + assert.throws(() => { crypto.createCipher(test.algo, test.password); }, + errMessages.FIPS); + } else { + const encrypt = crypto.createCipher(test.algo, test.password, options); + if (test.aad) + encrypt.setAAD(Buffer.from(test.aad, 'hex'), aadOptions); + let hex = encrypt.update(test.plain, 'ascii', 'hex'); + hex += encrypt.final('hex'); + const auth_tag = encrypt.getAuthTag(); + // Only test basic encryption run if output is marked as tampered. + if (!test.tampered) { + assert.strictEqual(hex, test.ct); + assert.strictEqual(auth_tag.toString('hex'), test.tag); + } + } + } + + if (test.password) { + if (common.hasFipsCrypto) { + assert.throws(() => { crypto.createDecipher(test.algo, test.password); }, + errMessages.FIPS); + } else { + const decrypt = crypto.createDecipher(test.algo, test.password, options); + decrypt.setAuthTag(Buffer.from(test.tag, 'hex')); + if (test.aad) + decrypt.setAAD(Buffer.from(test.aad, 'hex'), aadOptions); + let msg = decrypt.update(test.ct, 'hex', 'ascii'); + if (!test.tampered) { + msg += decrypt.final('ascii'); + assert.strictEqual(msg, test.plain); + } else { + // Assert that final throws if input data could not be verified! + assert.throws(function() { decrypt.final('ascii'); }, errMessages.auth); + } + } + } + + { + // Trying to get tag before inputting all data: + const encrypt = crypto.createCipheriv(test.algo, + Buffer.from(test.key, 'hex'), + Buffer.from(test.iv, 'hex'), + options); + encrypt.update('blah', 'ascii'); + assert.throws(function() { encrypt.getAuthTag(); }, errMessages.state); + } + + { + // Trying to create cipher with incorrect IV length + assert.throws(function() { + crypto.createCipheriv( + test.algo, + Buffer.from(test.key, 'hex'), + Buffer.alloc(0) + ); + }, errMessages.length); + } +} + +// Non-authenticating mode: +{ + const encrypt = + crypto.createCipheriv('aes-128-cbc', + 'ipxp9a6i1Mb4USb4', + '6fKjEjR3Vl30EUYC'); + encrypt.update('blah', 'ascii'); + encrypt.final(); + assert.throws(() => encrypt.getAuthTag(), errMessages.state); + assert.throws(() => encrypt.setAAD(Buffer.from('123', 'ascii')), + errMessages.state); +} + +// GCM only supports specific authentication tag lengths, invalid lengths should +// throw. +{ + for (const length of [0, 1, 2, 6, 9, 10, 11, 17]) { + assert.throws(() => { + const decrypt = crypto.createDecipheriv('aes-128-gcm', + 'FxLKsqdmv0E9xrQh', + 'qkuZpJWCewa6Szih'); + decrypt.setAuthTag(Buffer.from('1'.repeat(length))); + }, { + name: 'TypeError', + message: /Invalid authentication tag length/ + }); + + assert.throws(() => { + crypto.createCipheriv('aes-256-gcm', + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6Szih', + { + authTagLength: length + }); + }, { + name: 'TypeError', + message: /Invalid authentication tag length/ + }); + + assert.throws(() => { + crypto.createDecipheriv('aes-256-gcm', + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6Szih', + { + authTagLength: length + }); + }, { + name: 'TypeError', + message: /Invalid authentication tag length/ + }); + } +} + +// Test that GCM can produce shorter authentication tags than 16 bytes. +{ + const fullTag = '1debb47b2c91ba2cea16fad021703070'; + for (const [authTagLength, e] of [[undefined, 16], [12, 12], [4, 4]]) { + const cipher = crypto.createCipheriv('aes-256-gcm', + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6Szih', { + authTagLength + }); + cipher.setAAD(Buffer.from('abcd')); + cipher.update('01234567', 'hex'); + cipher.final(); + const tag = cipher.getAuthTag(); + assert.strictEqual(tag.toString('hex'), fullTag.substr(0, 2 * e)); + } +} + +// Test that users can manually restrict the GCM tag length to a single value. +{ + const decipher = crypto.createDecipheriv('aes-256-gcm', + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6Szih', { + authTagLength: 8 + }); + + assert.throws(() => { + // This tag would normally be allowed. + decipher.setAuthTag(Buffer.from('1'.repeat(12))); + }, { + name: 'TypeError', + message: /Invalid authentication tag length/ + }); + + // The Decipher object should be left intact. + decipher.setAuthTag(Buffer.from('445352d3ff85cf94', 'hex')); + const text = Buffer.concat([ + decipher.update('3a2a3647', 'hex'), + decipher.final(), + ]); + assert.strictEqual(text.toString('utf8'), 'node'); +} + +// Test that create(De|C)ipher(iv)? throws if the mode is CCM and an invalid +// authentication tag length has been specified. +{ + for (const authTagLength of [-1, true, false, NaN, 5.5]) { + assert.throws(() => { + crypto.createCipheriv('aes-256-ccm', + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6S', + { + authTagLength + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.authTagLength' is invalid. " + + `Received ${inspect(authTagLength)}` + }); + + assert.throws(() => { + crypto.createDecipheriv('aes-256-ccm', + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6S', + { + authTagLength + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.authTagLength' is invalid. " + + `Received ${inspect(authTagLength)}` + }); + + if (!common.hasFipsCrypto) { + assert.throws(() => { + crypto.createCipher('aes-256-ccm', 'bad password', { authTagLength }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.authTagLength' is invalid. " + + `Received ${inspect(authTagLength)}` + }); + + assert.throws(() => { + crypto.createDecipher('aes-256-ccm', 'bad password', { authTagLength }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.authTagLength' is invalid. " + + `Received ${inspect(authTagLength)}` + }); + } + } + + // The following values will not be caught by the JS layer and thus will not + // use the default error codes. + for (const authTagLength of [0, 1, 2, 3, 5, 7, 9, 11, 13, 15, 17, 18]) { + assert.throws(() => { + crypto.createCipheriv('aes-256-ccm', + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6S', + { + authTagLength + }); + }, errMessages.authTagLength); + + if (!common.hasFipsCrypto) { + assert.throws(() => { + crypto.createDecipheriv('aes-256-ccm', + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6S', + { + authTagLength + }); + }, errMessages.authTagLength); + + assert.throws(() => { + crypto.createCipher('aes-256-ccm', 'bad password', { authTagLength }); + }, errMessages.authTagLength); + + assert.throws(() => { + crypto.createDecipher('aes-256-ccm', 'bad password', { authTagLength }); + }, errMessages.authTagLength); + } + } +} + +// Test that create(De|C)ipher(iv)? throws if the mode is CCM or OCB and no +// authentication tag has been specified. +{ + for (const mode of ['ccm', 'ocb']) { + assert.throws(() => { + crypto.createCipheriv(`aes-256-${mode}`, + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6S'); + }, { + message: `authTagLength required for aes-256-${mode}` + }); + + // CCM decryption and create(De|C)ipher are unsupported in FIPS mode. + if (!common.hasFipsCrypto) { + assert.throws(() => { + crypto.createDecipheriv(`aes-256-${mode}`, + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6S'); + }, { + message: `authTagLength required for aes-256-${mode}` + }); + + assert.throws(() => { + crypto.createCipher(`aes-256-${mode}`, 'very bad password'); + }, { + message: `authTagLength required for aes-256-${mode}` + }); + + assert.throws(() => { + crypto.createDecipher(`aes-256-${mode}`, 'very bad password'); + }, { + message: `authTagLength required for aes-256-${mode}` + }); + } + } +} + +// Test that setAAD throws if an invalid plaintext length has been specified. +{ + const cipher = crypto.createCipheriv('aes-256-ccm', + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6S', + { + authTagLength: 10 + }); + + for (const plaintextLength of [-1, true, false, NaN, 5.5]) { + assert.throws(() => { + cipher.setAAD(Buffer.from('0123456789', 'hex'), { plaintextLength }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.plaintextLength' is invalid. " + + `Received ${inspect(plaintextLength)}` + }); + } +} + +// Test that setAAD and update throw if the plaintext is too long. +{ + for (const ivLength of [13, 12]) { + const maxMessageSize = (1 << (8 * (15 - ivLength))) - 1; + const key = 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8'; + const cipher = () => crypto.createCipheriv('aes-256-ccm', key, + '0'.repeat(ivLength), + { + authTagLength: 10 + }); + + assert.throws(() => { + cipher().setAAD(Buffer.alloc(0), { + plaintextLength: maxMessageSize + 1 + }); + }, /Invalid message length$/); + + const msg = Buffer.alloc(maxMessageSize + 1); + assert.throws(() => { + cipher().update(msg); + }, /Invalid message length/); + + const c = cipher(); + c.setAAD(Buffer.alloc(0), { + plaintextLength: maxMessageSize + }); + c.update(msg.slice(1)); + } +} + +// Test that setAAD throws if the mode is CCM and the plaintext length has not +// been specified. +{ + assert.throws(() => { + const cipher = crypto.createCipheriv('aes-256-ccm', + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6S', + { + authTagLength: 10 + }); + cipher.setAAD(Buffer.from('0123456789', 'hex')); + }, /options\.plaintextLength required for CCM mode with AAD/); + + if (!common.hasFipsCrypto) { + assert.throws(() => { + const cipher = crypto.createDecipheriv('aes-256-ccm', + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6S', + { + authTagLength: 10 + }); + cipher.setAAD(Buffer.from('0123456789', 'hex')); + }, /options\.plaintextLength required for CCM mode with AAD/); + } +} + +// Test that final() throws in CCM mode when no authentication tag is provided. +{ + if (!common.hasFipsCrypto) { + const key = Buffer.from('1ed2233fa2223ef5d7df08546049406c', 'hex'); + const iv = Buffer.from('7305220bca40d4c90e1791e9', 'hex'); + const ct = Buffer.from('8beba09d4d4d861f957d51c0794f4abf8030848e', 'hex'); + const decrypt = crypto.createDecipheriv('aes-128-ccm', key, iv, { + authTagLength: 10 + }); + // Normally, we would do this: + // decrypt.setAuthTag(Buffer.from('0d9bcd142a94caf3d1dd', 'hex')); + assert.throws(() => { + decrypt.setAAD(Buffer.from('63616c76696e', 'hex'), { + plaintextLength: ct.length + }); + decrypt.update(ct); + decrypt.final(); + }, errMessages.state); + } +} + +// Test that setAuthTag does not throw in GCM mode when called after setAAD. +{ + const key = Buffer.from('1ed2233fa2223ef5d7df08546049406c', 'hex'); + const iv = Buffer.from('579d9dfde9cd93d743da1ceaeebb86e4', 'hex'); + const decrypt = crypto.createDecipheriv('aes-128-gcm', key, iv); + decrypt.setAAD(Buffer.from('0123456789', 'hex')); + decrypt.setAuthTag(Buffer.from('1bb9253e250b8069cde97151d7ef32d9', 'hex')); + assert.strictEqual(decrypt.update('807022', 'hex', 'hex'), 'abcdef'); + assert.strictEqual(decrypt.final('hex'), ''); +} + +// Test that an IV length of 11 does not overflow max_message_size_. +{ + const key = 'x'.repeat(16); + const iv = Buffer.from('112233445566778899aabb', 'hex'); + const options = { authTagLength: 8 }; + const encrypt = crypto.createCipheriv('aes-128-ccm', key, iv, options); + encrypt.update('boom'); // Should not throw 'Message exceeds maximum size'. + encrypt.final(); +} + +// Test that the authentication tag can be set at any point before calling +// final() in GCM or OCB mode. +{ + const plain = Buffer.from('Hello world', 'utf8'); + const key = Buffer.from('0123456789abcdef', 'utf8'); + const iv = Buffer.from('0123456789ab', 'utf8'); + + for (const mode of ['gcm', 'ocb']) { + for (const authTagLength of mode === 'gcm' ? [undefined, 8] : [8]) { + const cipher = crypto.createCipheriv(`aes-128-${mode}`, key, iv, { + authTagLength + }); + const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]); + const authTag = cipher.getAuthTag(); + + for (const authTagBeforeUpdate of [true, false]) { + const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, { + authTagLength + }); + if (authTagBeforeUpdate) { + decipher.setAuthTag(authTag); + } + const resultUpdate = decipher.update(ciphertext); + if (!authTagBeforeUpdate) { + decipher.setAuthTag(authTag); + } + const resultFinal = decipher.final(); + const result = Buffer.concat([resultUpdate, resultFinal]); + assert(result.equals(plain)); + } + } + } +} + +// Test that setAuthTag can only be called once. +{ + const plain = Buffer.from('Hello world', 'utf8'); + const key = Buffer.from('0123456789abcdef', 'utf8'); + const iv = Buffer.from('0123456789ab', 'utf8'); + const opts = { authTagLength: 8 }; + + for (const mode of ['gcm', 'ccm', 'ocb']) { + const cipher = crypto.createCipheriv(`aes-128-${mode}`, key, iv, opts); + const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]); + const tag = cipher.getAuthTag(); + + const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, opts); + decipher.setAuthTag(tag); + assert.throws(() => { + decipher.setAuthTag(tag); + }, errMessages.state); + // Decryption should still work. + const plaintext = Buffer.concat([ + decipher.update(ciphertext), + decipher.final(), + ]); + assert(plain.equals(plaintext)); + } +} + + +// Test chacha20-poly1305 rejects invalid IV lengths of 13, 14, 15, and 16 (a +// length of 17 or greater was already rejected). +// - https://www.openssl.org/news/secadv/20190306.txt +{ + // Valid extracted from TEST_CASES, check that it detects IV tampering. + const valid = { + algo: 'chacha20-poly1305', + key: '808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f', + iv: '070000004041424344454647', + plain: '4c616469657320616e642047656e746c656d656e206f662074686520636c6173' + + '73206f66202739393a204966204920636f756c64206f6666657220796f75206f' + + '6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73' + + '637265656e20776f756c642062652069742e', + plainIsHex: true, + aad: '50515253c0c1c2c3c4c5c6c7', + ct: 'd31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5' + + 'a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e06' + + '0b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fa' + + 'b324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d265' + + '86cec64b6116', + tag: '1ae10b594f09e26a7e902ecbd0600691', + tampered: false, + }; + + // Invalid IV lengths should be detected: + // - 12 and below are valid. + // - 13-16 are not detected as invalid by some OpenSSL versions. + check(13); + check(14); + check(15); + check(16); + // - 17 and above were always detected as invalid by OpenSSL. + check(17); + + function check(ivLength) { + const prefix = ivLength - valid.iv.length / 2; + assert.throws(() => crypto.createCipheriv( + valid.algo, + Buffer.from(valid.key, 'hex'), + Buffer.from(H(prefix) + valid.iv, 'hex') + ), errMessages.length, `iv length ${ivLength} was not rejected`); + + function H(length) { return '00'.repeat(length); } + } +} + +{ + // CCM cipher without data should not crash, see https://github.com/nodejs/node/issues/38035. + const algo = 'aes-128-ccm'; + const key = Buffer.alloc(16); + const iv = Buffer.alloc(12); + const opts = { authTagLength: 10 }; + + for (const cipher of [ + crypto.createCipher(algo, 'foo', opts), + crypto.createCipheriv(algo, key, iv, opts), + ]) { + assert.throws(() => { + cipher.final(); + }, common.hasOpenSSL3 ? { + code: 'ERR_OSSL_TAG_NOT_SET' + } : { + message: /Unsupported state/ + }); + } +} + +{ + const key = Buffer.alloc(32); + const iv = Buffer.alloc(12); + + for (const authTagLength of [0, 17]) { + assert.throws(() => { + crypto.createCipheriv('chacha20-poly1305', key, iv, { authTagLength }); + }, { + code: 'ERR_CRYPTO_INVALID_AUTH_TAG', + message: errMessages.authTagLength + }); + } +} + +// ChaCha20-Poly1305 should respect the authTagLength option and should not +// require the authentication tag before calls to update() during decryption. +{ + const key = Buffer.alloc(32); + const iv = Buffer.alloc(12); + + for (let authTagLength = 1; authTagLength <= 16; authTagLength++) { + const cipher = + crypto.createCipheriv('chacha20-poly1305', key, iv, { authTagLength }); + const ciphertext = Buffer.concat([cipher.update('foo'), cipher.final()]); + const authTag = cipher.getAuthTag(); + assert.strictEqual(authTag.length, authTagLength); + + // The decipher operation should reject all authentication tags other than + // that of the expected length. + for (let other = 1; other <= 16; other++) { + const decipher = crypto.createDecipheriv('chacha20-poly1305', key, iv, { + authTagLength: other + }); + // ChaCha20 is a stream cipher so we do not need to call final() to obtain + // the full plaintext. + const plaintext = decipher.update(ciphertext); + assert.strictEqual(plaintext.toString(), 'foo'); + if (other === authTagLength) { + // The authentication tag length is as expected and the tag itself is + // correct, so this should work. + decipher.setAuthTag(authTag); + decipher.final(); + } else { + // The authentication tag that we are going to pass to setAuthTag is + // either too short or too long. If other < authTagLength, the + // authentication tag is still correct, but it should still be rejected + // because its security assurance is lower than expected. + assert.throws(() => { + decipher.setAuthTag(authTag); + }, { + code: 'ERR_CRYPTO_INVALID_AUTH_TAG', + message: `Invalid authentication tag length: ${authTagLength}` + }); + } + } + } +} + +// ChaCha20-Poly1305 should default to an authTagLength of 16. When encrypting, +// this matches the behavior of GCM ciphers. When decrypting, however, it is +// stricter than GCM in that it only allows authentication tags that are exactly +// 16 bytes long, whereas, when no authTagLength was specified, GCM would accept +// shorter tags as long as their length was valid according to NIST SP 800-38D. +// For ChaCha20-Poly1305, we intentionally deviate from that because there are +// no recommended or approved authentication tag lengths below 16 bytes. +{ + const rfcTestCases = TEST_CASES.filter(({ algo, tampered }) => { + return algo === 'chacha20-poly1305' && tampered === false; + }); + assert.strictEqual(rfcTestCases.length, 1); + + const [testCase] = rfcTestCases; + const key = Buffer.from(testCase.key, 'hex'); + const iv = Buffer.from(testCase.iv, 'hex'); + const aad = Buffer.from(testCase.aad, 'hex'); + + for (const opt of [ + undefined, + { authTagLength: undefined }, + { authTagLength: 16 }, + ]) { + const cipher = crypto.createCipheriv('chacha20-poly1305', key, iv, opt); + const ciphertext = Buffer.concat([ + cipher.setAAD(aad).update(testCase.plain, 'hex'), + cipher.final(), + ]); + const authTag = cipher.getAuthTag(); + + assert.strictEqual(ciphertext.toString('hex'), testCase.ct); + assert.strictEqual(authTag.toString('hex'), testCase.tag); + + const decipher = crypto.createDecipheriv('chacha20-poly1305', key, iv, opt); + const plaintext = Buffer.concat([ + decipher.setAAD(aad).update(ciphertext), + decipher.setAuthTag(authTag).final(), + ]); + + assert.strictEqual(plaintext.toString('hex'), testCase.plain); + } +} diff --git a/test/crypto/test-crypto-binary-default.js b/test/crypto/test-crypto-binary-default.js new file mode 100644 index 0000000..55e504e --- /dev/null +++ b/test/crypto/test-crypto-binary-default.js @@ -0,0 +1,723 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +// Flags: --expose-internals + +// This is the same as test/simple/test-crypto, but from before the shift +// to use buffers by default. + + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const fs = require('fs'); +const tls = require('tls'); +const fixtures = require('../common/fixtures'); + +require('internal/crypto/util').setDefaultEncoding('latin1'); + +// Test Certificates +const certPem = fixtures.readKey('rsa_cert.crt'); +const certPfx = fixtures.readKey('rsa_cert.pfx'); +const keyPem = fixtures.readKey('rsa_private.pem'); +const rsaPubPem = fixtures.readKey('rsa_public.pem', 'ascii'); +const rsaKeyPem = fixtures.readKey('rsa_private.pem', 'ascii'); + +// PFX tests +tls.createSecureContext({ pfx: certPfx, passphrase: 'sample' }); + +assert.throws(function() { + tls.createSecureContext({ pfx: certPfx }); +}, /^Error: mac verify failure$/); + +assert.throws(function() { + tls.createSecureContext({ pfx: certPfx, passphrase: 'test' }); +}, /^Error: mac verify failure$/); + +assert.throws(function() { + tls.createSecureContext({ pfx: 'sample', passphrase: 'test' }); +}, /^Error: not enough data$/); + +// Test HMAC +{ + const hmacHash = crypto.createHmac('sha1', 'Node') + .update('some data') + .update('to hmac') + .digest('hex'); + assert.strictEqual(hmacHash, '19fd6e1ba73d9ed2224dd5094a71babe85d9a892'); +} + +// Test HMAC-SHA-* (rfc 4231 Test Cases) +{ + const rfc4231 = [ + { + key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), + data: Buffer.from('4869205468657265', 'hex'), // 'Hi There' + hmac: { + sha224: '896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22', + sha256: + 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c' + + '2e32cff7', + sha384: + 'afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c' + + '7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6', + sha512: + '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b305' + + '45e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f170' + + '2e696c203a126854' + } + }, + { + key: Buffer.from('4a656665', 'hex'), // 'Jefe' + data: Buffer.from('7768617420646f2079612077616e7420666f72206e6f74686' + + '96e673f', 'hex'), // 'what do ya want for nothing?' + hmac: { + sha224: 'a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44', + sha256: + '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b9' + + '64ec3843', + sha384: + 'af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec373' + + '6322445e8e2240ca5e69e2c78b3239ecfab21649', + sha512: + '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7' + + 'ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b' + + '636e070a38bce737' + } + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), + data: Buffer.from('ddddddddddddddddddddddddddddddddddddddddddddddddd' + + 'ddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'hex'), + hmac: { + sha224: '7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea', + sha256: + '773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514' + + 'ced565fe', + sha384: + '88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e5' + + '5966144b2a5ab39dc13814b94e3ab6e101a34f27', + sha512: + 'fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33' + + 'b2279d39bf3e848279a722c806b485a47e67c807b946a337bee89426' + + '74278859e13292fb' + } + }, + { + key: Buffer.from('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'hex'), + data: Buffer.from('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' + + 'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd', + 'hex'), + hmac: { + sha224: '6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a', + sha256: + '82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff4' + + '6729665b', + sha384: + '3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e' + + '1f573b4e6801dd23c4a7d679ccf8a386c674cffb', + sha512: + 'b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050' + + '361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2d' + + 'e2adebeb10a298dd' + } + }, + { + key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'), + // 'Test With Truncation' + data: Buffer.from('546573742057697468205472756e636174696f6e', 'hex'), + hmac: { + sha224: '0e2aea68a90c8d37c988bcdb9fca6fa8', + sha256: 'a3b6167473100ee06e0c796c2955552b', + sha384: '3abf34c3503b2a23a46efc619baef897', + sha512: '415fad6271580a531d4179bc891d87a6' + }, + truncate: true + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaa', 'hex'), + // 'Test Using Larger Than Block-Size Key - Hash Key First' + data: Buffer.from('54657374205573696e67204c6172676572205468616e20426' + + 'c6f636b2d53697a65204b6579202d2048617368204b657920' + + '4669727374', 'hex'), + hmac: { + sha224: '95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e', + sha256: + '60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f' + + '0ee37f54', + sha384: + '4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05' + + '033ac4c60c2ef6ab4030fe8296248df163f44952', + sha512: + '80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b0137' + + '83f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec' + + '8b915a985d786598' + } + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaa', 'hex'), + // 'This is a test using a larger than block-size key and a larger ' + + // 'than block-size data. The key needs to be hashed before being ' + + // 'used by the HMAC algorithm.' + data: Buffer.from('5468697320697320612074657374207573696e672061206c6' + + '172676572207468616e20626c6f636b2d73697a65206b6579' + + '20616e642061206c6172676572207468616e20626c6f636b2' + + 'd73697a6520646174612e20546865206b6579206e65656473' + + '20746f20626520686173686564206265666f7265206265696' + + 'e6720757365642062792074686520484d414320616c676f72' + + '6974686d2e', 'hex'), + hmac: { + sha224: '3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1', + sha256: + '9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f5153' + + '5c3a35e2', + sha384: + '6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82' + + '461e99c5a678cc31e799176d3860e6110c46523e', + sha512: + 'e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d' + + '20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de04460' + + '65c97440fa8c6a58' + } + }, + ]; + + for (const testCase of rfc4231) { + for (const hash in testCase.hmac) { + let result = crypto.createHmac(hash, testCase.key) + .update(testCase.data) + .digest('hex'); + if (testCase.truncate) { + result = result.substr(0, 32); // first 128 bits == 32 hex chars + } + assert.strictEqual( + testCase.hmac[hash], + result + ); + } + } +} + +// Test HMAC-MD5/SHA1 (rfc 2202 Test Cases) +{ + const rfc2202_md5 = [ + { + key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), + data: 'Hi There', + hmac: '9294727a3638bb1c13f48ef8158bfc9d' + }, + { + key: 'Jefe', + data: 'what do ya want for nothing?', + hmac: '750c783e6ab0b503eaa86e310a5db738' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), + data: Buffer.from('ddddddddddddddddddddddddddddddddddddddddddddddddd' + + 'ddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'hex'), + hmac: '56be34521d144c88dbb8c733f0e8b3f6' + }, + { + key: Buffer.from('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'hex'), + data: Buffer.from('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' + + 'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' + + 'cdcdcdcdcd', + 'hex'), + hmac: '697eaf0aca3a3aea3a75164746ffaa79' + }, + { + key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'), + data: 'Test With Truncation', + hmac: '56461ef2342edc00f9bab995690efd4c' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: 'Test Using Larger Than Block-Size Key - Hash Key First', + hmac: '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: + 'Test Using Larger Than Block-Size Key and Larger Than One ' + + 'Block-Size Data', + hmac: '6f630fad67cda0ee1fb1f562db3aa53e' + }, + ]; + const rfc2202_sha1 = [ + { + key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), + data: 'Hi There', + hmac: 'b617318655057264e28bc0b6fb378c8ef146be00' + }, + { + key: 'Jefe', + data: 'what do ya want for nothing?', + hmac: 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), + data: Buffer.from('ddddddddddddddddddddddddddddddddddddddddddddd' + + 'ddddddddddddddddddddddddddddddddddddddddddddd' + + 'dddddddddd', + 'hex'), + hmac: '125d7342b9ac11cd91a39af48aa17b4f63f175d3' + }, + { + key: Buffer.from('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'hex'), + data: Buffer.from('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' + + 'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' + + 'cdcdcdcdcd', + 'hex'), + hmac: '4c9007f4026250c6bc8414f9bf50c86c2d7235da' + }, + { + key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'), + data: 'Test With Truncation', + hmac: '4c1a03424b55e07fe7f27be1d58bb9324a9a5a04' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: 'Test Using Larger Than Block-Size Key - Hash Key First', + hmac: 'aa4ae5e15272d00e95705637ce8a3b55ed402112' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: + 'Test Using Larger Than Block-Size Key and Larger Than One ' + + 'Block-Size Data', + hmac: 'e8e99d0f45237d786d6bbaa7965c7808bbff1a91' + }, + ]; + + if (!common.hasFipsCrypto) { + for (const testCase of rfc2202_md5) { + assert.strictEqual( + testCase.hmac, + crypto.createHmac('md5', testCase.key) + .update(testCase.data) + .digest('hex') + ); + } + } + for (const testCase of rfc2202_sha1) { + assert.strictEqual( + testCase.hmac, + crypto.createHmac('sha1', testCase.key) + .update(testCase.data) + .digest('hex') + ); + } +} + +// Test hashing +{ + const a1 = crypto.createHash('sha1').update('Test123').digest('hex'); + const a2 = crypto.createHash('sha256').update('Test123').digest('base64'); + const a3 = crypto.createHash('sha512').update('Test123').digest(); // binary + const a4 = crypto.createHash('sha1').update('Test123').digest('buffer'); + + if (!common.hasFipsCrypto) { + const a0 = crypto.createHash('md5').update('Test123').digest('latin1'); + assert.strictEqual( + a0, + 'h\u00ea\u00cb\u0097\u00d8o\fF!\u00fa+\u000e\u0017\u00ca\u00bd\u008c' + ); + } + + assert.strictEqual(a1, '8308651804facb7b9af8ffc53a33a22d6a1c8ac2'); + + assert.strictEqual(a2, '2bX1jws4GYKTlxhloUB09Z66PoJZW+y+hq5R8dnx9l4='); + + // Test SHA512 as assumed latin1 + assert.strictEqual( + a3, + '\u00c1(4\u00f1\u0003\u001fd\u0097!O\'\u00d4C/&Qz\u00d4' + + '\u0094\u0015l\u00b8\u008dQ+\u00db\u001d\u00c4\u00b5}\u00b2' + + '\u00d6\u0092\u00a3\u00df\u00a2i\u00a1\u009b\n\n*\u000f' + + '\u00d7\u00d6\u00a2\u00a8\u0085\u00e3<\u0083\u009c\u0093' + + '\u00c2\u0006\u00da0\u00a1\u00879(G\u00ed\'' + ); + + assert.deepStrictEqual( + a4, + Buffer.from('8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'hex') + ); +} + +// Test multiple updates to same hash +{ + const h1 = crypto.createHash('sha1').update('Test123').digest('hex'); + const h2 = crypto.createHash('sha1').update('Test').update('123') + .digest('hex'); + assert.strictEqual(h1, h2); +} + +// Test hashing for binary files +{ + const fn = fixtures.path('sample.png'); + const sha1Hash = crypto.createHash('sha1'); + const fileStream = fs.createReadStream(fn); + fileStream.on('data', function(data) { + sha1Hash.update(data); + }); + fileStream.on('close', common.mustCall(function() { + assert.strictEqual( + sha1Hash.digest('hex'), + '22723e553129a336ad96e10f6aecdf0f45e4149e' + ); + })); +} + +// Unknown digest method should throw an error: +// https://github.com/nodejs/node-v0.x-archive/issues/2227 +assert.throws(function() { + crypto.createHash('xyzzy'); +}, /^Error: Digest method not supported$/); + +// Test signing and verifying +{ + const s1 = crypto.createSign('SHA1') + .update('Test123') + .sign(keyPem, 'base64'); + const s1Verified = crypto.createVerify('SHA1') + .update('Test') + .update('123') + .verify(certPem, s1, 'base64'); + assert.strictEqual(s1Verified, true); + + const s2 = crypto.createSign('SHA256') + .update('Test123') + .sign(keyPem); // binary + const s2Verified = crypto.createVerify('SHA256') + .update('Test') + .update('123') + .verify(certPem, s2); // binary + assert.strictEqual(s2Verified, true); + + const s3 = crypto.createSign('SHA1') + .update('Test123') + .sign(keyPem, 'buffer'); + const s3Verified = crypto.createVerify('SHA1') + .update('Test') + .update('123') + .verify(certPem, s3); + assert.strictEqual(s3Verified, true); +} + + +function testCipher1(key) { + // Test encryption and decryption + const plaintext = 'Keep this a secret? No! Tell everyone about node.js!'; + const cipher = crypto.createCipher('aes192', key); + + // Encrypt plaintext which is in utf8 format + // to a ciphertext which will be in hex + let ciph = cipher.update(plaintext, 'utf8', 'hex'); + // Only use binary or hex, not base64. + ciph += cipher.final('hex'); + + const decipher = crypto.createDecipher('aes192', key); + let txt = decipher.update(ciph, 'hex', 'utf8'); + txt += decipher.final('utf8'); + + assert.strictEqual(txt, plaintext); +} + + +function testCipher2(key) { + // Encryption and decryption with Base64. + // Reported in https://github.com/joyent/node/issues/738 + const plaintext = + '32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBGWWELw' + + 'eCBsThSsfUHLeRe0KCsK8ooHgxie0zOINpXxfZi/oNG7uq9JWFVCk70gfzQH8ZUJ' + + 'jAfaFg**'; + const cipher = crypto.createCipher('aes256', key); + + // Encrypt plaintext which is in utf8 format + // to a ciphertext which will be in Base64 + let ciph = cipher.update(plaintext, 'utf8', 'base64'); + ciph += cipher.final('base64'); + + const decipher = crypto.createDecipher('aes256', key); + let txt = decipher.update(ciph, 'base64', 'utf8'); + txt += decipher.final('utf8'); + + assert.strictEqual(txt, plaintext); +} + + +function testCipher3(key, iv) { + // Test encryption and decryption with explicit key and iv + const plaintext = + '32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBGWWELw' + + 'eCBsThSsfUHLeRe0KCsK8ooHgxie0zOINpXxfZi/oNG7uq9JWFVCk70gfzQH8ZUJ' + + 'jAfaFg**'; + const cipher = crypto.createCipheriv('des-ede3-cbc', key, iv); + let ciph = cipher.update(plaintext, 'utf8', 'hex'); + ciph += cipher.final('hex'); + + const decipher = crypto.createDecipheriv('des-ede3-cbc', key, iv); + let txt = decipher.update(ciph, 'hex', 'utf8'); + txt += decipher.final('utf8'); + + assert.strictEqual(txt, plaintext); +} + + +function testCipher4(key, iv) { + // Test encryption and decryption with explicit key and iv + const plaintext = + '32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBGWWELw' + + 'eCBsThSsfUHLeRe0KCsK8ooHgxie0zOINpXxfZi/oNG7uq9JWFVCk70gfzQH8ZUJ' + + 'jAfaFg**'; + const cipher = crypto.createCipheriv('des-ede3-cbc', key, iv); + let ciph = cipher.update(plaintext, 'utf8', 'buffer'); + ciph = Buffer.concat([ciph, cipher.final('buffer')]); + + const decipher = crypto.createDecipheriv('des-ede3-cbc', key, iv); + let txt = decipher.update(ciph, 'buffer', 'utf8'); + txt += decipher.final('utf8'); + + assert.strictEqual(txt, plaintext); +} + + +function testCipher5(key, iv) { + // Test encryption and decryption with explicit key with aes128-wrap + const plaintext = + '32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBGWWELw' + + 'eCBsThSsfUHLeRe0KCsK8ooHgxie0zOINpXxfZi/oNG7uq9JWFVCk70gfzQH8ZUJ' + + 'jAfaFg**'; + const cipher = crypto.createCipher('id-aes128-wrap', key); + let ciph = cipher.update(plaintext, 'utf8', 'buffer'); + ciph = Buffer.concat([ciph, cipher.final('buffer')]); + + const decipher = crypto.createDecipher('id-aes128-wrap', key); + let txt = decipher.update(ciph, 'buffer', 'utf8'); + txt += decipher.final('utf8'); + + assert.strictEqual(txt, plaintext); +} + +if (!common.hasFipsCrypto) { + testCipher1('MySecretKey123'); + testCipher1(Buffer.from('MySecretKey123')); + + testCipher2('0123456789abcdef'); + testCipher2(Buffer.from('0123456789abcdef')); + + testCipher5(Buffer.from('0123456789abcd0123456789')); +} + +testCipher3('0123456789abcd0123456789', '12345678'); +testCipher3('0123456789abcd0123456789', Buffer.from('12345678')); +testCipher3(Buffer.from('0123456789abcd0123456789'), '12345678'); +testCipher3(Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678')); + +testCipher4(Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678')); + + +// update() should only take buffers / strings +assert.throws( + () => crypto.createHash('sha1').update({ foo: 'bar' }), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + + +// Test Diffie-Hellman with two parties sharing a secret, +// using various encodings as we go along +{ + const size = common.hasFipsCrypto || common.hasOpenSSL3 ? 1024 : 256; + const dh1 = crypto.createDiffieHellman(size); + const p1 = dh1.getPrime('buffer'); + const dh2 = crypto.createDiffieHellman(p1, 'base64'); + const key1 = dh1.generateKeys(); + const key2 = dh2.generateKeys('hex'); + const secret1 = dh1.computeSecret(key2, 'hex', 'base64'); + const secret2 = dh2.computeSecret(key1, 'latin1', 'buffer'); + + assert.strictEqual(secret1, secret2.toString('base64')); + + // Create "another dh1" using generated keys from dh1, + // and compute secret again + const dh3 = crypto.createDiffieHellman(p1, 'buffer'); + const privkey1 = dh1.getPrivateKey(); + dh3.setPublicKey(key1); + dh3.setPrivateKey(privkey1); + + assert.strictEqual(dh1.getPrime(), dh3.getPrime()); + assert.strictEqual(dh1.getGenerator(), dh3.getGenerator()); + assert.strictEqual(dh1.getPublicKey(), dh3.getPublicKey()); + assert.strictEqual(dh1.getPrivateKey(), dh3.getPrivateKey()); + + const secret3 = dh3.computeSecret(key2, 'hex', 'base64'); + + assert.strictEqual(secret1, secret3); + + // https://github.com/joyent/node/issues/2338 + const p = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' + + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' + + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' + + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF'; + crypto.createDiffieHellman(p, 'hex'); + + // Test RSA key signing/verification + const rsaSign = crypto.createSign('SHA1'); + const rsaVerify = crypto.createVerify('SHA1'); + assert.ok(rsaSign instanceof crypto.Sign); + assert.ok(rsaVerify instanceof crypto.Verify); + + rsaSign.update(rsaPubPem); + const rsaSignature = rsaSign.sign(rsaKeyPem, 'hex'); + const expectedSignature = fixtures.readKey( + 'rsa_public_sha1_signature_signedby_rsa_private.sha1', + 'hex' + ); + assert.strictEqual(rsaSignature, expectedSignature); + + rsaVerify.update(rsaPubPem); + assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true); +} + +// +// Test RSA signing and verification +// +{ + const privateKey = fixtures.readKey('rsa_private_b.pem'); + const publicKey = fixtures.readKey('rsa_public_b.pem'); + + const input = 'I AM THE WALRUS'; + + const signature = fixtures.readKey( + 'I_AM_THE_WALRUS_sha256_signature_signedby_rsa_private_b.sha256', + 'hex' + ); + + const sign = crypto.createSign('SHA256'); + sign.update(input); + + const output = sign.sign(privateKey, 'hex'); + assert.strictEqual(output, signature); + + const verify = crypto.createVerify('SHA256'); + verify.update(input); + + assert.strictEqual(verify.verify(publicKey, signature, 'hex'), true); +} + + +// +// Test DSA signing and verification +// +{ + const privateKey = fixtures.readKey('dsa_private.pem'); + const publicKey = fixtures.readKey('dsa_public.pem'); + + const input = 'I AM THE WALRUS'; + + // DSA signatures vary across runs so there is no static string to verify + // against + const sign = crypto.createSign('SHA1'); + sign.update(input); + const signature = sign.sign(privateKey, 'hex'); + + const verify = crypto.createVerify('SHA1'); + verify.update(input); + + assert.strictEqual(verify.verify(publicKey, signature, 'hex'), true); +} + + +// +// Test PBKDF2 with RFC 6070 test vectors (except #4) +// +function testPBKDF2(password, salt, iterations, keylen, expected) { + const actual = crypto.pbkdf2Sync(password, salt, iterations, keylen, + 'sha256'); + assert.strictEqual(actual, expected); + + const cb = common.mustCall((err, actual) => { + assert.strictEqual(actual, expected); + }); + crypto.pbkdf2(password, salt, iterations, keylen, 'sha256', cb); +} + + +testPBKDF2('password', 'salt', 1, 20, + '\x12\x0f\xb6\xcf\xfc\xf8\xb3\x2c\x43\xe7\x22\x52' + + '\x56\xc4\xf8\x37\xa8\x65\x48\xc9'); + +testPBKDF2('password', 'salt', 2, 20, + '\xae\x4d\x0c\x95\xaf\x6b\x46\xd3\x2d\x0a\xdf\xf9' + + '\x28\xf0\x6d\xd0\x2a\x30\x3f\x8e'); + +testPBKDF2('password', 'salt', 4096, 20, + '\xc5\xe4\x78\xd5\x92\x88\xc8\x41\xaa\x53\x0d\xb6' + + '\x84\x5c\x4c\x8d\x96\x28\x93\xa0'); + +testPBKDF2('passwordPASSWORDpassword', + 'saltSALTsaltSALTsaltSALTsaltSALTsalt', + 4096, + 25, + '\x34\x8c\x89\xdb\xcb\xd3\x2b\x2f\x32\xd8\x14\xb8' + + '\x11\x6e\x84\xcf\x2b\x17\x34\x7e\xbc\x18\x00\x18\x1c'); + +testPBKDF2('pass\0word', 'sa\0lt', 4096, 16, + '\x89\xb6\x9d\x05\x16\xf8\x29\x89\x3c\x69\x62\x26' + + '\x65\x0a\x86\x87'); diff --git a/test/crypto/test-crypto-certificate.js b/test/crypto/test-crypto-certificate.js new file mode 100644 index 0000000..6672aad --- /dev/null +++ b/test/crypto/test-crypto-certificate.js @@ -0,0 +1,121 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const { Certificate } = crypto; +const fixtures = require('../common/fixtures'); + +// Test Certificates +const spkacValid = fixtures.readKey('rsa_spkac.spkac'); +const spkacChallenge = 'this-is-a-challenge'; +const spkacFail = fixtures.readKey('rsa_spkac_invalid.spkac'); +const spkacPublicPem = fixtures.readKey('rsa_public.pem'); + +function copyArrayBuffer(buf) { + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); +} + +function checkMethods(certificate) { + + assert.strictEqual(certificate.verifySpkac(spkacValid), true); + assert.strictEqual(certificate.verifySpkac(spkacFail), false); + + assert.strictEqual( + stripLineEndings(certificate.exportPublicKey(spkacValid).toString('utf8')), + stripLineEndings(spkacPublicPem.toString('utf8')) + ); + assert.strictEqual(certificate.exportPublicKey(spkacFail), ''); + + assert.strictEqual( + certificate.exportChallenge(spkacValid).toString('utf8'), + spkacChallenge + ); + assert.strictEqual(certificate.exportChallenge(spkacFail), ''); + + const ab = copyArrayBuffer(spkacValid); + assert.strictEqual(certificate.verifySpkac(ab), true); + assert.strictEqual(certificate.verifySpkac(new Uint8Array(ab)), true); + assert.strictEqual(certificate.verifySpkac(new DataView(ab)), true); +} + +{ + // Test maximum size of input buffer + let buf; + let skip = false; + try { + buf = Buffer.alloc(2 ** 31); + } catch { + // The allocation may fail on some systems. That is expected due + // to architecture and memory constraints. If it does, go ahead + // and skip this test. + skip = true; + } + if (!skip) { + assert.throws( + () => Certificate.verifySpkac(buf), { + code: 'ERR_OUT_OF_RANGE' + }); + assert.throws( + () => Certificate.exportChallenge(buf), { + code: 'ERR_OUT_OF_RANGE' + }); + assert.throws( + () => Certificate.exportPublicKey(buf), { + code: 'ERR_OUT_OF_RANGE' + }); + } +} + +{ + // Test instance methods + checkMethods(new Certificate()); +} + +{ + // Test static methods + checkMethods(Certificate); +} + +function stripLineEndings(obj) { + return obj.replace(/\n/g, ''); +} + +// Direct call Certificate() should return instance +assert(Certificate() instanceof Certificate); + +[1, {}, [], Infinity, true, undefined, null].forEach((val) => { + assert.throws( + () => Certificate.verifySpkac(val), + { code: 'ERR_INVALID_ARG_TYPE' } + ); +}); + +[1, {}, [], Infinity, true, undefined, null].forEach((val) => { + const errObj = { code: 'ERR_INVALID_ARG_TYPE' }; + assert.throws(() => Certificate.exportPublicKey(val), errObj); + assert.throws(() => Certificate.exportChallenge(val), errObj); +}); diff --git a/test/crypto/test-crypto-cipher-decipher.js b/test/crypto/test-crypto-cipher-decipher.js new file mode 100644 index 0000000..2498a63 --- /dev/null +++ b/test/crypto/test-crypto-cipher-decipher.js @@ -0,0 +1,285 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +if (common.hasFipsCrypto) + common.skip('not supported in FIPS mode'); + +const crypto = require('crypto'); +const assert = require('assert'); + +common.expectWarning({ + Warning: [ + ['Use Cipheriv for counter mode of aes-256-gcm'], + ], + DeprecationWarning: [ + ['crypto.createCipher is deprecated.', 'DEP0106'], + ] +}); + +function testCipher1(key) { + // Test encryption and decryption + const plaintext = 'Keep this a secret? No! Tell everyone about node.js!'; + const cipher = crypto.createCipher('aes192', key); + + // Encrypt plaintext which is in utf8 format + // to a ciphertext which will be in hex + let ciph = cipher.update(plaintext, 'utf8', 'hex'); + // Only use binary or hex, not base64. + ciph += cipher.final('hex'); + + const decipher = crypto.createDecipher('aes192', key); + let txt = decipher.update(ciph, 'hex', 'utf8'); + txt += decipher.final('utf8'); + + assert.strictEqual(txt, plaintext); + + // Streaming cipher interface + // NB: In real life, it's not guaranteed that you can get all of it + // in a single read() like this. But in this case, we know it's + // quite small, so there's no harm. + const cStream = crypto.createCipher('aes192', key); + cStream.end(plaintext); + ciph = cStream.read(); + + const dStream = crypto.createDecipher('aes192', key); + dStream.end(ciph); + txt = dStream.read().toString('utf8'); + + assert.strictEqual(txt, plaintext); +} + + +function testCipher2(key) { + // Encryption and decryption with Base64. + // Reported in https://github.com/joyent/node/issues/738 + const plaintext = + '32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBGWWELw' + + 'eCBsThSsfUHLeRe0KCsK8ooHgxie0zOINpXxfZi/oNG7uq9JWFVCk70gfzQH8ZUJ' + + 'jAfaFg**'; + const cipher = crypto.createCipher('aes256', key); + + // Encrypt plaintext which is in utf8 format to a ciphertext which will be in + // Base64. + let ciph = cipher.update(plaintext, 'utf8', 'base64'); + ciph += cipher.final('base64'); + + const decipher = crypto.createDecipher('aes256', key); + let txt = decipher.update(ciph, 'base64', 'utf8'); + txt += decipher.final('utf8'); + + assert.strictEqual(txt, plaintext); +} + +testCipher1('MySecretKey123'); +testCipher1(Buffer.from('MySecretKey123')); + +testCipher2('0123456789abcdef'); +testCipher2(Buffer.from('0123456789abcdef')); + +{ + const Cipher = crypto.Cipher; + const instance = crypto.Cipher('aes-256-cbc', 'secret'); + assert(instance instanceof Cipher, 'Cipher is expected to return a new ' + + 'instance when called without `new`'); + + assert.throws( + () => crypto.createCipher(null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "cipher" argument must be of type string. ' + + 'Received null' + }); + + assert.throws( + () => crypto.createCipher('aes-256-cbc', null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + + assert.throws( + () => crypto.createCipher('aes-256-cbc', 'secret').update(null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + + assert.throws( + () => crypto.createCipher('aes-256-cbc', 'secret').setAAD(null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); +} + +{ + const Decipher = crypto.Decipher; + const instance = crypto.Decipher('aes-256-cbc', 'secret'); + assert(instance instanceof Decipher, 'Decipher is expected to return a new ' + + 'instance when called without `new`'); + + assert.throws( + () => crypto.createDecipher(null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "cipher" argument must be of type string. ' + + 'Received null' + }); + + assert.throws( + () => crypto.createDecipher('aes-256-cbc', 'secret').setAuthTag(null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + + assert.throws( + () => crypto.createDecipher('aes-256-cbc', null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); +} + +// Base64 padding regression test, see +// https://github.com/nodejs/node-v0.x-archive/issues/4837. +{ + const c = crypto.createCipher('aes-256-cbc', 'secret'); + const s = c.update('test', 'utf8', 'base64') + c.final('base64'); + assert.strictEqual(s, '375oxUQCIocvxmC5At+rvA=='); +} + +// Calling Cipher.final() or Decipher.final() twice should error but +// not assert. See https://github.com/nodejs/node-v0.x-archive/issues/4886. +{ + const c = crypto.createCipher('aes-256-cbc', 'secret'); + try { c.final('xxx'); } catch { /* Ignore. */ } + try { c.final('xxx'); } catch { /* Ignore. */ } + try { c.final('xxx'); } catch { /* Ignore. */ } + const d = crypto.createDecipher('aes-256-cbc', 'secret'); + try { d.final('xxx'); } catch { /* Ignore. */ } + try { d.final('xxx'); } catch { /* Ignore. */ } + try { d.final('xxx'); } catch { /* Ignore. */ } +} + +// Regression test for https://github.com/nodejs/node-v0.x-archive/issues/5482: +// string to Cipher#update() should not assert. +{ + const c = crypto.createCipher('aes192', '0123456789abcdef'); + c.update('update'); + c.final(); +} + +// https://github.com/nodejs/node-v0.x-archive/issues/5655 regression tests, +// 'utf-8' and 'utf8' are identical. +{ + let c = crypto.createCipher('aes192', '0123456789abcdef'); + c.update('update', ''); // Defaults to "utf8". + c.final('utf-8'); // Should not throw. + + c = crypto.createCipher('aes192', '0123456789abcdef'); + c.update('update', 'utf8'); + c.final('utf-8'); // Should not throw. + + c = crypto.createCipher('aes192', '0123456789abcdef'); + c.update('update', 'utf-8'); + c.final('utf8'); // Should not throw. +} + +// Regression tests for https://github.com/nodejs/node/issues/8236. +{ + const key = '0123456789abcdef'; + const plaintext = 'Top secret!!!'; + const c = crypto.createCipher('aes192', key); + let ciph = c.update(plaintext, 'utf16le', 'base64'); + ciph += c.final('base64'); + + let decipher = crypto.createDecipher('aes192', key); + + let txt; + txt = decipher.update(ciph, 'base64', 'ucs2'); + txt += decipher.final('ucs2'); + assert.strictEqual(txt, plaintext); + + decipher = crypto.createDecipher('aes192', key); + txt = decipher.update(ciph, 'base64', 'ucs-2'); + txt += decipher.final('ucs-2'); + assert.strictEqual(txt, plaintext); + + decipher = crypto.createDecipher('aes192', key); + txt = decipher.update(ciph, 'base64', 'utf-16le'); + txt += decipher.final('utf-16le'); + assert.strictEqual(txt, plaintext); +} + +// setAutoPadding/setAuthTag/setAAD should return `this` +{ + const key = '0123456789'; + const tagbuf = Buffer.from('auth_tag'); + const aadbuf = Buffer.from('aadbuf'); + const decipher = crypto.createDecipher('aes-256-gcm', key); + assert.strictEqual(decipher.setAutoPadding(), decipher); + assert.strictEqual(decipher.setAuthTag(tagbuf), decipher); + assert.strictEqual(decipher.setAAD(aadbuf), decipher); +} + +// Error throwing in setAAD/setAuthTag/getAuthTag/setAutoPadding +{ + const key = '0123456789'; + const aadbuf = Buffer.from('aadbuf'); + const data = Buffer.from('test-crypto-cipher-decipher'); + + const cipher = crypto.createCipher('aes-256-gcm', key); + cipher.setAAD(aadbuf); + cipher.setAutoPadding(); + + assert.throws( + () => cipher.getAuthTag(), + { + code: 'ERR_CRYPTO_INVALID_STATE', + name: 'Error', + message: 'Invalid state for operation getAuthTag' + } + ); + + const encrypted = Buffer.concat([cipher.update(data), cipher.final()]); + + const decipher = crypto.createDecipher('aes-256-gcm', key); + decipher.setAAD(aadbuf); + decipher.setAuthTag(cipher.getAuthTag()); + decipher.setAutoPadding(); + decipher.update(encrypted); + decipher.final(); + + assert.throws( + () => decipher.setAAD(aadbuf), + { + code: 'ERR_CRYPTO_INVALID_STATE', + name: 'Error', + message: 'Invalid state for operation setAAD' + }); + + assert.throws( + () => decipher.setAuthTag(cipher.getAuthTag()), + { + code: 'ERR_CRYPTO_INVALID_STATE', + name: 'Error', + message: 'Invalid state for operation setAuthTag' + }); + + assert.throws( + () => decipher.setAutoPadding(), + { + code: 'ERR_CRYPTO_INVALID_STATE', + name: 'Error', + message: 'Invalid state for operation setAutoPadding' + } + ); +} diff --git a/test/crypto/test-crypto-cipheriv-decipheriv.js b/test/crypto/test-crypto-cipheriv-decipheriv.js new file mode 100644 index 0000000..6979ff6 --- /dev/null +++ b/test/crypto/test-crypto-cipheriv-decipheriv.js @@ -0,0 +1,251 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +import common from '../common'; +if (!common.hasCrypto) + common.skip('missing crypto'); + +import assert from 'assert'; +import crypto from 'crypto'; + +function testCipher1(key, iv) { + return; // unsupport des-ebe3-cbc + // Test encryption and decryption with explicit key and iv + const plaintext = + '32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBGWWELw' + + 'eCBsThSsfUHLeRe0KCsK8ooHgxie0zOINpXxfZi/oNG7uq9JWFVCk70gfzQH8ZUJ' + + 'jAfaFg**'; + const cipher = crypto.createCipheriv('des-ede3-cbc', key, iv); + let ciph = cipher.update(plaintext, 'utf8', 'hex'); + ciph += cipher.final('hex'); + + const decipher = crypto.createDecipheriv('des-ede3-cbc', key, iv); + let txt = decipher.update(ciph, 'hex', 'utf8'); + txt += decipher.final('utf8'); + + assert.strictEqual(txt, plaintext, + `encryption/decryption with key ${key} and iv ${iv}`); + + // Streaming cipher interface + // NB: In real life, it's not guaranteed that you can get all of it + // in a single read() like this. But in this case, we know it's + // quite small, so there's no harm. + const cStream = crypto.createCipheriv('des-ede3-cbc', key, iv); + cStream.end(plaintext); + ciph = cStream.read(); + + const dStream = crypto.createDecipheriv('des-ede3-cbc', key, iv); + dStream.end(ciph); + txt = dStream.read().toString('utf8'); + + assert.strictEqual(txt, plaintext, + `streaming cipher with key ${key} and iv ${iv}`); +} + + +function testCipher2(key, iv) { + return; // unsupport des-ebe3-cbc + // Test encryption and decryption with explicit key and iv + const plaintext = + '32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBGWWELw' + + 'eCBsThSsfUHLeRe0KCsK8ooHgxie0zOINpXxfZi/oNG7uq9JWFVCk70gfzQH8ZUJ' + + 'jAfaFg**'; + const cipher = crypto.createCipheriv('des-ede3-cbc', key, iv); + let ciph = cipher.update(plaintext, 'utf8', 'buffer'); + ciph = Buffer.concat([ciph, cipher.final('buffer')]); + + const decipher = crypto.createDecipheriv('des-ede3-cbc', key, iv); + let txt = decipher.update(ciph, 'buffer', 'utf8'); + txt += decipher.final('utf8'); + + assert.strictEqual(txt, plaintext, + `encryption/decryption with key ${key} and iv ${iv}`); +} + + +function testCipher3(key, iv) { + return; // unsupport id-aes128-wrap + // Test encryption and decryption with explicit key and iv. + // AES Key Wrap test vector comes from RFC3394 + const plaintext = Buffer.from('00112233445566778899AABBCCDDEEFF', 'hex'); + + const cipher = crypto.createCipheriv('id-aes128-wrap', key, iv); + let ciph = cipher.update(plaintext, 'utf8', 'buffer'); + ciph = Buffer.concat([ciph, cipher.final('buffer')]); + const ciph2 = Buffer.from('1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5', + 'hex'); + assert(ciph.equals(ciph2)); + const decipher = crypto.createDecipheriv('id-aes128-wrap', key, iv); + let deciph = decipher.update(ciph, 'buffer'); + deciph = Buffer.concat([deciph, decipher.final()]); + + assert(deciph.equals(plaintext), + `encryption/decryption with key ${key} and iv ${iv}`); +} + +// copy from testCipher1 but used aes-128-gcm +function testCipher4(key, iv) { + // Test encryption and decryption with explicit key and iv + const plaintext = + '32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBGWWELw' + + 'eCBsThSsfUHLeRe0KCsK8ooHgxie0zOINpXxfZi/oNG7uq9JWFVCk70gfzQH8ZUJ' + + 'jAfaFg**'; + + const cipher = crypto.createCipheriv('aes-128-gcm', key, iv); + let ciph = cipher.update(plaintext, 'utf8', 'hex'); + ciph += cipher.final('hex'); + let tag = cipher.getAuthTag(); + + const decipher = crypto.createDecipheriv('aes-128-gcm', key, iv); + decipher.setAuthTag(tag); + let txt = decipher.update(ciph, 'hex', 'utf8'); + txt += decipher.final('utf8'); + + assert.strictEqual(txt, plaintext, + `encryption/decryption with key ${key} and iv ${iv}`); +} + +{ + const Cipheriv = crypto.Cipheriv; + const key = '1234567890123456'; + const iv = '123456789012'; + + const instance = Cipheriv('aes-128-gcm', key, iv); + assert(instance instanceof Cipheriv, 'Cipheriv is expected to return a new ' + + 'instance when called without `new`'); + + assert.throws( + () => crypto.createCipheriv(null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "cipher" argument must be of type string. ' + + 'Received null' + }); + + assert.throws( + () => crypto.createCipheriv('aes-128-gcm', null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + + assert.throws( + () => crypto.createCipheriv('aes-128-gcm', key, 10), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); +} + +{ + const Decipheriv = crypto.Decipheriv; + const key = '1234567890123456'; + const iv = '123456789012'; + + const instance = Decipheriv('aes-128-gcm', key, iv); + assert(instance instanceof Decipheriv, 'Decipheriv expected to return a new' + + ' instance when called without `new`'); + + assert.throws( + () => crypto.createDecipheriv(null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "cipher" argument must be of type string. ' + + 'Received null' + }); + + assert.throws( + () => crypto.createDecipheriv('aes-128-gcm', null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + + assert.throws( + () => crypto.createDecipheriv('aes-128-gcm', key, 10), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); +} + +testCipher4('0123456789abcdef', '0123456789ab'); +testCipher4('0123456789abcdef', Buffer.from('0123456789ab')); +testCipher4(Buffer.from('0123456789abcdef'), '0123456789ab'); +testCipher4(Buffer.from('0123456789abcdef'), Buffer.from('0123456789ab')); + +/* +testCipher1('0123456789abcd0123456789', '12345678'); +testCipher1('0123456789abcd0123456789', Buffer.from('12345678')); +testCipher1(Buffer.from('0123456789abcd0123456789'), '12345678'); +testCipher1(Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678')); +testCipher2(Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678')); + +if (!common.hasFipsCrypto) { + testCipher3(Buffer.from('000102030405060708090A0B0C0D0E0F', 'hex'), + Buffer.from('A6A6A6A6A6A6A6A6', 'hex')); +} + +// Zero-sized IV or null should be accepted in ECB mode. +crypto.createCipheriv('aes-128-ecb', Buffer.alloc(16), Buffer.alloc(0)); +crypto.createCipheriv('aes-128-ecb', Buffer.alloc(16), null); +*/ +const errMessage = /Invalid initialization vector/; +/* +// But non-empty IVs should be rejected. +for (let n = 1; n < 256; n += 1) { + assert.throws( + () => crypto.createCipheriv('aes-128-ecb', Buffer.alloc(16), + Buffer.alloc(n)), + errMessage); +} + +// Correctly sized IV should be accepted in CBC mode. +crypto.createCipheriv('aes-128-cbc', Buffer.alloc(16), Buffer.alloc(16)); + +// But all other IV lengths should be rejected. +for (let n = 0; n < 256; n += 1) { + if (n === 16) continue; + assert.throws( + () => crypto.createCipheriv('aes-128-cbc', Buffer.alloc(16), + Buffer.alloc(n)), + errMessage); +} + +// And so should null be. +assert.throws(() => { + crypto.createCipheriv('aes-128-cbc', Buffer.alloc(16), null); +}, /Invalid initialization vector/); +*/ +// Zero-sized IV should be rejected in GCM mode. +assert.throws( + () => crypto.createCipheriv('aes-128-gcm', Buffer.alloc(16), + Buffer.alloc(0)), + errMessage); + +// But all other IV lengths should be accepted. +/* Unsupported +const minIvLength = common.hasOpenSSL3 ? 8 : 1; +const maxIvLength = common.hasOpenSSL3 ? 64 : 256; +for (let n = minIvLength; n < maxIvLength; n += 1) { + if (common.hasFipsCrypto && n < 12) continue; + crypto.createCipheriv('aes-128-gcm', Buffer.alloc(16), Buffer.alloc(n)); +}*/ + +{ + // Passing an invalid cipher name should throw. + assert.throws( + () => crypto.createCipheriv('aes-127', Buffer.alloc(16), null), + { + name: 'Error', + code: 'ERR_CRYPTO_UNKNOWN_CIPHER', + message: 'Unknown cipher' + }); + + // Passing a key with an invalid length should throw. + /*assert.throws( + () => crypto.createCipheriv('aes-128-ecb', Buffer.alloc(17), null), + /Invalid key length/);*/ +} diff --git a/test/crypto/test-crypto-classes.js b/test/crypto/test-crypto-classes.js new file mode 100644 index 0000000..d7d9247 --- /dev/null +++ b/test/crypto/test-crypto-classes.js @@ -0,0 +1,34 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +if (!common.hasCrypto) { + common.skip('missing crypto'); + return; +} +const crypto = require('crypto'); + +// 'ClassName' : ['args', 'for', 'constructor'] +const TEST_CASES = { + 'Hash': ['sha1'], + 'Hmac': ['sha1', 'Node'], + 'Cipheriv': ['des-ede3-cbc', '0123456789abcd0123456789', '12345678'], + 'Decipheriv': ['des-ede3-cbc', '0123456789abcd0123456789', '12345678'], + 'Sign': ['RSA-SHA1'], + 'Verify': ['RSA-SHA1'], + 'DiffieHellman': [1024], + 'DiffieHellmanGroup': ['modp5'], + 'ECDH': ['prime256v1'], +}; + +if (!common.hasFipsCrypto) { + TEST_CASES.Cipher = ['aes192', 'secret']; + TEST_CASES.Decipher = ['aes192', 'secret']; + TEST_CASES.DiffieHellman = [common.hasOpenSSL3 ? 1024 : 256]; +} + +for (const [clazz, args] of Object.entries(TEST_CASES)) { + assert(crypto[`create${clazz}`](...args) instanceof crypto[clazz]); +} diff --git a/test/crypto/test-crypto-des3-wrap.js b/test/crypto/test-crypto-des3-wrap.js new file mode 100644 index 0000000..8ffe282 --- /dev/null +++ b/test/crypto/test-crypto-des3-wrap.js @@ -0,0 +1,27 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +// Test case for des-ede3 wrap/unwrap. des3-wrap needs extra 2x blocksize +// then plaintext to store ciphertext. +const test = { + key: Buffer.from('3c08e25be22352910671cfe4ba3652b1220a8a7769b490ba', 'hex'), + iv: Buffer.alloc(0), + plaintext: '32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBG' + + 'WWELweCBsThSsfUHLeRe0KCsK8ooHgxie0zOINpXxfZi/oNG7uq9JWFVCk70gfzQH8ZU' + + 'JjAfaFg**' +}; + +const cipher = crypto.createCipheriv('des3-wrap', test.key, test.iv); +const ciphertext = cipher.update(test.plaintext, 'utf8'); + +const decipher = crypto.createDecipheriv('des3-wrap', test.key, test.iv); +const msg = decipher.update(ciphertext, 'buffer', 'utf8'); + +assert.strictEqual(msg, test.plaintext); diff --git a/test/crypto/test-crypto-dh-constructor.js b/test/crypto/test-crypto-dh-constructor.js new file mode 100644 index 0000000..26db216 --- /dev/null +++ b/test/crypto/test-crypto-dh-constructor.js @@ -0,0 +1,36 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +const size = common.hasFipsCrypto || common.hasOpenSSL3 ? 1024 : 256; +const dh1 = crypto.createDiffieHellman(size); +const p1 = dh1.getPrime('buffer'); + +{ + const DiffieHellman = crypto.DiffieHellman; + + const dh = DiffieHellman(p1, 'buffer'); + assert(dh instanceof DiffieHellman, 'DiffieHellman is expected to return a ' + + 'new instance when called without `new`'); +} + +{ + const DiffieHellmanGroup = crypto.DiffieHellmanGroup; + const dhg = DiffieHellmanGroup('modp5'); + assert(dhg instanceof DiffieHellmanGroup, 'DiffieHellmanGroup is expected ' + + 'to return a new instance when ' + + 'called without `new`'); +} + +{ + const ECDH = crypto.ECDH; + const ecdh = ECDH('prime256v1'); + assert(ecdh instanceof ECDH, 'ECDH is expected to return a new instance ' + + 'when called without `new`'); +} diff --git a/test/crypto/test-crypto-dh-curves.js b/test/crypto/test-crypto-dh-curves.js new file mode 100644 index 0000000..9ba1250 --- /dev/null +++ b/test/crypto/test-crypto-dh-curves.js @@ -0,0 +1,193 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +// Second OAKLEY group, see +// https://github.com/nodejs/node-v0.x-archive/issues/2338 and +// https://xml2rfc.tools.ietf.org/public/rfc/html/rfc2412.html#anchor49 +const p = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' + + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' + + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' + + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF'; +crypto.createDiffieHellman(p, 'hex'); + +// Confirm DH_check() results are exposed for optional examination. +const bad_dh = crypto.createDiffieHellman('02', 'hex'); +assert.notStrictEqual(bad_dh.verifyError, 0); + +const availableCurves = new Set(crypto.getCurves()); +const availableHashes = new Set(crypto.getHashes()); + +// Oakley curves do not clean up ERR stack, it was causing unexpected failure +// when accessing other OpenSSL APIs afterwards. +if (availableCurves.has('Oakley-EC2N-3')) { + crypto.createECDH('Oakley-EC2N-3'); + crypto.createHash('sha256'); +} + +// Test ECDH +if (availableCurves.has('prime256v1') && availableCurves.has('secp256k1')) { + const ecdh1 = crypto.createECDH('prime256v1'); + const ecdh2 = crypto.createECDH('prime256v1'); + const key1 = ecdh1.generateKeys(); + const key2 = ecdh2.generateKeys('hex'); + const secret1 = ecdh1.computeSecret(key2, 'hex', 'base64'); + const secret2 = ecdh2.computeSecret(key1, 'latin1', 'buffer'); + + assert.strictEqual(secret1, secret2.toString('base64')); + + // Point formats + assert.strictEqual(ecdh1.getPublicKey('buffer', 'uncompressed')[0], 4); + let firstByte = ecdh1.getPublicKey('buffer', 'compressed')[0]; + assert(firstByte === 2 || firstByte === 3); + firstByte = ecdh1.getPublicKey('buffer', 'hybrid')[0]; + assert(firstByte === 6 || firstByte === 7); + // Format value should be string + + assert.throws( + () => ecdh1.getPublicKey('buffer', 10), + { + code: 'ERR_CRYPTO_ECDH_INVALID_FORMAT', + name: 'TypeError', + message: 'Invalid ECDH format: 10' + }); + + // ECDH should check that point is on curve + const ecdh3 = crypto.createECDH('secp256k1'); + const key3 = ecdh3.generateKeys(); + + assert.throws( + () => ecdh2.computeSecret(key3, 'latin1', 'buffer'), + { + code: 'ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY', + name: 'Error', + message: 'Public key is not valid for specified curve' + }); + + // ECDH should allow .setPrivateKey()/.setPublicKey() + const ecdh4 = crypto.createECDH('prime256v1'); + + ecdh4.setPrivateKey(ecdh1.getPrivateKey()); + ecdh4.setPublicKey(ecdh1.getPublicKey()); + + assert.throws(() => { + ecdh4.setPublicKey(ecdh3.getPublicKey()); + }, { message: 'Failed to convert Buffer to EC_POINT' }); + + // Verify that we can use ECDH without having to use newly generated keys. + const ecdh5 = crypto.createECDH('secp256k1'); + + // Verify errors are thrown when retrieving keys from an uninitialized object. + assert.throws(() => { + ecdh5.getPublicKey(); + }, /^Error: Failed to get ECDH public key$/); + + assert.throws(() => { + ecdh5.getPrivateKey(); + }, /^Error: Failed to get ECDH private key$/); + + // A valid private key for the secp256k1 curve. + const cafebabeKey = 'cafebabe'.repeat(8); + // Associated compressed and uncompressed public keys (points). + const cafebabePubPtComp = + '03672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3'; + const cafebabePubPtUnComp = + '04672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3' + + '2e02c7f93d13dc2732b760ca377a5897b9dd41a1c1b29dc0442fdce6d0a04d1d'; + ecdh5.setPrivateKey(cafebabeKey, 'hex'); + assert.strictEqual(ecdh5.getPrivateKey('hex'), cafebabeKey); + // Show that the public point (key) is generated while setting the + // private key. + assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp); + + // Compressed and uncompressed public points/keys for other party's + // private key. + // 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF + const peerPubPtComp = + '02c6b754b20826eb925e052ee2c25285b162b51fdca732bcf67e39d647fb6830ae'; + const peerPubPtUnComp = + '04c6b754b20826eb925e052ee2c25285b162b51fdca732bcf67e39d647fb6830ae' + + 'b651944a574a362082a77e3f2b5d9223eb54d7f2f76846522bf75f3bedb8178e'; + + const sharedSecret = + '1da220b5329bbe8bfd19ceef5a5898593f411a6f12ea40f2a8eead9a5cf59970'; + + assert.strictEqual(ecdh5.computeSecret(peerPubPtComp, 'hex', 'hex'), + sharedSecret); + assert.strictEqual(ecdh5.computeSecret(peerPubPtUnComp, 'hex', 'hex'), + sharedSecret); + + // Verify that we still have the same key pair as before the computation. + assert.strictEqual(ecdh5.getPrivateKey('hex'), cafebabeKey); + assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp); + + // Verify setting and getting compressed and non-compressed serializations. + ecdh5.setPublicKey(cafebabePubPtComp, 'hex'); + assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp); + assert.strictEqual( + ecdh5.getPublicKey('hex', 'compressed'), + cafebabePubPtComp + ); + ecdh5.setPublicKey(cafebabePubPtUnComp, 'hex'); + assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp); + assert.strictEqual( + ecdh5.getPublicKey('hex', 'compressed'), + cafebabePubPtComp + ); + + // Show why allowing the public key to be set on this type + // does not make sense. + ecdh5.setPublicKey(peerPubPtComp, 'hex'); + assert.strictEqual(ecdh5.getPublicKey('hex'), peerPubPtUnComp); + assert.throws(() => { + // Error because the public key does not match the private key anymore. + ecdh5.computeSecret(peerPubPtComp, 'hex', 'hex'); + }, /Invalid key pair/); + + // Set to a valid key to show that later attempts to set an invalid key are + // rejected. + ecdh5.setPrivateKey(cafebabeKey, 'hex'); + + // Some invalid private keys for the secp256k1 curve. + const errMessage = /Private key is not valid for specified curve/; + ['0000000000000000000000000000000000000000000000000000000000000000', + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', + ].forEach((element) => { + assert.throws(() => { + ecdh5.setPrivateKey(element, 'hex'); + }, errMessage); + // Verify object state did not change. + assert.strictEqual(ecdh5.getPrivateKey('hex'), cafebabeKey); + }); +} + +// Use of invalid keys was not cleaning up ERR stack, and was causing +// unexpected failure in subsequent signing operations. +if (availableCurves.has('prime256v1') && availableHashes.has('sha256')) { + const curve = crypto.createECDH('prime256v1'); + const invalidKey = Buffer.alloc(65); + invalidKey.fill('\0'); + curve.generateKeys(); + assert.throws( + () => curve.computeSecret(invalidKey), + { + code: 'ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY', + name: 'Error', + message: 'Public key is not valid for specified curve' + }); + // Check that signing operations are not impacted by the above error. + const ecPrivateKey = + '-----BEGIN EC PRIVATE KEY-----\n' + + 'MHcCAQEEIF+jnWY1D5kbVYDNvxxo/Y+ku2uJPDwS0r/VuPZQrjjVoAoGCCqGSM49\n' + + 'AwEHoUQDQgAEurOxfSxmqIRYzJVagdZfMMSjRNNhB8i3mXyIMq704m2m52FdfKZ2\n' + + 'pQhByd5eyj3lgZ7m7jbchtdgyOF8Io/1ng==\n' + + '-----END EC PRIVATE KEY-----'; + crypto.createSign('SHA256').sign(ecPrivateKey); +} diff --git a/test/crypto/test-crypto-dh-leak.js b/test/crypto/test-crypto-dh-leak.js new file mode 100644 index 0000000..895284f --- /dev/null +++ b/test/crypto/test-crypto-dh-leak.js @@ -0,0 +1,31 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// Flags: --expose-gc --noconcurrent_recompilation +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +if (process.config.variables.asan) + common.skip('ASAN messes with memory measurements'); + +const assert = require('assert'); +const crypto = require('crypto'); + +const before = process.memoryUsage.rss(); +{ + const size = common.hasFipsCrypto || common.hasOpenSSL3 ? 1024 : 256; + const dh = crypto.createDiffieHellman(size); + const publicKey = dh.generateKeys(); + const privateKey = dh.getPrivateKey(); + for (let i = 0; i < 5e4; i += 1) { + dh.setPublicKey(publicKey); + dh.setPrivateKey(privateKey); + } +} +global.gc(); +const after = process.memoryUsage.rss(); + +// RSS should stay the same, ceteris paribus, but allow for +// some slop because V8 mallocs memory during execution. +assert(after - before < 10 << 20, `before=${before} after=${after}`); diff --git a/test/crypto/test-crypto-dh-modp2-views.js b/test/crypto/test-crypto-dh-modp2-views.js new file mode 100644 index 0000000..2d330bb --- /dev/null +++ b/test/crypto/test-crypto-dh-modp2-views.js @@ -0,0 +1,26 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const { modp2buf } = require('../common/crypto'); + +const modp2 = crypto.createDiffieHellmanGroup('modp2'); + +const views = common.getArrayBufferViews(modp2buf); +for (const buf of [modp2buf, ...views]) { + // Ensure specific generator (string with encoding) works as expected with + // any ArrayBufferViews as the first argument to createDiffieHellman(). + const exmodp2 = crypto.createDiffieHellman(buf, '02', 'hex'); + modp2.generateKeys(); + exmodp2.generateKeys(); + const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey()) + .toString('hex'); + const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey()) + .toString('hex'); + assert.strictEqual(modp2Secret, exmodp2Secret); +} diff --git a/test/crypto/test-crypto-dh-modp2.js b/test/crypto/test-crypto-dh-modp2.js new file mode 100644 index 0000000..89dd76f --- /dev/null +++ b/test/crypto/test-crypto-dh-modp2.js @@ -0,0 +1,45 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const { modp2buf } = require('../common/crypto'); +const modp2 = crypto.createDiffieHellmanGroup('modp2'); + +{ + // Ensure specific generator (buffer) works as expected. + const exmodp2 = crypto.createDiffieHellman(modp2buf, Buffer.from([2])); + modp2.generateKeys(); + exmodp2.generateKeys(); + const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey()) + .toString('hex'); + const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey()) + .toString('hex'); + assert.strictEqual(modp2Secret, exmodp2Secret); +} + +{ + // Ensure specific generator (string without encoding) works as expected. + const exmodp2 = crypto.createDiffieHellman(modp2buf, '\x02'); + exmodp2.generateKeys(); + const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey()) + .toString('hex'); + const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey()) + .toString('hex'); + assert.strictEqual(modp2Secret, exmodp2Secret); +} + +{ + // Ensure specific generator (numeric) works as expected. + const exmodp2 = crypto.createDiffieHellman(modp2buf, 2); + exmodp2.generateKeys(); + const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey()) + .toString('hex'); + const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey()) + .toString('hex'); + assert.strictEqual(modp2Secret, exmodp2Secret); +} diff --git a/test/crypto/test-crypto-dh-odd-key.js b/test/crypto/test-crypto-dh-odd-key.js new file mode 100644 index 0000000..895a4f5 --- /dev/null +++ b/test/crypto/test-crypto-dh-odd-key.js @@ -0,0 +1,43 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +function test() { + const odd = Buffer.alloc(39, 'A'); + + const c = crypto.createDiffieHellman(common.hasOpenSSL3 ? 1024 : 32); + c.setPrivateKey(odd); + c.generateKeys(); +} + +// FIPS requires a length of at least 1024 +if (!common.hasFipsCrypto) { + test(); +} else { + assert.throws(function() { test(); }, /key size too small/); +} diff --git a/test/crypto/test-crypto-dh-padding.js b/test/crypto/test-crypto-dh-padding.js new file mode 100644 index 0000000..2c21e47 --- /dev/null +++ b/test/crypto/test-crypto-dh-padding.js @@ -0,0 +1,109 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('node compiled without OpenSSL.'); + +const assert = require('assert'); +const crypto = require('crypto'); + +// This test verifies padding with leading zeroes for shared +// secrets that are strictly smaller than the modulus (prime). +// See: +// RFC 4346: https://www.ietf.org/rfc/rfc4346.txt +// https://github.com/nodejs/node-v0.x-archive/issues/7906 +// https://github.com/nodejs/node-v0.x-archive/issues/5239 +// +// In FIPS mode OPENSSL_DH_FIPS_MIN_MODULUS_BITS = 1024, meaning we need +// a FIPS-friendly >= 1024 bit prime, we can use MODP 14 from RFC 3526: +// https://www.ietf.org/rfc/rfc3526.txt +// +// We can generate appropriate values with this code: +// +// crypto = require('crypto'); +// +// for (;;) { +// var a = crypto.getDiffieHellman('modp14'), +// var b = crypto.getDiffieHellman('modp14'); +// +// a.generateKeys(); +// b.generateKeys(); +// +// var aSecret = a.computeSecret(b.getPublicKey()).toString('hex'); +// console.log("A public: " + a.getPublicKey().toString('hex')); +// console.log("A private: " + a.getPrivateKey().toString('hex')); +// console.log("B public: " + b.getPublicKey().toString('hex')); +// console.log("B private: " + b.getPrivateKey().toString('hex')); +// console.log("A secret: " + aSecret); +// console.log('-------------------------------------------------'); +// if(aSecret.substring(0,2) === "00") { +// console.log("found short key!"); +// return; +// } +// } + +const apub = +'5484455905d3eff34c70980e871f27f05448e66f5a6efbb97cbcba4e927196c2bd9ea272cded91\ +10a4977afa8d9b16c9139a444ed2d954a794650e5d7cb525204f385e1af81530518563822ecd0f9\ +524a958d02b3c269e79d6d69850f0968ad567a4404fbb0b19efc8bc73e267b6136b88cafb33299f\ +f7c7cace3ffab1a88c2c9ee841f88b4c3679b4efc465f5c93cca11d487be57373e4c5926f634c4e\ +efee6721d01db91cd66321615b2522f96368dbc818875d422140d0edf30bdb97d9721feddcb9ff6\ +453741a4f687ee46fc54bf1198801f1210ac789879a5ee123f79e2d2ce1209df2445d32166bc9e4\ +8f89e944ec9c3b2e16c8066cd8eebd4e33eb941'; +const bpub = +'3fca64510e36bc7da8a3a901c7b74c2eabfa25deaf7cbe1d0c50235866136ad677317279e1fb0\ +06e9c0a07f63e14a3363c8e016fbbde2b2c7e79fed1cc3e08e95f7459f547a8cd0523ee9dc744d\ +e5a956d92b937db4448917e1f6829437f05e408ee7aea70c0362b37370c7c75d14449d8b2d2133\ +04ac972302d349975e2265ca7103cfebd019d9e91234d638611abd049014f7abf706c1c5da6c88\ +788a1fdc6cdf17f5fffaf024ce8711a2ebde0b52e9f1cb56224483826d6e5ac6ecfaae07b75d20\ +6e8ac97f5be1a5b68f20382f2a7dac189cf169325c4cf845b26a0cd616c31fec905c5d9035e5f7\ +8e9880c812374ac0f3ca3d365f06e4be526b5affd4b79'; +const apriv = +'62411e34704637d99c6c958a7db32ac22fcafafbe1c33d2cfdb76e12ded41f38fc16b792b9041\ +2e4c82755a3815ba52f780f0ee296ad46e348fc4d1dcd6b64f4eea1b231b2b7d95c5b1c2e26d34\ +83520558b9860a6eb668f01422a54e6604aa7702b4e67511397ef3ecb912bff1a83899c5a5bfb2\ +0ee29249a91b8a698e62486f7009a0e9eaebda69d77ecfa2ca6ba2db6c8aa81759c8c90c675979\ +08c3b3e6fc60668f7be81cce6784482af228dd7f489005253a165e292802cfd0399924f6c56827\ +7012f68255207722355634290acc7fddeefbba75650a85ece95b6a12de67eac016ba78960108dd\ +5dbadfaa43cc9fed515a1f307b7d90ae0623bc7b8cefb'; +const secret = +'00c37b1e06a436d6717816a40e6d72907a6f255638b93032267dcb9a5f0b4a9aa0236f3dce63b\ +1c418c60978a00acd1617dfeecf1661d8a3fafb4d0d8824386750f4853313400e7e4afd22847e4\ +fa56bc9713872021265111906673b38db83d10cbfa1dea3b6b4c97c8655f4ae82125281af7f234\ +8916a15c6f95649367d169d587697480df4d10b381479e86d5518b520d9d8fb764084eab518224\ +dc8fe984ddaf532fc1531ce43155fa0ab32532bf1ece5356b8a3447b5267798a904f16f3f4e635\ +597adc0179d011132dcffc0bbcb0dd2c8700872f8663ec7ddd897c659cc2efebccc73f38f0ec96\ +8612314311231f905f91c63a1aea52e0b60cead8b57df'; + +/* FIPS-friendly 2048 bit prime */ +const p = crypto.createDiffieHellman( + crypto.getDiffieHellman('modp14').getPrime()); + +p.setPublicKey(apub, 'hex'); +p.setPrivateKey(apriv, 'hex'); + +assert.strictEqual( + p.computeSecret(bpub, 'hex', 'hex').toString('hex'), + secret +); diff --git a/test/crypto/test-crypto-dh-shared.js b/test/crypto/test-crypto-dh-shared.js new file mode 100644 index 0000000..ea30da5 --- /dev/null +++ b/test/crypto/test-crypto-dh-shared.js @@ -0,0 +1,17 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +const alice = crypto.createDiffieHellmanGroup('modp5'); +const bob = crypto.createDiffieHellmanGroup('modp5'); +alice.generateKeys(); +bob.generateKeys(); +const aSecret = alice.computeSecret(bob.getPublicKey()).toString('hex'); +const bSecret = bob.computeSecret(alice.getPublicKey()).toString('hex'); +assert.strictEqual(aSecret, bSecret); diff --git a/test/crypto/test-crypto-dh-stateless.js b/test/crypto/test-crypto-dh-stateless.js new file mode 100644 index 0000000..4028d29 --- /dev/null +++ b/test/crypto/test-crypto-dh-stateless.js @@ -0,0 +1,248 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +assert.throws(() => crypto.diffieHellman(), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options" argument must be of type object. Received undefined' +}); + +assert.throws(() => crypto.diffieHellman(null), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options" argument must be of type object. Received null' +}); + +assert.throws(() => crypto.diffieHellman([]), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: + 'The "options" argument must be of type object. ' + + 'Received an instance of Array', +}); + +function test({ publicKey: alicePublicKey, privateKey: alicePrivateKey }, + { publicKey: bobPublicKey, privateKey: bobPrivateKey }, + expectedValue) { + const buf1 = crypto.diffieHellman({ + privateKey: alicePrivateKey, + publicKey: bobPublicKey + }); + const buf2 = crypto.diffieHellman({ + privateKey: bobPrivateKey, + publicKey: alicePublicKey + }); + assert.deepStrictEqual(buf1, buf2); + + if (expectedValue !== undefined) + assert.deepStrictEqual(buf1, expectedValue); +} + +const alicePrivateKey = crypto.createPrivateKey({ + key: '-----BEGIN PRIVATE KEY-----\n' + + 'MIIBoQIBADCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKL\n' + + 'gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt\n' + + 'bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR\n' + + '7ORbPcIAfLihY78FmNpINhxV05ppFj+o/STPX4NlXSPco62WHGLzViCFUrue1SkH\n' + + 'cJaWbWcMNU5KvJgE8XRsCMojcyf//////////wIBAgSBwwKBwEh82IAVnYNf0Kjb\n' + + 'qYSImDFyg9sH6CJ0GzRK05e6hM3dOSClFYi4kbA7Pr7zyfdn2SH6wSlNS14Jyrtt\n' + + 'HePrRSeYl1T+tk0AfrvaLmyM56F+9B3jwt/nzqr5YxmfVdXb2aQV53VS/mm3pB2H\n' + + 'iIt9FmvFaaOVe2DupqSr6xzbf/zyON+WF5B5HNVOWXswgpgdUsCyygs98hKy/Xje\n' + + 'TGzJUoWInW39t0YgMXenJrkS0m6wol8Rhxx81AGgELNV7EHZqg==\n' + + '-----END PRIVATE KEY-----', + format: 'pem' +}); +const alicePublicKey = crypto.createPublicKey({ + key: '-----BEGIN PUBLIC KEY-----\n' + + 'MIIBnzCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKLgNwc\n' + + '0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHC\n' + + 'ReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7ORb\n' + + 'PcIAfLihY78FmNpINhxV05ppFj+o/STPX4NlXSPco62WHGLzViCFUrue1SkHcJaW\n' + + 'bWcMNU5KvJgE8XRsCMojcyf//////////wIBAgOBxAACgcBR7+iL5qx7aOb9K+aZ\n' + + 'y2oLt7ST33sDKT+nxpag6cWDDWzPBKFDCJ8fr0v7yW453px8N4qi4R7SYYxFBaYN\n' + + 'Y3JvgDg1ct2JC9sxSuUOLqSFn3hpmAjW7cS0kExIVGfdLlYtIqbhhuo45cTEbVIM\n' + + 'rDEz8mjIlnvbWpKB9+uYmbjfVoc3leFvUBqfG2In2m23Md1swsPxr3n7g68H66JX\n' + + 'iBJKZLQMqNdbY14G9rdKmhhTJrQjC+i7Q/wI8JPhOFzHIGA=\n' + + '-----END PUBLIC KEY-----', + format: 'pem' +}); + +const bobPrivateKey = crypto.createPrivateKey({ + key: '-----BEGIN PRIVATE KEY-----\n' + + 'MIIBoQIBADCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKL\n' + + 'gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt\n' + + 'bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR\n' + + '7ORbPcIAfLihY78FmNpINhxV05ppFj+o/STPX4NlXSPco62WHGLzViCFUrue1SkH\n' + + 'cJaWbWcMNU5KvJgE8XRsCMojcyf//////////wIBAgSBwwKBwHxnT7Zw2Ehh1vyw\n' + + 'eolzQFHQzyuT0y+3BF+FxK2Ox7VPguTp57wQfGHbORJ2cwCdLx2mFM7gk4tZ6COS\n' + + 'E3Vta85a/PuhKXNLRdP79JgLnNtVtKXB+ePDS5C2GgXH1RHvqEdJh7JYnMy7Zj4P\n' + + 'GagGtIy3dV5f4FA0B/2C97jQ1pO16ah8gSLQRKsNpTCw2rqsZusE0rK6RaYAef7H\n' + + 'y/0tmLIsHxLIn+WK9CANqMbCWoP4I178BQaqhiOBkNyNZ0ndqA==\n' + + '-----END PRIVATE KEY-----', + format: 'pem' +}); + +const bobPublicKey = crypto.createPublicKey({ + key: '-----BEGIN PUBLIC KEY-----\n' + + 'MIIBoDCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKLgNwc\n' + + '0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHC\n' + + 'ReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7ORb\n' + + 'PcIAfLihY78FmNpINhxV05ppFj+o/STPX4NlXSPco62WHGLzViCFUrue1SkHcJaW\n' + + 'bWcMNU5KvJgE8XRsCMojcyf//////////wIBAgOBxQACgcEAi26oq8z/GNSBm3zi\n' + + 'gNt7SA7cArUBbTxINa9iLYWp6bxrvCKwDQwISN36/QUw8nUAe8aRyMt0oYn+y6vW\n' + + 'Pw5OlO+TLrUelMVFaADEzoYomH0zVGb0sW4aBN8haC0mbrPt9QshgCvjr1hEPEna\n' + + 'QFKfjzNaJRNMFFd4f2Dn8MSB4yu1xpA1T2i0JSk24vS2H55jx24xhUYtfhT2LJgK\n' + + 'JvnaODey/xtY4Kql10ZKf43Lw6gdQC3G8opC9OxVxt9oNR7Z\n' + + '-----END PUBLIC KEY-----', + format: 'pem' +}); + +assert.throws(() => crypto.diffieHellman({ privateKey: alicePrivateKey }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.publicKey' is invalid. Received undefined" +}); + +assert.throws(() => crypto.diffieHellman({ publicKey: alicePublicKey }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.privateKey' is invalid. Received undefined" +}); + +const privateKey = Buffer.from( + '487CD880159D835FD0A8DBA9848898317283DB07E822741B344AD397BA84CDDD3920A51588' + + 'B891B03B3EBEF3C9F767D921FAC1294D4B5E09CABB6D1DE3EB4527989754FEB64D007EBBDA' + + '2E6C8CE7A17EF41DE3C2DFE7CEAAF963199F55D5DBD9A415E77552FE69B7A41D87888B7D16' + + '6BC569A3957B60EEA6A4ABEB1CDB7FFCF238DF961790791CD54E597B3082981D52C0B2CA0B' + + '3DF212B2FD78DE4C6CC95285889D6DFDB746203177A726B912D26EB0A25F11871C7CD401A0' + + '10B355EC41D9AA', 'hex'); +const publicKey = Buffer.from( + '8b6ea8abccff18d4819b7ce280db7b480edc02b5016d3c4835af622d85a9e9bc6bbc22b00d' + + '0c0848ddfafd0530f275007bc691c8cb74a189fecbabd63f0e4e94ef932eb51e94c5456800' + + 'c4ce8628987d335466f4b16e1a04df21682d266eb3edf50b21802be3af58443c49da40529f' + + '8f335a25134c1457787f60e7f0c481e32bb5c690354f68b4252936e2f4b61f9e63c76e3185' + + '462d7e14f62c980a26f9da3837b2ff1b58e0aaa5d7464a7f8dcbc3a81d402dc6f28a42f4ec' + + '55c6df68351ed9', 'hex'); + +const group = crypto.getDiffieHellman('modp5'); +const dh = crypto.createDiffieHellman(group.getPrime(), group.getGenerator()); +dh.setPrivateKey(privateKey); + +// Test simple Diffie-Hellman, no curves involved. +test({ publicKey: alicePublicKey, privateKey: alicePrivateKey }, + { publicKey: bobPublicKey, privateKey: bobPrivateKey }, + dh.computeSecret(publicKey)); + +test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), + crypto.generateKeyPairSync('dh', { group: 'modp5' })); + +test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), + crypto.generateKeyPairSync('dh', { prime: group.getPrime() })); + +const list = [ + // Same generator, but different primes. + [{ group: 'modp5' }, { group: 'modp18' }]]; + +// TODO(danbev): Take a closer look if there should be a check in OpenSSL3 +// when the dh parameters differ. +if (!common.hasOpenSSL3) { + // Same primes, but different generator. + list.push([{ group: 'modp5' }, { prime: group.getPrime(), generator: 5 }]); + // Same generator, but different primes. + list.push([{ primeLength: 1024 }, { primeLength: 1024 }]); +} + +for (const [params1, params2] of list) { + assert.throws(() => { + test(crypto.generateKeyPairSync('dh', params1), + crypto.generateKeyPairSync('dh', params2)); + }, common.hasOpenSSL3 ? { + name: 'Error', + code: 'ERR_OSSL_MISMATCHING_DOMAIN_PARAMETERS' + } : { + name: 'Error', + code: 'ERR_OSSL_EVP_DIFFERENT_PARAMETERS' + }); +} +{ + const privateKey = crypto.createPrivateKey({ + key: '-----BEGIN PRIVATE KEY-----\n' + + 'MIIBoQIBADCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKL\n' + + 'gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt\n' + + 'bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR\n' + + '7ORbPcIAfLihY78FmNpINhxV05ppFj+o/STPX4NlXSPco62WHGLzViCFUrue1SkH\n' + + 'cJaWbWcMNU5KvJgE8XRsCMojcyf//////////wIBAgSBwwKBwHu9fpiqrfJJ+tl9\n' + + 'ujFtEWv4afub6A/1/7sgishOYN3YQ+nmWQlmPpveIY34an5dG82CTrixHwUzQTMF\n' + + 'JaiCW3ax9+qk31f2jTNKrQznmKgopVKXF0FEJC6H79W/8Y0U14gsI9sHpovKhfou\n' + + 'RQD0QogW7ejSwMG8hCYibfrvMm0b5PHlwimISyEKh7VtDQ1frYN/Wr9ZbiV+FePJ\n' + + '2j6RUKYNj1Pv+B4zdMgiLLjILAs8WUfbHciU21KSJh1izVQaUQ==\n' + + '-----END PRIVATE KEY-----' + }); + const publicKey = crypto.createPublicKey({ + key: '-----BEGIN PUBLIC KEY-----\n' + + 'MIIBoDCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKLgNwc\n' + + '0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHC\n' + + 'ReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7ORb\n' + + 'PcIAfLihY78FmNpINhxV05ppFj+o/STPX4NlXSPco62WHGLzViCFUrue1SkHcJaW\n' + + 'bWcMNU5KvJgE8XRsCMojcyf//////////wIBAgOBxQACgcEAmG9LpD8SAA6/W7oK\n' + + 'E4MCuuQtf5E8bqtcEAfYTOOvKyCS+eiX3TtZRsvHJjUBEyeO99PR/KrGVlkSuW52\n' + + 'ZOSXUOFu1L/0tqHrvRVHo+QEq3OvZ3EAyJkdtSEUTztxuUrMOyJXHDc1OUdNSnk0\n' + + 'taGX4mP3247golVx2DS4viDYs7UtaMdx03dWaP6y5StNUZQlgCIUzL7MYpC16V5y\n' + + 'KkFrE+Kp/Z77gEjivaG6YuxVj4GPLxJYbNFVTel42oSVeKuq\n' + + '-----END PUBLIC KEY-----', + format: 'pem' + }); + + // This key combination will result in an unusually short secret, and should + // not cause an assertion failure. + const secret = crypto.diffieHellman({ publicKey, privateKey }); + assert.strictEqual(secret.toString('hex'), + '0099d0fa242af5db9ea7330e23937a27db041f79c581500fc7f9976' + + '554d59d5b9ced934778d72e19a1fefc81e9d981013198748c0b5c6c' + + '762985eec687dc5bec5c9367b05837daee9d0bcc29024ed7f3abba1' + + '2794b65a745117fb0d87bc5b1b2b68c296c3f686cc29e450e4e1239' + + '21f56a5733fe58aabf71f14582954059c2185d342b9b0fa10c2598a' + + '5426c2baee7f9a686fc1e16cd4757c852bf7225a2732250548efe28' + + 'debc26f1acdec51efe23d20786a6f8a14d360803bbc71972e87fd3'); +} + +// Test ECDH. + +test(crypto.generateKeyPairSync('ec', { namedCurve: 'secp256k1' }), + crypto.generateKeyPairSync('ec', { namedCurve: 'secp256k1' })); + +const not256k1 = crypto.getCurves().find((c) => /^sec.*(224|384|512)/.test(c)); +assert.throws(() => { + test(crypto.generateKeyPairSync('ec', { namedCurve: 'secp256k1' }), + crypto.generateKeyPairSync('ec', { namedCurve: not256k1 })); +}, common.hasOpenSSL3 ? { + name: 'Error', + code: 'ERR_OSSL_MISMATCHING_DOMAIN_PARAMETERS' +} : { + name: 'Error', + code: 'ERR_OSSL_EVP_DIFFERENT_PARAMETERS' +}); + +// Test ECDH-ES. + +test(crypto.generateKeyPairSync('x448'), + crypto.generateKeyPairSync('x448')); + +test(crypto.generateKeyPairSync('x25519'), + crypto.generateKeyPairSync('x25519')); + +assert.throws(() => { + test(crypto.generateKeyPairSync('x448'), + crypto.generateKeyPairSync('x25519')); +}, { + name: 'Error', + code: 'ERR_CRYPTO_INCOMPATIBLE_KEY', + message: 'Incompatible key types for Diffie-Hellman: x448 and x25519' +}); diff --git a/test/crypto/test-crypto-dh.js b/test/crypto/test-crypto-dh.js new file mode 100644 index 0000000..b8897f8 --- /dev/null +++ b/test/crypto/test-crypto-dh.js @@ -0,0 +1,206 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +const size = common.hasFipsCrypto || common.hasOpenSSL3 ? 1024 : 256; +const dh1 = crypto.createDiffieHellman(size); +const p1 = dh1.getPrime('buffer'); +const dh2 = crypto.createDiffieHellman(p1, 'buffer'); +const key1 = dh1.generateKeys(); +const key2 = dh2.generateKeys('hex'); +const secret1 = dh1.computeSecret(key2, 'hex', 'base64'); +const secret2 = dh2.computeSecret(key1, 'latin1', 'buffer'); + +// Test Diffie-Hellman with two parties sharing a secret, +// using various encodings as we go along +assert.strictEqual(secret2.toString('base64'), secret1); +assert.strictEqual(dh1.verifyError, 0); +assert.strictEqual(dh2.verifyError, 0); + +// https://github.com/nodejs/node/issues/32738 +// XXX(bnoordhuis) validateInt32() throwing ERR_OUT_OF_RANGE and RangeError +// instead of ERR_INVALID_ARG_TYPE and TypeError is questionable, IMO. +assert.throws(() => crypto.createDiffieHellman(13.37), { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "sizeOrKey" is out of range. ' + + 'It must be an integer. Received 13.37', +}); + +assert.throws(() => crypto.createDiffieHellman('abcdef', 13.37), { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "generator" is out of range. ' + + 'It must be an integer. Received 13.37', +}); + +for (const bits of [-1, 0, 1]) { + if (common.hasOpenSSL3) { + assert.throws(() => crypto.createDiffieHellman(bits), { + code: 'ERR_OSSL_DH_MODULUS_TOO_SMALL', + name: 'Error', + message: /modulus too small/, + }); + } else { + assert.throws(() => crypto.createDiffieHellman(bits), { + code: 'ERR_OSSL_BN_BITS_TOO_SMALL', + name: 'Error', + message: /bits too small/, + }); + } +} + +// Through a fluke of history, g=0 defaults to DH_GENERATOR (2). +{ + const g = 0; + crypto.createDiffieHellman('abcdef', g); + crypto.createDiffieHellman('abcdef', 'hex', g); +} + +for (const g of [-1, 1]) { + const ex = { + code: 'ERR_OSSL_DH_BAD_GENERATOR', + name: 'Error', + message: /bad generator/, + }; + assert.throws(() => crypto.createDiffieHellman('abcdef', g), ex); + assert.throws(() => crypto.createDiffieHellman('abcdef', 'hex', g), ex); +} + +crypto.createDiffieHellman('abcdef', Buffer.from([2])); // OK + +for (const g of [Buffer.from([]), + Buffer.from([0]), + Buffer.from([1])]) { + const ex = { + code: 'ERR_OSSL_DH_BAD_GENERATOR', + name: 'Error', + message: /bad generator/, + }; + assert.throws(() => crypto.createDiffieHellman('abcdef', g), ex); + assert.throws(() => crypto.createDiffieHellman('abcdef', 'hex', g), ex); +} + +[ + [0x1, 0x2], + () => { }, + /abc/, + {}, +].forEach((input) => { + assert.throws( + () => crypto.createDiffieHellman(input), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } + ); +}); + +// Create "another dh1" using generated keys from dh1, +// and compute secret again +const dh3 = crypto.createDiffieHellman(p1, 'buffer'); +const privkey1 = dh1.getPrivateKey(); +dh3.setPublicKey(key1); +dh3.setPrivateKey(privkey1); + +assert.deepStrictEqual(dh1.getPrime(), dh3.getPrime()); +assert.deepStrictEqual(dh1.getGenerator(), dh3.getGenerator()); +assert.deepStrictEqual(dh1.getPublicKey(), dh3.getPublicKey()); +assert.deepStrictEqual(dh1.getPrivateKey(), dh3.getPrivateKey()); +assert.strictEqual(dh3.verifyError, 0); + +const secret3 = dh3.computeSecret(key2, 'hex', 'base64'); + +assert.strictEqual(secret1, secret3); + +// computeSecret works without a public key set at all. +const dh4 = crypto.createDiffieHellman(p1, 'buffer'); +dh4.setPrivateKey(privkey1); + +assert.deepStrictEqual(dh1.getPrime(), dh4.getPrime()); +assert.deepStrictEqual(dh1.getGenerator(), dh4.getGenerator()); +assert.deepStrictEqual(dh1.getPrivateKey(), dh4.getPrivateKey()); +assert.strictEqual(dh4.verifyError, 0); + +const secret4 = dh4.computeSecret(key2, 'hex', 'base64'); + +assert.strictEqual(secret1, secret4); + +let wrongBlockLength; +if (common.hasOpenSSL3) { + wrongBlockLength = { + message: 'error:1C80006B:Provider routines::wrong final block length', + code: 'ERR_OSSL_WRONG_FINAL_BLOCK_LENGTH', + library: 'Provider routines', + reason: 'wrong final block length' + }; +} else { + wrongBlockLength = { + message: 'error:0606506D:digital envelope' + + ' routines:EVP_DecryptFinal_ex:wrong final block length', + code: 'ERR_OSSL_EVP_WRONG_FINAL_BLOCK_LENGTH', + library: 'digital envelope routines', + reason: 'wrong final block length' + }; +} + +// Run this one twice to make sure that the dh3 clears its error properly +{ + const c = crypto.createDecipheriv('aes-128-ecb', crypto.randomBytes(16), ''); + assert.throws(() => { + c.final('utf8'); + }, wrongBlockLength); +} + +{ + const c = crypto.createDecipheriv('aes-128-ecb', crypto.randomBytes(16), ''); + assert.throws(() => { + c.final('utf8'); + }, wrongBlockLength); +} + +assert.throws(() => { + dh3.computeSecret(''); +}, { message: common.hasOpenSSL3 ? + 'error:02800080:Diffie-Hellman routines::invalid secret' : + 'Supplied key is too small' }); + +// Invalid test: curve argument is undefined +assert.throws( + () => crypto.createECDH(), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "curve" argument must be of type string. ' + + 'Received undefined' + }); + +assert.throws( + function() { + crypto.getDiffieHellman('unknown-group'); + }, + { + name: 'Error', + code: 'ERR_CRYPTO_UNKNOWN_DH_GROUP', + message: 'Unknown DH group' + }, + 'crypto.getDiffieHellman(\'unknown-group\') ' + + 'failed to throw the expected error.' +); + +assert.throws( + () => crypto.createDiffieHellman('', true), + { + code: 'ERR_INVALID_ARG_TYPE' + } +); +[true, Symbol(), {}, () => {}, []].forEach((generator) => assert.throws( + () => crypto.createDiffieHellman('', 'base64', generator), + { code: 'ERR_INVALID_ARG_TYPE' } +)); diff --git a/test/crypto/test-crypto-domain.js b/test/crypto/test-crypto-domain.js new file mode 100644 index 0000000..8eafca8 --- /dev/null +++ b/test/crypto/test-crypto-domain.js @@ -0,0 +1,49 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const domain = require('domain'); + +const test = (fn) => { + const ex = new Error('BAM'); + const d = domain.create(); + d.on('error', common.mustCall(function(err) { + assert.strictEqual(err, ex); + })); + const cb = common.mustCall(function() { + throw ex; + }); + d.run(cb); +}; + +test(function(cb) { + crypto.pbkdf2('password', 'salt', 1, 8, cb); +}); + +test(function(cb) { + crypto.randomBytes(32, cb); +}); diff --git a/test/crypto/test-crypto-domains.js b/test/crypto/test-crypto-domains.js new file mode 100644 index 0000000..35e755d --- /dev/null +++ b/test/crypto/test-crypto-domains.js @@ -0,0 +1,60 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const domain = require('domain'); +const assert = require('assert'); +const crypto = require('crypto'); + +const d = domain.create(); +const expect = ['pbkdf2', 'randomBytes', 'pseudoRandomBytes']; + +d.on('error', common.mustCall(function(e) { + assert.strictEqual(e.message, expect.shift()); +}, 3)); + +d.run(function() { + one(); + + function one() { + crypto.pbkdf2('a', 'b', 1, 8, 'sha1', function() { + two(); + throw new Error('pbkdf2'); + }); + } + + function two() { + crypto.randomBytes(4, function() { + three(); + throw new Error('randomBytes'); + }); + } + + function three() { + crypto.pseudoRandomBytes(4, function() { + throw new Error('pseudoRandomBytes'); + }); + } +}); diff --git a/test/crypto/test-crypto-ecb.js b/test/crypto/test-crypto-ecb.js new file mode 100644 index 0000000..22bec67 --- /dev/null +++ b/test/crypto/test-crypto-ecb.js @@ -0,0 +1,54 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +if (common.hasFipsCrypto) + common.skip('BF-ECB is not FIPS 140-2 compatible'); + +if (common.hasOpenSSL3) + common.skip('Blowfish is only available with the legacy provider in ' + + 'OpenSSl 3.x'); + +const assert = require('assert'); +const crypto = require('crypto'); + +// Testing whether EVP_CipherInit_ex is functioning correctly. +// Reference: bug#1997 + +{ + const encrypt = + crypto.createCipheriv('BF-ECB', 'SomeRandomBlahz0c5GZVnR', ''); + let hex = encrypt.update('Hello World!', 'ascii', 'hex'); + hex += encrypt.final('hex'); + assert.strictEqual(hex.toUpperCase(), '6D385F424AAB0CFBF0BB86E07FFB7D71'); +} + +{ + const decrypt = + crypto.createDecipheriv('BF-ECB', 'SomeRandomBlahz0c5GZVnR', ''); + let msg = decrypt.update('6D385F424AAB0CFBF0BB86E07FFB7D71', 'hex', 'ascii'); + msg += decrypt.final('ascii'); + assert.strictEqual(msg, 'Hello World!'); +} diff --git a/test/crypto/test-crypto-ecdh-convert-key.js b/test/crypto/test-crypto-ecdh-convert-key.js new file mode 100644 index 0000000..f234256 --- /dev/null +++ b/test/crypto/test-crypto-ecdh-convert-key.js @@ -0,0 +1,127 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); + +const { ECDH, createSign, getCurves } = require('crypto'); + +// A valid private key for the secp256k1 curve. +const cafebabeKey = 'cafebabe'.repeat(8); +// Associated compressed and uncompressed public keys (points). +const cafebabePubPtComp = + '03672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3'; +const cafebabePubPtUnComp = + '04672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3' + + '2e02c7f93d13dc2732b760ca377a5897b9dd41a1c1b29dc0442fdce6d0a04d1d'; + +// Invalid test: key argument is undefined. +assert.throws( + () => ECDH.convertKey(), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + +// Invalid test: curve argument is undefined. +assert.throws( + () => ECDH.convertKey(cafebabePubPtComp), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + +// Invalid test: curve argument is invalid. +assert.throws( + () => ECDH.convertKey(cafebabePubPtComp, 'badcurve'), + { + name: 'TypeError', + message: 'Invalid EC curve name' + }); + +if (getCurves().includes('secp256k1')) { + // Invalid test: format argument is undefined. + assert.throws( + () => ECDH.convertKey(cafebabePubPtComp, 'secp256k1', 'hex', 'hex', 10), + { + code: 'ERR_CRYPTO_ECDH_INVALID_FORMAT', + name: 'TypeError', + message: 'Invalid ECDH format: 10' + }); + + // Point formats. + let uncompressed = ECDH.convertKey(cafebabePubPtComp, + 'secp256k1', + 'hex', + 'buffer', + 'uncompressed'); + let compressed = ECDH.convertKey(cafebabePubPtComp, + 'secp256k1', + 'hex', + 'buffer', + 'compressed'); + let hybrid = ECDH.convertKey(cafebabePubPtComp, + 'secp256k1', + 'hex', + 'buffer', + 'hybrid'); + assert.strictEqual(uncompressed[0], 4); + let firstByte = compressed[0]; + assert(firstByte === 2 || firstByte === 3); + firstByte = hybrid[0]; + assert(firstByte === 6 || firstByte === 7); + + // Format conversion from hex to hex + uncompressed = ECDH.convertKey(cafebabePubPtComp, + 'secp256k1', + 'hex', + 'hex', + 'uncompressed'); + compressed = ECDH.convertKey(cafebabePubPtComp, + 'secp256k1', + 'hex', + 'hex', + 'compressed'); + hybrid = ECDH.convertKey(cafebabePubPtComp, + 'secp256k1', + 'hex', + 'hex', + 'hybrid'); + assert.strictEqual(uncompressed, cafebabePubPtUnComp); + assert.strictEqual(compressed, cafebabePubPtComp); + + // Compare to getPublicKey. + const ecdh1 = ECDH('secp256k1'); + ecdh1.generateKeys(); + ecdh1.setPrivateKey(cafebabeKey, 'hex'); + assert.strictEqual(ecdh1.getPublicKey('hex', 'uncompressed'), uncompressed); + assert.strictEqual(ecdh1.getPublicKey('hex', 'compressed'), compressed); + assert.strictEqual(ecdh1.getPublicKey('hex', 'hybrid'), hybrid); +} + +// See https://github.com/nodejs/node/issues/26133, failed ConvertKey +// operations should not leave errors on OpenSSL's error stack because +// that's observable by subsequent operations. +{ + const privateKey = + '-----BEGIN EC PRIVATE KEY-----\n' + + 'MHcCAQEEIF+jnWY1D5kbVYDNvxxo/Y+ku2uJPDwS0r/VuPZQrjjVoAoGCCqGSM49\n' + + 'AwEHoUQDQgAEurOxfSxmqIRYzJVagdZfMMSjRNNhB8i3mXyIMq704m2m52FdfKZ2\n' + + 'pQhByd5eyj3lgZ7m7jbchtdgyOF8Io/1ng==\n' + + '-----END EC PRIVATE KEY-----'; + + const sign = createSign('sha256').update('plaintext'); + + // TODO(bnoordhuis) This should really bubble up the specific OpenSSL error + // rather than Node's generic error message. + const badKey = 'f'.repeat(128); + assert.throws( + () => ECDH.convertKey(badKey, 'secp521r1', 'hex', 'hex', 'compressed'), + /Failed to convert Buffer to EC_POINT/); + + // Next statement should not throw an exception. + sign.sign(privateKey); +} diff --git a/test/crypto/test-crypto-fips.js b/test/crypto/test-crypto-fips.js new file mode 100644 index 0000000..2d89a5e --- /dev/null +++ b/test/crypto/test-crypto-fips.js @@ -0,0 +1,277 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const spawnSync = require('child_process').spawnSync; +const path = require('path'); +const fixtures = require('../common/fixtures'); +const { internalBinding } = require('internal/test/binding'); +const { testFipsCrypto } = internalBinding('crypto'); + +const FIPS_ENABLED = 1; +const FIPS_DISABLED = 0; +const FIPS_ERROR_STRING2 = + 'Error [ERR_CRYPTO_FIPS_FORCED]: Cannot set FIPS mode, it was forced with ' + + '--force-fips at startup.'; +const FIPS_UNSUPPORTED_ERROR_STRING = 'fips mode not supported'; +const FIPS_ENABLE_ERROR_STRING = 'OpenSSL error when trying to enable FIPS:'; + +const CNF_FIPS_ON = fixtures.path('openssl_fips_enabled.cnf'); +const CNF_FIPS_OFF = fixtures.path('openssl_fips_disabled.cnf'); + +let num_children_ok = 0; + +function sharedOpenSSL() { + return process.config.variables.node_shared_openssl; +} + +function testHelper(stream, args, expectedOutput, cmd, env) { + const fullArgs = args.concat(['-e', `console.log(${cmd})`]); + const child = spawnSync(process.execPath, fullArgs, { + cwd: path.dirname(process.execPath), + env: env + }); + + console.error( + `Spawned child [pid:${child.pid}] with cmd '${cmd}' expect %j with args '${ + args}' OPENSSL_CONF=%j`, expectedOutput, env.OPENSSL_CONF); + + function childOk(child) { + console.error(`Child #${++num_children_ok} [pid:${child.pid}] OK.`); + } + + function responseHandler(buffer, expectedOutput) { + const response = buffer.toString(); + assert.notStrictEqual(response.length, 0); + if (FIPS_ENABLED !== expectedOutput && FIPS_DISABLED !== expectedOutput) { + // In the case of expected errors just look for a substring. + assert.ok(response.includes(expectedOutput)); + } else { + const getFipsValue = Number(response); + if (!Number.isNaN(getFipsValue)) + // Normal path where we expect either FIPS enabled or disabled. + assert.strictEqual(getFipsValue, expectedOutput); + } + childOk(child); + } + + responseHandler(child[stream], expectedOutput); +} + +// --enable-fips should raise an error if OpenSSL is not FIPS enabled. +testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + ['--enable-fips'], + testFipsCrypto() ? FIPS_ENABLED : FIPS_ENABLE_ERROR_STRING, + 'process.versions', + process.env); + +// --force-fips should raise an error if OpenSSL is not FIPS enabled. +testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + ['--force-fips'], + testFipsCrypto() ? FIPS_ENABLED : FIPS_ENABLE_ERROR_STRING, + 'process.versions', + process.env); + +// By default FIPS should be off in both FIPS and non-FIPS builds. +testHelper( + 'stdout', + [], + FIPS_DISABLED, + 'require("crypto").getFips()', + { ...process.env, 'OPENSSL_CONF': ' ' }); + +// Toggling fips with setFips should not be allowed from a worker thread +testHelper( + 'stderr', + [], + 'Calling crypto.setFips() is not supported in workers', + 'new worker_threads.Worker(\'require("crypto").setFips(true);\', { eval: true })', + process.env); + +// This should succeed for both FIPS and non-FIPS builds in combination with +// OpenSSL 1.1.1 or OpenSSL 3.0 +const test_result = testFipsCrypto(); +assert.ok(test_result === 1 || test_result === 0); + +// If Node was configured using --shared-openssl fips support might be +// available depending on how OpenSSL was built. If fips support is +// available the tests that toggle the fips_mode on/off using the config +// file option will succeed and return 1 instead of 0. +// +// Note that this case is different from when calling the fips setter as the +// configuration file is handled by OpenSSL, so it is not possible for us +// to try to call the fips setter, to try to detect this situation, as +// that would throw an error: +// ("Error: Cannot set FIPS mode in a non-FIPS build."). +// Due to this uncertainty the following tests are skipped when configured +// with --shared-openssl. +if (!sharedOpenSSL() && !common.hasOpenSSL3) { + // OpenSSL config file should be able to turn on FIPS mode + testHelper( + 'stdout', + [`--openssl-config=${CNF_FIPS_ON}`], + testFipsCrypto() ? FIPS_ENABLED : FIPS_DISABLED, + 'require("crypto").getFips()', + process.env); + + // OPENSSL_CONF should be able to turn on FIPS mode + testHelper( + 'stdout', + [], + testFipsCrypto() ? FIPS_ENABLED : FIPS_DISABLED, + 'require("crypto").getFips()', + Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_ON })); + + // --openssl-config option should override OPENSSL_CONF + testHelper( + 'stdout', + [`--openssl-config=${CNF_FIPS_ON}`], + testFipsCrypto() ? FIPS_ENABLED : FIPS_DISABLED, + 'require("crypto").getFips()', + Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_OFF })); +} + +// OpenSSL 3.x has changed the configuration files so the following tests +// will not work as expected with that version. +// TODO(danbev) Revisit these test once FIPS support is available in +// OpenSSL 3.x. +if (!common.hasOpenSSL3) { + testHelper( + 'stdout', + [`--openssl-config=${CNF_FIPS_OFF}`], + FIPS_DISABLED, + 'require("crypto").getFips()', + Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_ON })); + + // --enable-fips should take precedence over OpenSSL config file + testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + ['--enable-fips', `--openssl-config=${CNF_FIPS_OFF}`], + testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, + 'require("crypto").getFips()', + process.env); + // --force-fips should take precedence over OpenSSL config file + testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + ['--force-fips', `--openssl-config=${CNF_FIPS_OFF}`], + testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, + 'require("crypto").getFips()', + process.env); + // --enable-fips should turn FIPS mode on + testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + ['--enable-fips'], + testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, + 'require("crypto").getFips()', + process.env); + + // --force-fips should turn FIPS mode on + testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + ['--force-fips'], + testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, + 'require("crypto").getFips()', + process.env); + + // OPENSSL_CONF should _not_ make a difference to --enable-fips + testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + ['--enable-fips'], + testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, + 'require("crypto").getFips()', + Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_OFF })); + + // Using OPENSSL_CONF should not make a difference to --force-fips + testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + ['--force-fips'], + testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, + 'require("crypto").getFips()', + Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_OFF })); + + // setFipsCrypto should be able to turn FIPS mode on + testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + [], + testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, + '(require("crypto").setFips(true),' + + 'require("crypto").getFips())', + process.env); + + // setFipsCrypto should be able to turn FIPS mode on and off + testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + [], + testFipsCrypto() ? FIPS_DISABLED : FIPS_UNSUPPORTED_ERROR_STRING, + '(require("crypto").setFips(true),' + + 'require("crypto").setFips(false),' + + 'require("crypto").getFips())', + process.env); + + // setFipsCrypto takes precedence over OpenSSL config file, FIPS on + testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + [`--openssl-config=${CNF_FIPS_OFF}`], + testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, + '(require("crypto").setFips(true),' + + 'require("crypto").getFips())', + process.env); + + // setFipsCrypto takes precedence over OpenSSL config file, FIPS off + testHelper( + 'stdout', + [`--openssl-config=${CNF_FIPS_ON}`], + FIPS_DISABLED, + '(require("crypto").setFips(false),' + + 'require("crypto").getFips())', + process.env); + + // --enable-fips does not prevent use of setFipsCrypto API + testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + ['--enable-fips'], + testFipsCrypto() ? FIPS_DISABLED : FIPS_UNSUPPORTED_ERROR_STRING, + '(require("crypto").setFips(false),' + + 'require("crypto").getFips())', + process.env); + + // --force-fips prevents use of setFipsCrypto API + testHelper( + 'stderr', + ['--force-fips'], + testFipsCrypto() ? FIPS_ERROR_STRING2 : FIPS_UNSUPPORTED_ERROR_STRING, + 'require("crypto").setFips(false)', + process.env); + + // --force-fips makes setFipsCrypto enable a no-op (FIPS stays on) + testHelper( + testFipsCrypto() ? 'stdout' : 'stderr', + ['--force-fips'], + testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, + '(require("crypto").setFips(true),' + + 'require("crypto").getFips())', + process.env); + + // --force-fips and --enable-fips order does not matter + testHelper( + 'stderr', + ['--force-fips', '--enable-fips'], + testFipsCrypto() ? FIPS_ERROR_STRING2 : FIPS_UNSUPPORTED_ERROR_STRING, + 'require("crypto").setFips(false)', + process.env); + + // --enable-fips and --force-fips order does not matter + testHelper( + 'stderr', + ['--enable-fips', '--force-fips'], + testFipsCrypto() ? FIPS_ERROR_STRING2 : FIPS_UNSUPPORTED_ERROR_STRING, + 'require("crypto").setFips(false)', + process.env); +} diff --git a/test/crypto/test-crypto-from-binary.js b/test/crypto/test-crypto-from-binary.js new file mode 100644 index 0000000..6203e1f --- /dev/null +++ b/test/crypto/test-crypto-from-binary.js @@ -0,0 +1,65 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +// This is the same as test/simple/test-crypto, but from before the shift +// to use buffers by default. + + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +const EXTERN_APEX = 0xFBEE9; + +// Manually controlled string for checking binary output +let ucs2_control = 'a\u0000'; + +// Grow the strings to proper length +while (ucs2_control.length <= EXTERN_APEX) { + ucs2_control = ucs2_control.repeat(2); +} + + +// Check resultant buffer and output string +const b = Buffer.from(ucs2_control + ucs2_control, 'ucs2'); + +// +// Test updating from birant data +// +{ + const datum1 = b.slice(700000); + const hash1_converted = crypto.createHash('sha1') + .update(datum1.toString('base64'), 'base64') + .digest('hex'); + const hash1_direct = crypto.createHash('sha1').update(datum1).digest('hex'); + assert.strictEqual(hash1_direct, hash1_converted); + + const datum2 = b; + const hash2_converted = crypto.createHash('sha1') + .update(datum2.toString('base64'), 'base64') + .digest('hex'); + const hash2_direct = crypto.createHash('sha1').update(datum2).digest('hex'); + assert.strictEqual(hash2_direct, hash2_converted); +} diff --git a/test/crypto/test-crypto-getcipherinfo.js b/test/crypto/test-crypto-getcipherinfo.js new file mode 100644 index 0000000..90fe583 --- /dev/null +++ b/test/crypto/test-crypto-getcipherinfo.js @@ -0,0 +1,72 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { + getCiphers, + getCipherInfo +} = require('crypto'); + +const assert = require('assert'); + +const ciphers = getCiphers(); + +assert.strictEqual(getCipherInfo(-1), undefined); +assert.strictEqual(getCipherInfo('cipher that does not exist'), undefined); + +ciphers.forEach((cipher) => { + const info = getCipherInfo(cipher); + assert(info); + const info2 = getCipherInfo(info.nid); + assert.deepStrictEqual(info, info2); +}); + +const info = getCipherInfo('aes-128-cbc'); +assert.strictEqual(info.name, 'aes-128-cbc'); +assert.strictEqual(info.nid, 419); +assert.strictEqual(info.blockSize, 16); +assert.strictEqual(info.ivLength, 16); +assert.strictEqual(info.keyLength, 16); +assert.strictEqual(info.mode, 'cbc'); + +[null, undefined, [], {}].forEach((arg) => { + assert.throws(() => getCipherInfo(arg), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +[null, '', 1, true].forEach((options) => { + assert.throws( + () => getCipherInfo('aes-192-cbc', options), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +[null, '', {}, [], true].forEach((len) => { + assert.throws( + () => getCipherInfo('aes-192-cbc', { keyLength: len }), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws( + () => getCipherInfo('aes-192-cbc', { ivLength: len }), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +assert(!getCipherInfo('aes-128-cbc', { keyLength: 12 })); +assert(getCipherInfo('aes-128-cbc', { keyLength: 16 })); +assert(!getCipherInfo('aes-128-cbc', { ivLength: 12 })); +assert(getCipherInfo('aes-128-cbc', { ivLength: 16 })); + +assert(!getCipherInfo('aes-128-ccm', { ivLength: 1 })); +assert(!getCipherInfo('aes-128-ccm', { ivLength: 14 })); +for (let n = 7; n <= 13; n++) + assert(getCipherInfo('aes-128-ccm', { ivLength: n })); + +assert(!getCipherInfo('aes-128-ocb', { ivLength: 16 })); +for (let n = 1; n < 16; n++) + assert(getCipherInfo('aes-128-ocb', { ivLength: n })); diff --git a/test/crypto/test-crypto-hash-stream-pipe.js b/test/crypto/test-crypto-hash-stream-pipe.js new file mode 100644 index 0000000..950261d --- /dev/null +++ b/test/crypto/test-crypto-hash-stream-pipe.js @@ -0,0 +1,46 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +import common from '../common'; + +if (!common.hasCrypto) + common.skip('missing crypto'); + +import assert from 'assert'; +import crypto from 'crypto'; + +import stream from 'stream'; +const s = new stream.PassThrough(); +const h = crypto.createHash('sha3-512'); +const expect = '36a38a2a35e698974d4e5791a3f05b05' + + '198235381e864f91a0e8cd6a26b677ec' + + 'dcde8e2b069bd7355fabd68abd6fc801' + + '19659f25e92f8efc961ee3a7c815c758'; + +s.pipe(h).on('data', common.mustCall(function(c) { + assert.strictEqual(c, expect); + // Calling digest() after piping into a stream with SHA3 should not cause + // a segmentation fault, see https://github.com/nodejs/node/issues/28245. + assert.strictEqual(h.digest('hex'), expect); +})).setEncoding('hex'); + +s.end('aoeu'); diff --git a/test/crypto/test-crypto-hash.js b/test/crypto/test-crypto-hash.js new file mode 100644 index 0000000..f9bef94 --- /dev/null +++ b/test/crypto/test-crypto-hash.js @@ -0,0 +1,280 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +import common from'../common'; +if (!common.hasCrypto) + common.skip('missing crypto'); + +import assert from 'assert'; +import crypto from 'crypto'; +import fs from 'fs'; + +import fixtures from '../common/fixtures'; + +let cryptoType; +let digest; + +// Test hashing +//const a1 = crypto.createHash('sha1').update('Test123').digest('hex'); +const a2 = crypto.createHash('sha256').update('Test123').digest('base64'); +const a3 = crypto.createHash('sha512').update('Test123').digest(); // buffer +//const a4 = crypto.createHash('sha1').update('Test123').digest('buffer'); + +// stream interface +let a5 = crypto.createHash('sha512'); +a5.end('Test123'); +a5 = a5.read(); + +let a6 = crypto.createHash('sha512'); +a6.write('Te'); +a6.write('st'); +a6.write('123'); +a6.end(); +a6 = a6.read(); + +let a7 = crypto.createHash('sha512'); +a7.end(); +a7 = a7.read(); + +let a8 = crypto.createHash('sha512'); +a8.write(''); +a8.end(); +a8 = a8.read(); +/* +if (!common.hasFipsCrypto) { + cryptoType = 'md5'; + digest = 'latin1'; + const a0 = crypto.createHash(cryptoType).update('Test123').digest(digest); + assert.strictEqual( + a0, + 'h\u00ea\u00cb\u0097\u00d8o\fF!\u00fa+\u000e\u0017\u00ca\u00bd\u008c', + `${cryptoType} with ${digest} digest failed to evaluate to expected hash` + ); +} +cryptoType = 'md5'; +digest = 'hex'; +assert.strictEqual( + a1, + '8308651804facb7b9af8ffc53a33a22d6a1c8ac2', + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`);*/ +cryptoType = 'sha256'; +digest = 'base64'; +assert.strictEqual( + a2, + '2bX1jws4GYKTlxhloUB09Z66PoJZW+y+hq5R8dnx9l4=', + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`); +cryptoType = 'sha512'; +digest = 'latin1'; +assert.deepStrictEqual( + a3, + Buffer.from( + '\u00c1(4\u00f1\u0003\u001fd\u0097!O\'\u00d4C/&Qz\u00d4' + + '\u0094\u0015l\u00b8\u008dQ+\u00db\u001d\u00c4\u00b5}\u00b2' + + '\u00d6\u0092\u00a3\u00df\u00a2i\u00a1\u009b\n\n*\u000f' + + '\u00d7\u00d6\u00a2\u00a8\u0085\u00e3<\u0083\u009c\u0093' + + '\u00c2\u0006\u00da0\u00a1\u00879(G\u00ed\'', + 'latin1'), + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`); +/*cryptoType = 'sha1'; +digest = 'hex'; +assert.deepStrictEqual( + a4, + Buffer.from('8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'hex'), + `${cryptoType} with ${digest} digest failed to evaluate to expected hash` +);*/ + +// Stream interface should produce the same result. +assert.deepStrictEqual(a5, a3); +assert.deepStrictEqual(a6, a3); +assert.notStrictEqual(a7, undefined); +assert.notStrictEqual(a8, undefined); + +// Test multiple updates to same hash +/*const h1 = crypto.createHash('sha1').update('Test123').digest('hex'); +const h2 = crypto.createHash('sha1').update('Test').update('123').digest('hex'); +assert.strictEqual(h1, h2);*/ + +// Test hashing for binary files +/*const fn = fixtures.path('sample.png'); +const sha1Hash = crypto.createHash('sha1'); +const fileStream = fs.createReadStream(fn); +fileStream.on('data', function(data) { + sha1Hash.update(data); +}); +fileStream.on('close', common.mustCall(function() { + // Test SHA1 of sample.png + assert.strictEqual(sha1Hash.digest('hex'), + '22723e553129a336ad96e10f6aecdf0f45e4149e'); +}));*/ + +// Issue https://github.com/nodejs/node-v0.x-archive/issues/2227: unknown digest +// method should throw an error. +assert.throws(function() { + crypto.createHash('xyzzy'); +}, /Digest method not supported/); + +// Issue https://github.com/nodejs/node/issues/9819: throwing encoding used to +// segfault. +assert.throws( + () => crypto.createHash('sha256').digest({ + toString: () => { throw new Error('boom'); }, + }), + { + name: 'Error', + message: 'boom' + }); + +// Issue https://github.com/nodejs/node/issues/25487: error message for invalid +// arg type to update method should include all possible types +assert.throws( + () => crypto.createHash('sha256').update(), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + +// Default UTF-8 encoding +const hutf8 = crypto.createHash('sha512').update('УТФ-8 text').digest('hex'); +assert.strictEqual( + hutf8, + '4b21bbd1a68e690a730ddcb5a8bc94ead9879ffe82580767ad7ec6fa8ba2dea6' + + '43a821af66afa9a45b6a78c712fecf0e56dc7f43aef4bcfc8eb5b4d8dca6ea5b'); + +assert.notStrictEqual( + hutf8, + crypto.createHash('sha512').update('УТФ-8 text', 'latin1').digest('hex')); + +const h3 = crypto.createHash('sha256'); +h3.digest(); + +assert.throws( + () => h3.digest(), + { + code: 'ERR_CRYPTO_HASH_FINALIZED', + name: 'Error' + }); + +assert.throws( + () => h3.update('foo'), + { + code: 'ERR_CRYPTO_HASH_FINALIZED', + name: 'Error' + }); + +assert.strictEqual( + crypto.createHash('sha256').update('test').digest('ucs2'), + crypto.createHash('sha256').update('test').digest().toString('ucs2')); + +assert.throws( + () => crypto.createHash(), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "algorithm" argument must be of type string. ' + + 'Received undefined' + } +); + +{ + const Hash = crypto.Hash; + const instance = crypto.Hash('sha256'); + assert(instance instanceof Hash, 'Hash is expected to return a new instance' + + ' when called without `new`'); +} + +// Test XOF hash functions and the outputLength option. +{ + // Default outputLengths. + /*assert.strictEqual(crypto.createHash('shake128').digest('hex'), + '7f9c2ba4e88f827d616045507605853e'); + assert.strictEqual(crypto.createHash('shake128', null).digest('hex'), + '7f9c2ba4e88f827d616045507605853e'); + assert.strictEqual(crypto.createHash('shake256').digest('hex'), + '46b9dd2b0ba88d13233b3feb743eeb24' + + '3fcd52ea62b81b82b50c27646ed5762f'); + assert.strictEqual(crypto.createHash('shake256', { outputLength: 0 }) + .copy() // Default outputLength. + .digest('hex'), + '46b9dd2b0ba88d13233b3feb743eeb24' + + '3fcd52ea62b81b82b50c27646ed5762f'); + + // Short outputLengths. + assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 }) + .digest('hex'), + ''); + assert.strictEqual(crypto.createHash('shake128', { outputLength: 5 }) + .copy({ outputLength: 0 }) + .digest('hex'), + ''); + assert.strictEqual(crypto.createHash('shake128', { outputLength: 5 }) + .digest('hex'), + '7f9c2ba4e8'); + assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 }) + .copy({ outputLength: 5 }) + .digest('hex'), + '7f9c2ba4e8'); + assert.strictEqual(crypto.createHash('shake128', { outputLength: 15 }) + .digest('hex'), + '7f9c2ba4e88f827d61604550760585'); + assert.strictEqual(crypto.createHash('shake256', { outputLength: 16 }) + .digest('hex'), + '46b9dd2b0ba88d13233b3feb743eeb24'); + + // Large outputLengths. + assert.strictEqual(crypto.createHash('shake128', { outputLength: 128 }) + .digest('hex'), + '7f9c2ba4e88f827d616045507605853e' + + 'd73b8093f6efbc88eb1a6eacfa66ef26' + + '3cb1eea988004b93103cfb0aeefd2a68' + + '6e01fa4a58e8a3639ca8a1e3f9ae57e2' + + '35b8cc873c23dc62b8d260169afa2f75' + + 'ab916a58d974918835d25e6a435085b2' + + 'badfd6dfaac359a5efbb7bcc4b59d538' + + 'df9a04302e10c8bc1cbf1a0b3a5120ea'); + const superLongHash = crypto.createHash('shake256', { + outputLength: 1024 * 1024 + }).update('The message is shorter than the hash!') + .digest('hex'); + assert.strictEqual(superLongHash.length, 2 * 1024 * 1024); + assert.ok(superLongHash.endsWith('193414035ddba77bf7bba97981e656ec')); + assert.ok(superLongHash.startsWith('a2a28dbc49cfd6e5d6ceea3d03e77748')); + + // Non-XOF hash functions should accept valid outputLength options as well. + assert.strictEqual(crypto.createHash('sha224', { outputLength: 28 }) + .digest('hex'), + 'd14a028c2a3a2bc9476102bb288234c4' + + '15a2b01f828ea62ac5b3e42f'); + + // Passing invalid sizes should throw during creation. + assert.throws(() => { + crypto.createHash('sha256', { outputLength: 28 }); + }, { + code: 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH' + });*/ + + for (const outputLength of [null, {}, 'foo', false]) { + assert.throws(() => crypto.createHash('sha256', { outputLength }), + { code: 'ERR_INVALID_ARG_TYPE' }); + } + + for (const outputLength of [-1, .5, Infinity, 2 ** 90]) { + assert.throws(() => crypto.createHash('sha256', { outputLength }), + { code: 'ERR_OUT_OF_RANGE' }); + } +} + +{ + const h = crypto.createHash('sha512'); + h.digest(); + assert.throws(() => h.copy(), { code: 'ERR_CRYPTO_HASH_FINALIZED' }); + assert.throws(() => h.digest(), { code: 'ERR_CRYPTO_HASH_FINALIZED' }); +} + +{ + const a = crypto.createHash('sha512').update('abc'); + const b = a.copy(); + const c = b.copy().update('def'); + const d = crypto.createHash('sha512').update('abcdef'); + assert.strictEqual(a.digest('hex'), b.digest('hex')); + assert.strictEqual(c.digest('hex'), d.digest('hex')); +} diff --git a/test/crypto/test-crypto-hkdf.js b/test/crypto/test-crypto-hkdf.js new file mode 100644 index 0000000..f9ee5d2 --- /dev/null +++ b/test/crypto/test-crypto-hkdf.js @@ -0,0 +1,227 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; + +import common from '../common'; + +if (!common.hasCrypto) + common.skip('missing crypto'); + +import { kMaxLength } from 'buffer'; +import assert from 'assert'; +import { + createSecretKey, + hkdf, + hkdfSync, + getHashes +} from 'crypto'; + +{ + assert.throws(() => hkdf(), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "digest" argument must be of type string/ + }); + + [1, {}, [], false, Infinity].forEach((i) => { + assert.throws(() => hkdf(i, 'a'), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "digest" argument must be of type string/ + }); + assert.throws(() => hkdfSync(i, 'a'), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "digest" argument must be of type string/ + }); + }); + + [1, {}, [], false, Infinity].forEach((i) => { + assert.throws(() => hkdf('sha256', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "ikm" argument must be / + }); + assert.throws(() => hkdfSync('sha256', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "ikm" argument must be / + }); + }); + + [1, {}, [], false, Infinity].forEach((i) => { + assert.throws(() => hkdf('sha256', 'secret', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "salt" argument must be / + }); + assert.throws(() => hkdfSync('sha256', 'secret', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "salt" argument must be / + }); + }); + + [1, {}, [], false, Infinity].forEach((i) => { + assert.throws(() => hkdf('sha256', 'secret', 'salt', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "info" argument must be / + }); + assert.throws(() => hkdfSync('sha256', 'secret', 'salt', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "info" argument must be / + }); + }); + + ['test', {}, [], false].forEach((i) => { + assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "length" argument must be of type number/ + }); + assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "length" argument must be of type number/ + }); + }); + + assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info', -1), { + code: 'ERR_OUT_OF_RANGE' + }); + assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info', -1), { + code: 'ERR_OUT_OF_RANGE' + }); + assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info', + kMaxLength + 1), { + code: 'ERR_OUT_OF_RANGE' + }); + assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info', + kMaxLength + 1), { + code: 'ERR_OUT_OF_RANGE' + }); + + assert.throws(() => hkdfSync('unknown', 'a', '', '', 10), { + code: 'ERR_CRYPTO_INVALID_DIGEST' + }); + + assert.throws(() => hkdf('unknown', 'a', '', '', 10, common.mustNotCall()), { + code: 'ERR_CRYPTO_INVALID_DIGEST' + }); + + assert.throws(() => hkdf('unknown', 'a', '', Buffer.alloc(1025), 10, + common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' + }); + + assert.throws(() => hkdfSync('unknown', 'a', '', Buffer.alloc(1025), 10), { + code: 'ERR_OUT_OF_RANGE' + }); + + assert.throws( + () => hkdf('sha512', 'a', '', '', 64 * 255 + 1, common.mustNotCall()), { + code: 'ERR_CRYPTO_INVALID_KEYLEN' + }); + + assert.throws( + () => hkdfSync('sha512', 'a', '', '', 64 * 255 + 1), { + code: 'ERR_CRYPTO_INVALID_KEYLEN' + }); +} + +const algorithms = [ + ['sha256', 'secret', 'salt', 'info', 10], + ['sha256', '', '', '', 10], + ['sha256', '', 'salt', '', 10], + ['sha512', 'secret', 'salt', '', 15], +]; +if (!common.hasOpenSSL3) + algorithms.push(['whirlpool', 'secret', '', 'info', 20]); + +algorithms.forEach(([ hash, secret, salt, info, length ]) => { + { + const syncResult = hkdfSync(hash, secret, salt, info, length); + assert(syncResult instanceof ArrayBuffer); + let is_async = false; + hkdf(hash, secret, salt, info, length, + common.mustSucceed((asyncResult) => { + assert(is_async); + assert(asyncResult instanceof ArrayBuffer); + assert.deepStrictEqual(syncResult, asyncResult); + })); + // Keep this after the hkdf call above. This verifies + // that the callback is invoked asynchronously. + is_async = true; + } + + { + const buf_secret = Buffer.from(secret); + const buf_salt = Buffer.from(salt); + const buf_info = Buffer.from(info); + + const syncResult = hkdfSync(hash, buf_secret, buf_salt, buf_info, length); + hkdf(hash, buf_secret, buf_salt, buf_info, length, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(syncResult, asyncResult); + })); + } + + { + const key_secret = createSecretKey(Buffer.from(secret)); + const buf_salt = Buffer.from(salt); + const buf_info = Buffer.from(info); + + const syncResult = hkdfSync(hash, key_secret, buf_salt, buf_info, length); + hkdf(hash, key_secret, buf_salt, buf_info, length, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(syncResult, asyncResult); + })); + } + + { + const ta_secret = new Uint8Array(Buffer.from(secret)); + const ta_salt = new Uint16Array(Buffer.from(salt)); + const ta_info = new Uint32Array(Buffer.from(info)); + + const syncResult = hkdfSync(hash, ta_secret, ta_salt, ta_info, length); + hkdf(hash, ta_secret, ta_salt, ta_info, length, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(syncResult, asyncResult); + })); + } + + { + const ta_secret = new Uint8Array(Buffer.from(secret)); + const ta_salt = new Uint16Array(Buffer.from(salt)); + const ta_info = new Uint32Array(Buffer.from(info)); + + const syncResult = hkdfSync( + hash, + ta_secret.buffer, + ta_salt.buffer, + ta_info.buffer, + length); + hkdf(hash, ta_secret, ta_salt, ta_info, length, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(syncResult, asyncResult); + })); + } + + { + const ta_secret = new Uint8Array(Buffer.from(secret)); + const sa_salt = new SharedArrayBuffer(0); + const sa_info = new SharedArrayBuffer(1); + + const syncResult = hkdfSync( + hash, + ta_secret.buffer, + sa_salt, + sa_info, + length); + hkdf(hash, ta_secret, sa_salt, sa_info, length, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(syncResult, asyncResult); + })); + } +}); + + +if (!common.hasOpenSSL3) { + const kKnownUnsupported = ['shake128', 'shake256']; + getHashes() + .filter((hash) => !kKnownUnsupported.includes(hash)) + .forEach((hash) => { + assert(hkdfSync(hash, 'key', 'salt', 'info', 5)); + }); +} diff --git a/test/crypto/test-crypto-hmac.js b/test/crypto/test-crypto-hmac.js new file mode 100644 index 0000000..3672303 --- /dev/null +++ b/test/crypto/test-crypto-hmac.js @@ -0,0 +1,470 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +import common from '../common'; +if (!common.hasCrypto) + common.skip('missing crypto'); + +import assert from 'assert'; +import crypto from 'crypto'; +import { getHashes } from '../../modules/crypto'; + +{ + const Hmac = crypto.Hmac; + const instance = crypto.Hmac('sha256', 'Node'); + assert(instance instanceof Hmac, 'Hmac is expected to return a new instance' + + ' when called without `new`'); +} + +assert.throws( + () => crypto.createHmac(null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "hmac" argument must be of type string. Received null' + }); + +// This used to segfault. See: https://github.com/nodejs/node/issues/9819 +assert.throws( + () => crypto.createHmac('sha256', 'key').digest({ + toString: () => { throw new Error('boom'); }, + }), + { + name: 'Error', + message: 'boom' + }); + +/*assert.throws( + () => crypto.createHmac('sha1', null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + });*/ + +function testHmac(algo, key, data, expected) { + // FIPS does not support MD5. + if (common.hasFipsCrypto && algo === 'md5') + return; + + // wasi-crypto only support sha256 and sha512 + if (!getHashes().includes(algo)) + return; + + if (!Array.isArray(data)) + data = [data]; + + // If the key is a Buffer, test Hmac with a key object as well. + const keyWrappers = [ + (key) => key, + ...(typeof key === 'string' ? [] : [crypto.createSecretKey]), + ]; + + for (const keyWrapper of keyWrappers) { + const hmac = crypto.createHmac(algo, keyWrapper(key)); + for (const chunk of data) + hmac.update(chunk); + const actual = hmac.digest('hex'); + assert.strictEqual(actual, expected); + } +} + +{ + // Test HMAC with multiple updates. + testHmac('sha1', 'Node', ['some data', 'to hmac'], + '19fd6e1ba73d9ed2224dd5094a71babe85d9a892'); +} + +// Test HMAC (Wikipedia Test Cases) +const wikipedia = [ + { + key: 'key', data: 'The quick brown fox jumps over the lazy dog', + hmac: { // HMACs lifted from Wikipedia. + md5: '80070713463e7749b90c2dc24911e275', + sha1: 'de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9', + sha256: + 'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc' + + '2d1a3cd8' + } + }, + { + key: 'key', data: '', + hmac: { // Intermediate test to help debugging. + md5: '63530468a04e386459855da0063b6596', + sha1: 'f42bb0eeb018ebbd4597ae7213711ec60760843f', + sha256: + '5d5d139563c95b5967b9bd9a8c9b233a9dedb45072794cd232dc1b74' + + '832607d0' + } + }, + { + key: '', data: 'The quick brown fox jumps over the lazy dog', + hmac: { // Intermediate test to help debugging. + md5: 'ad262969c53bc16032f160081c4a07a0', + sha1: '2ba7f707ad5f187c412de3106583c3111d668de8', + sha256: + 'fb011e6154a19b9a4c767373c305275a5a69e8b68b0b4c9200c383dc' + + 'ed19a416' + } + }, + { + key: '', data: '', + hmac: { // HMACs lifted from Wikipedia. + md5: '74e6f7298a9c2d168935f58c001bad88', + sha1: 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', + sha256: + 'b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c71214' + + '4292c5ad' + } + }, +]; + +for (const { key, data, hmac } of wikipedia) { + for (const hash in hmac) + testHmac(hash, key, data, hmac[hash]); +} + +// Test HMAC-SHA-* (rfc 4231 Test Cases) +const rfc4231 = [ + { + key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), + data: Buffer.from('4869205468657265', 'hex'), // 'Hi There' + hmac: { + sha224: '896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22', + sha256: + 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c' + + '2e32cff7', + sha384: + 'afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c' + + '7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6', + sha512: + '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b305' + + '45e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f170' + + '2e696c203a126854' + } + }, + { + key: Buffer.from('4a656665', 'hex'), // 'Jefe' + data: Buffer.from('7768617420646f2079612077616e7420666f72206e6f74686' + + '96e673f', 'hex'), // 'what do ya want for nothing?' + hmac: { + sha224: 'a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44', + sha256: + '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b9' + + '64ec3843', + sha384: + 'af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec373' + + '6322445e8e2240ca5e69e2c78b3239ecfab21649', + sha512: + '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7' + + 'ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b' + + '636e070a38bce737' + } + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), + data: Buffer.from('ddddddddddddddddddddddddddddddddddddddddddddddddd' + + 'ddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'hex'), + hmac: { + sha224: '7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea', + sha256: + '773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514' + + 'ced565fe', + sha384: + '88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e5' + + '5966144b2a5ab39dc13814b94e3ab6e101a34f27', + sha512: + 'fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33' + + 'b2279d39bf3e848279a722c806b485a47e67c807b946a337bee89426' + + '74278859e13292fb' + } + }, + { + key: Buffer.from('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'hex'), + data: Buffer.from('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' + + 'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd', + 'hex'), + hmac: { + sha224: '6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a', + sha256: + '82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff4' + + '6729665b', + sha384: + '3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e' + + '1f573b4e6801dd23c4a7d679ccf8a386c674cffb', + sha512: + 'b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050' + + '361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2d' + + 'e2adebeb10a298dd' + } + }, + + { + key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'), + // 'Test With Truncation' + data: Buffer.from('546573742057697468205472756e636174696f6e', 'hex'), + hmac: { + sha224: '0e2aea68a90c8d37c988bcdb9fca6fa8', + sha256: 'a3b6167473100ee06e0c796c2955552b', + sha384: '3abf34c3503b2a23a46efc619baef897', + sha512: '415fad6271580a531d4179bc891d87a6' + }, + truncate: true + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaa', 'hex'), + // 'Test Using Larger Than Block-Size Key - Hash Key First' + data: Buffer.from('54657374205573696e67204c6172676572205468616e20426' + + 'c6f636b2d53697a65204b6579202d2048617368204b657920' + + '4669727374', 'hex'), + hmac: { + sha224: '95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e', + sha256: + '60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f' + + '0ee37f54', + sha384: + '4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05' + + '033ac4c60c2ef6ab4030fe8296248df163f44952', + sha512: + '80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b0137' + + '83f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec' + + '8b915a985d786598' + } + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaa', 'hex'), + // 'This is a test using a larger than block-size key and a larger ' + + // 'than block-size data. The key needs to be hashed before being ' + + // 'used by the HMAC algorithm.' + data: Buffer.from('5468697320697320612074657374207573696e672061206c6' + + '172676572207468616e20626c6f636b2d73697a65206b6579' + + '20616e642061206c6172676572207468616e20626c6f636b2' + + 'd73697a6520646174612e20546865206b6579206e65656473' + + '20746f20626520686173686564206265666f7265206265696' + + 'e6720757365642062792074686520484d414320616c676f72' + + '6974686d2e', 'hex'), + hmac: { + sha224: '3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1', + sha256: + '9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f5153' + + '5c3a35e2', + sha384: + '6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82' + + '461e99c5a678cc31e799176d3860e6110c46523e', + sha512: + 'e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d' + + '20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de04460' + + '65c97440fa8c6a58' + } + }, +]; + +for (let i = 0, l = rfc4231.length; i < l; i++) { + for (const hash in rfc4231[i].hmac) { + if (!getHashes().includes(hash)) + continue; + const str = crypto.createHmac(hash, rfc4231[i].key); + str.end(rfc4231[i].data); + let strRes = str.read().toString('hex'); + let actual = crypto.createHmac(hash, rfc4231[i].key) + .update(rfc4231[i].data) + .digest('hex'); + if (rfc4231[i].truncate) { + actual = actual.substr(0, 32); // first 128 bits == 32 hex chars + strRes = strRes.substr(0, 32); + } + const expected = rfc4231[i].hmac[hash]; + assert.strictEqual( + actual, + expected, + `Test HMAC-${hash} rfc 4231 case ${i + 1}: ${actual} must be ${expected}` + ); + assert.strictEqual( + actual, + strRes, + `Should get same result from stream (hash: ${hash} and case: ${i + 1})` + + ` => ${actual} must be ${strRes}` + ); + } +} + +// Test HMAC-MD5/SHA1 (rfc 2202 Test Cases) +const rfc2202_md5 = [ + { + key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), + data: 'Hi There', + hmac: '9294727a3638bb1c13f48ef8158bfc9d' + }, + { + key: 'Jefe', + data: 'what do ya want for nothing?', + hmac: '750c783e6ab0b503eaa86e310a5db738' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), + data: Buffer.from('ddddddddddddddddddddddddddddddddddddddddddddddddd' + + 'ddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'hex'), + hmac: '56be34521d144c88dbb8c733f0e8b3f6' + }, + { + key: Buffer.from('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'hex'), + data: Buffer.from('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' + + 'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' + + 'cdcdcdcdcd', + 'hex'), + hmac: '697eaf0aca3a3aea3a75164746ffaa79' + }, + { + key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'), + data: 'Test With Truncation', + hmac: '56461ef2342edc00f9bab995690efd4c' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: 'Test Using Larger Than Block-Size Key - Hash Key First', + hmac: '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: + 'Test Using Larger Than Block-Size Key and Larger Than One ' + + 'Block-Size Data', + hmac: '6f630fad67cda0ee1fb1f562db3aa53e' + }, +]; + +for (const { key, data, hmac } of rfc2202_md5) + testHmac('md5', key, data, hmac); + +const rfc2202_sha1 = [ + { + key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), + data: 'Hi There', + hmac: 'b617318655057264e28bc0b6fb378c8ef146be00' + }, + { + key: 'Jefe', + data: 'what do ya want for nothing?', + hmac: 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), + data: Buffer.from('ddddddddddddddddddddddddddddddddddddddddddddd' + + 'ddddddddddddddddddddddddddddddddddddddddddddd' + + 'dddddddddd', + 'hex'), + hmac: '125d7342b9ac11cd91a39af48aa17b4f63f175d3' + }, + { + key: Buffer.from('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'hex'), + data: Buffer.from('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' + + 'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' + + 'cdcdcdcdcd', + 'hex'), + hmac: '4c9007f4026250c6bc8414f9bf50c86c2d7235da' + }, + { + key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'), + data: 'Test With Truncation', + hmac: '4c1a03424b55e07fe7f27be1d58bb9324a9a5a04' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: 'Test Using Larger Than Block-Size Key - Hash Key First', + hmac: 'aa4ae5e15272d00e95705637ce8a3b55ed402112' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: + 'Test Using Larger Than Block-Size Key and Larger Than One ' + + 'Block-Size Data', + hmac: 'e8e99d0f45237d786d6bbaa7965c7808bbff1a91' + }, +]; + +for (const { key, data, hmac } of rfc2202_sha1) + testHmac('sha1', key, data, hmac); + +assert.strictEqual( + crypto.createHmac('sha256', 'w00t').digest('ucs2'), + crypto.createHmac('sha256', 'w00t').digest().toString('ucs2')); + +// Check initialized -> uninitialized state transition after calling digest(). +/*{ + const expected = + '\u0010\u0041\u0052\u00c5\u00bf\u00dc\u00a0\u007b\u00c6\u0033' + + '\u00ee\u00bd\u0046\u0019\u009f\u0002\u0055\u00c9\u00f4\u009d'; + { + const h = crypto.createHmac('sha1', 'key').update('data'); + assert.deepStrictEqual(h.digest('buffer'), Buffer.from(expected, 'latin1')); + assert.deepStrictEqual(h.digest('buffer'), Buffer.from('')); + } + { + const h = crypto.createHmac('sha1', 'key').update('data'); + assert.strictEqual(h.digest('latin1'), expected); + assert.strictEqual(h.digest('latin1'), ''); + } +} + +// Check initialized -> uninitialized state transition after calling digest(). +// Calls to update() omitted intentionally. +{ + const expected = + '\u00f4\u002b\u00b0\u00ee\u00b0\u0018\u00eb\u00bd\u0045\u0097' + + '\u00ae\u0072\u0013\u0071\u001e\u00c6\u0007\u0060\u0084\u003f'; + { + const h = crypto.createHmac('sha1', 'key'); + assert.deepStrictEqual(h.digest('buffer'), Buffer.from(expected, 'latin1')); + assert.deepStrictEqual(h.digest('buffer'), Buffer.from('')); + } + { + const h = crypto.createHmac('sha1', 'key'); + assert.strictEqual(h.digest('latin1'), expected); + assert.strictEqual(h.digest('latin1'), ''); + } +} + +{ + assert.throws( + () => crypto.createHmac('sha7', 'key'), + /Invalid digest/); +}*/ + +{ + const buf = Buffer.alloc(0); + const keyObject = crypto.createSecretKey(Buffer.alloc(0)); + assert.deepStrictEqual( + crypto.createHmac('sha256', buf).update('foo').digest(), + crypto.createHmac('sha256', keyObject).update('foo').digest(), + ); +} diff --git a/test/crypto/test-crypto-key-objects-messageport.js b/test/crypto/test-crypto-key-objects-messageport.js new file mode 100644 index 0000000..8bb0229 --- /dev/null +++ b/test/crypto/test-crypto-key-objects-messageport.js @@ -0,0 +1,89 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { createSecretKey, generateKeyPairSync, randomBytes } = require('crypto'); +const { createContext } = require('vm'); +const { + MessageChannel, + Worker, + moveMessagePortToContext, + parentPort +} = require('worker_threads'); + +function keyToString(key) { + let ret; + if (key.type === 'secret') { + ret = key.export().toString('hex'); + } else { + ret = key.export({ type: 'pkcs1', format: 'pem' }); + } + return ret; +} + +// Worker threads simply reply with their representation of the received key. +if (process.env.HAS_STARTED_WORKER) { + return parentPort.once('message', ({ key }) => { + parentPort.postMessage(keyToString(key)); + }); +} + +// Don't use isMainThread to allow running this test inside a worker. +process.env.HAS_STARTED_WORKER = 1; + +// The main thread generates keys and passes them to worker threads. +const secretKey = createSecretKey(randomBytes(32)); +const { publicKey, privateKey } = generateKeyPairSync('rsa', { + modulusLength: 1024 +}); + +// Get immutable representations of all keys. +const keys = [secretKey, publicKey, privateKey] + .map((key) => [key, keyToString(key)]); + +for (const [key, repr] of keys) { + { + // Test 1: No context change. + const { port1, port2 } = new MessageChannel(); + + port1.postMessage({ key }); + assert.strictEqual(keyToString(key), repr); + + port2.once('message', common.mustCall(({ key }) => { + assert.strictEqual(keyToString(key), repr); + })); + } + + { + // Test 2: Across threads. + const worker = new Worker(__filename); + worker.once('message', common.mustCall((receivedRepresentation) => { + assert.strictEqual(receivedRepresentation, repr); + })); + worker.on('disconnect', () => console.log('disconnect')); + worker.postMessage({ key }); + } + + { + // Test 3: Across contexts (should not work). + const { port1, port2 } = new MessageChannel(); + const context = createContext(); + const port2moved = moveMessagePortToContext(port2, context); + assert(!(port2moved instanceof Object)); + + // TODO(addaleax): Switch this to a 'messageerror' event once MessagePort + // implements EventTarget fully and in a cross-context manner. + port2moved.onmessageerror = common.mustCall((event) => { + assert.strictEqual(event.data.code, + 'ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE'); + }); + + port2moved.start(); + port1.postMessage({ key }); + port1.close(); + } +} diff --git a/test/crypto/test-crypto-key-objects.js b/test/crypto/test-crypto-key-objects.js new file mode 100644 index 0000000..a8c7636 --- /dev/null +++ b/test/crypto/test-crypto-key-objects.js @@ -0,0 +1,884 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + createCipheriv, + createDecipheriv, + createSign, + createVerify, + createSecretKey, + createPublicKey, + createPrivateKey, + KeyObject, + randomBytes, + publicDecrypt, + publicEncrypt, + privateDecrypt, + privateEncrypt, + getCurves, + generateKeySync, + generateKeyPairSync, +} = require('crypto'); + +const fixtures = require('../common/fixtures'); + +const publicPem = fixtures.readKey('rsa_public.pem', 'ascii'); +const privatePem = fixtures.readKey('rsa_private.pem', 'ascii'); + +const publicDsa = fixtures.readKey('dsa_public_1025.pem', 'ascii'); +const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', + 'ascii'); + +{ + // Attempting to create a key of a wrong type should throw + const TYPE = 'wrong_type'; + + assert.throws(() => new KeyObject(TYPE), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: `The argument 'type' is invalid. Received '${TYPE}'` + }); +} + +{ + // Attempting to create a key with non-object handle should throw + assert.throws(() => new KeyObject('secret', ''), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: + 'The "handle" argument must be of type object. Received type ' + + "string ('')" + }); +} + +{ + assert.throws(() => KeyObject.from('invalid_key'), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: + 'The "key" argument must be an instance of CryptoKey. Received type ' + + "string ('invalid_key')" + }); +} + +{ + const keybuf = randomBytes(32); + const key = createSecretKey(keybuf); + assert.strictEqual(key.type, 'secret'); + assert.strictEqual(key.symmetricKeySize, 32); + assert.strictEqual(key.asymmetricKeyType, undefined); + assert.strictEqual(key.asymmetricKeyDetails, undefined); + + const exportedKey = key.export(); + assert(keybuf.equals(exportedKey)); + + const plaintext = Buffer.from('Hello world', 'utf8'); + + const cipher = createCipheriv('aes-256-ecb', key, null); + const ciphertext = Buffer.concat([ + cipher.update(plaintext), cipher.final(), + ]); + + const decipher = createDecipheriv('aes-256-ecb', key, null); + const deciphered = Buffer.concat([ + decipher.update(ciphertext), decipher.final(), + ]); + + assert(plaintext.equals(deciphered)); +} + +{ + // Passing an existing public key object to createPublicKey should throw. + const publicKey = createPublicKey(publicPem); + assert.throws(() => createPublicKey(publicKey), { + name: 'TypeError', + code: 'ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE', + message: 'Invalid key object type public, expected private.' + }); + + // Constructing a private key from a public key should be impossible, even + // if the public key was derived from a private key. + assert.throws(() => createPrivateKey(createPublicKey(privatePem)), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); + + // Similarly, passing an existing private key object to createPrivateKey + // should throw. + const privateKey = createPrivateKey(privatePem); + assert.throws(() => createPrivateKey(privateKey), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); +} + +{ + const jwk = { + e: 'AQAB', + n: 't9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe' + + '1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4YCytivE24YI0D4XZ' + + 'MPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYSlTIGIhzyaiYBh7wrZBoPczIE' + + 'u6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsF' + + 'di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q', + d: 'ktnq2LvIMqBj4txP82IEOorIRQGVsw1khbm8A-cEpuEkgM71Yi_0WzupKktucUeevQ5i0' + + 'Yh8w9e1SJiTLDRAlJz66kdky9uejiWWl6zR4dyNZVMFYRM43ijLC-P8rPne9Fz16IqHFW' + + '5VbJqA1xCBhKmuPMsD71RNxZ4Hrsa7Kt_xglQTYsLbdGIwDmcZihId9VGXRzvmCPsDRf2' + + 'fCkAj7HDeRxpUdEiEDpajADc-PWikra3r3b40tVHKWm8wxJLivOIN7GiYXKQIW6RhZgH-' + + 'Rk45JIRNKxNagxdeXUqqyhnwhbTo1Hite0iBDexN9tgoZk0XmdYWBn6ElXHRZ7VCDQ', + p: '8UovlB4nrBm7xH-u7XXBMbqxADQm5vaEZxw9eluc-tP7cIAI4sglMIvL_FMpbd2pEeP_B' + + 'kR76NTDzzDuPAZvUGRavgEjy0O9j2NAs_WPK4tZF-vFdunhnSh4EHAF4Ij9kbsUi90NOp' + + 'bGfVqPdOaHqzgHKoR23Cuusk9wFQ2XTV8', + q: 'wxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrWOb-76rSfuL8wGR4OBNmQdhLuU9zTIh22pog-X' + + 'PnLPAecC-4yu_wtJ2SPCKiKDbJBre0CKPyRfGqzvA3njXwMxXazU4kGs-2Fg-xu_iKbaI' + + 'jxXrclBLhkxhBtySrwAFhxxOk6fFcPLSs', + dp: 'qS_Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc_Gh4cnO-b7BNJ_-5L8WZog0vr' + + '6PgiLhrqBaCYm2wjpyoG2o2wDHm-NAlzN_wp3G2EFhrSxdOux-S1c0kpRcyoiAO2n29rN' + + 'Da-jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8', + dq: 'WAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCDdCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP_' + + 'Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNmbDXFPdG0G3hzQovx_9fajiRV4DWghLHeT9wzJ' + + 'fZabRRiI0VQR472300AVEeX4vgbrDBn600', + qi: 'k7czBCT9rHn_PNwCa17hlTy88C4vXkwbz83Oa-aX5L4e5gw5lhcR2ZuZHLb2r6oMt9rl' + + 'D7EIDItSs-u21LOXWPTAlazdnpYUyw_CzogM_PN-qNwMRXn5uXFFhmlP2mVg2EdELTahX' + + 'ch8kWqHaCSX53yvqCtRKu_j76V31TfQZGM', + kty: 'RSA', + }; + const publicJwk = { kty: jwk.kty, e: jwk.e, n: jwk.n }; + + const publicKey = createPublicKey(publicPem); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); + assert.strictEqual(publicKey.symmetricKeySize, undefined); + + const privateKey = createPrivateKey(privatePem); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); + assert.strictEqual(privateKey.symmetricKeySize, undefined); + + // It should be possible to derive a public key from a private key. + const derivedPublicKey = createPublicKey(privateKey); + assert.strictEqual(derivedPublicKey.type, 'public'); + assert.strictEqual(derivedPublicKey.asymmetricKeyType, 'rsa'); + assert.strictEqual(derivedPublicKey.symmetricKeySize, undefined); + + const publicKeyFromJwk = createPublicKey({ key: publicJwk, format: 'jwk' }); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); + assert.strictEqual(publicKey.symmetricKeySize, undefined); + + const privateKeyFromJwk = createPrivateKey({ key: jwk, format: 'jwk' }); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); + assert.strictEqual(privateKey.symmetricKeySize, undefined); + + // It should also be possible to import an encrypted private key as a public + // key. + const decryptedKey = createPublicKey({ + key: privateKey.export({ + type: 'pkcs8', + format: 'pem', + passphrase: '123', + cipher: 'aes-128-cbc' + }), + format: 'pem', + passphrase: '123' + }); + assert.strictEqual(decryptedKey.type, 'public'); + assert.strictEqual(decryptedKey.asymmetricKeyType, 'rsa'); + + // Test exporting with an invalid options object, this should throw. + for (const opt of [undefined, null, 'foo', 0, NaN]) { + assert.throws(() => publicKey.export(opt), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "options" argument must be of type object/ + }); + } + + for (const keyObject of [publicKey, derivedPublicKey, publicKeyFromJwk]) { + assert.deepStrictEqual( + keyObject.export({ format: 'jwk' }), + { kty: 'RSA', n: jwk.n, e: jwk.e } + ); + } + + for (const keyObject of [privateKey, privateKeyFromJwk]) { + assert.deepStrictEqual( + keyObject.export({ format: 'jwk' }), + jwk + ); + } + + // Exporting the key using JWK should not work since this format does not + // support key encryption + assert.throws(() => { + privateKey.export({ format: 'jwk', passphrase: 'secret' }); + }, { + message: 'The selected key encoding jwk does not support encryption.', + code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' + }); + + const publicDER = publicKey.export({ + format: 'der', + type: 'pkcs1' + }); + + const privateDER = privateKey.export({ + format: 'der', + type: 'pkcs1' + }); + + assert(Buffer.isBuffer(publicDER)); + assert(Buffer.isBuffer(privateDER)); + + const plaintext = Buffer.from('Hello world', 'utf8'); + const testDecryption = (fn, ciphertexts, decryptionKeys) => { + for (const ciphertext of ciphertexts) { + for (const key of decryptionKeys) { + const deciphered = fn(key, ciphertext); + assert.deepStrictEqual(deciphered, plaintext); + } + } + }; + + testDecryption(privateDecrypt, [ + // Encrypt using the public key. + publicEncrypt(publicKey, plaintext), + publicEncrypt({ key: publicKey }, plaintext), + publicEncrypt({ key: publicJwk, format: 'jwk' }, plaintext), + + // Encrypt using the private key. + publicEncrypt(privateKey, plaintext), + publicEncrypt({ key: privateKey }, plaintext), + publicEncrypt({ key: jwk, format: 'jwk' }, plaintext), + + // Encrypt using a public key derived from the private key. + publicEncrypt(derivedPublicKey, plaintext), + publicEncrypt({ key: derivedPublicKey }, plaintext), + + // Test distinguishing PKCS#1 public and private keys based on the + // DER-encoded data only. + publicEncrypt({ format: 'der', type: 'pkcs1', key: publicDER }, plaintext), + publicEncrypt({ format: 'der', type: 'pkcs1', key: privateDER }, plaintext), + ], [ + privateKey, + { format: 'pem', key: privatePem }, + { format: 'der', type: 'pkcs1', key: privateDER }, + { key: jwk, format: 'jwk' }, + ]); + + testDecryption(publicDecrypt, [ + privateEncrypt(privateKey, plaintext), + ], [ + // Decrypt using the public key. + publicKey, + { format: 'pem', key: publicPem }, + { format: 'der', type: 'pkcs1', key: publicDER }, + { key: publicJwk, format: 'jwk' }, + + // Decrypt using the private key. + privateKey, + { format: 'pem', key: privatePem }, + { format: 'der', type: 'pkcs1', key: privateDER }, + { key: jwk, format: 'jwk' }, + ]); +} + +{ + // This should not cause a crash: https://github.com/nodejs/node/issues/25247 + assert.throws(() => { + createPrivateKey({ key: '' }); + }, common.hasOpenSSL3 ? { + message: 'error:1E08010C:DECODER routines::unsupported', + } : { + message: 'error:0909006C:PEM routines:get_name:no start line', + code: 'ERR_OSSL_PEM_NO_START_LINE', + reason: 'no start line', + library: 'PEM routines', + function: 'get_name', + }); + + // This should not abort either: https://github.com/nodejs/node/issues/29904 + assert.throws(() => { + createPrivateKey({ key: Buffer.alloc(0), format: 'der', type: 'spki' }); + }, { + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.type' is invalid. Received 'spki'" + }); + + // Unlike SPKI, PKCS#1 is a valid encoding for private keys (and public keys), + // so it should be accepted by createPrivateKey, but OpenSSL won't parse it. + assert.throws(() => { + const key = createPublicKey(publicPem).export({ + format: 'der', + type: 'pkcs1' + }); + createPrivateKey({ key, format: 'der', type: 'pkcs1' }); + }, common.hasOpenSSL3 ? { + message: /error:1E08010C:DECODER routines::unsupported/, + library: 'DECODER routines' + } : { + message: /asn1 encoding/, + library: 'asn1 encoding routines' + }); +} + +[ + { private: fixtures.readKey('ed25519_private.pem', 'ascii'), + public: fixtures.readKey('ed25519_public.pem', 'ascii'), + keyType: 'ed25519', + jwk: { + crv: 'Ed25519', + x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768', + d: 'wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA', + kty: 'OKP' + } }, + { private: fixtures.readKey('ed448_private.pem', 'ascii'), + public: fixtures.readKey('ed448_public.pem', 'ascii'), + keyType: 'ed448', + jwk: { + crv: 'Ed448', + x: 'oX_ee5-jlcU53-BbGRsGIzly0V-SZtJ_oGXY0udf84q2hTW2RdstLktvwpkVJOoNb7o' + + 'Dgc2V5ZUA', + d: '060Ke71sN0GpIc01nnGgMDkp0sFNQ09woVo4AM1ffax1-mjnakK0-p-S7-Xf859QewX' + + 'jcR9mxppY', + kty: 'OKP' + } }, + { private: fixtures.readKey('x25519_private.pem', 'ascii'), + public: fixtures.readKey('x25519_public.pem', 'ascii'), + keyType: 'x25519', + jwk: { + crv: 'X25519', + x: 'aSb8Q-RndwfNnPeOYGYPDUN3uhAPnMLzXyfi-mqfhig', + d: 'mL_IWm55RrALUGRfJYzw40gEYWMvtRkesP9mj8o8Omc', + kty: 'OKP' + } }, + { private: fixtures.readKey('x448_private.pem', 'ascii'), + public: fixtures.readKey('x448_public.pem', 'ascii'), + keyType: 'x448', + jwk: { + crv: 'X448', + x: 'ioHSHVpTs6hMvghosEJDIR7ceFiE3-Xccxati64oOVJ7NWjfozE7ae31PXIUFq6cVYg' + + 'vSKsDFPA', + d: 'tMNtrO_q8dlY6Y4NDeSTxNQ5CACkHiPvmukidPnNIuX_EkcryLEXt_7i6j6YZMKsrWy' + + 'S0jlSYJk', + kty: 'OKP' + } }, +].forEach((info) => { + const keyType = info.keyType; + + { + const key = createPrivateKey(info.private); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.asymmetricKeyType, keyType); + assert.strictEqual(key.symmetricKeySize, undefined); + assert.strictEqual( + key.export({ type: 'pkcs8', format: 'pem' }), info.private); + assert.deepStrictEqual( + key.export({ format: 'jwk' }), info.jwk); + } + + { + const key = createPrivateKey({ key: info.jwk, format: 'jwk' }); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.asymmetricKeyType, keyType); + assert.strictEqual(key.symmetricKeySize, undefined); + assert.strictEqual( + key.export({ type: 'pkcs8', format: 'pem' }), info.private); + assert.deepStrictEqual( + key.export({ format: 'jwk' }), info.jwk); + } + + { + for (const input of [ + info.private, info.public, { key: info.jwk, format: 'jwk' }]) { + const key = createPublicKey(input); + assert.strictEqual(key.type, 'public'); + assert.strictEqual(key.asymmetricKeyType, keyType); + assert.strictEqual(key.symmetricKeySize, undefined); + assert.strictEqual( + key.export({ type: 'spki', format: 'pem' }), info.public); + const jwk = { ...info.jwk }; + delete jwk.d; + assert.deepStrictEqual( + key.export({ format: 'jwk' }), jwk); + } + } +}); + +[ + { private: fixtures.readKey('ec_p256_private.pem', 'ascii'), + public: fixtures.readKey('ec_p256_public.pem', 'ascii'), + keyType: 'ec', + namedCurve: 'prime256v1', + jwk: { + crv: 'P-256', + d: 'DxBsPQPIgMuMyQbxzbb9toew6Ev6e9O6ZhpxLNgmAEo', + kty: 'EC', + x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs', + y: 'UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI' + } }, + { private: fixtures.readKey('ec_secp256k1_private.pem', 'ascii'), + public: fixtures.readKey('ec_secp256k1_public.pem', 'ascii'), + keyType: 'ec', + namedCurve: 'secp256k1', + jwk: { + crv: 'secp256k1', + d: 'c34ocwTwpFa9NZZh3l88qXyrkoYSxvC0FEsU5v1v4IM', + kty: 'EC', + x: 'cOzhFSpWxhalCbWNdP2H_yUkdC81C9T2deDpfxK7owA', + y: '-A3DAZTk9IPppN-f03JydgHaFvL1fAHaoXf4SX4NXyo' + } }, + { private: fixtures.readKey('ec_p384_private.pem', 'ascii'), + public: fixtures.readKey('ec_p384_public.pem', 'ascii'), + keyType: 'ec', + namedCurve: 'secp384r1', + jwk: { + crv: 'P-384', + d: 'dwfuHuAtTlMRn7ZBCBm_0grpc1D_4hPeNAgevgelljuC0--k_LDFosDgBlLLmZsi', + kty: 'EC', + x: 'hON3nzGJgv-08fdHpQxgRJFZzlK-GZDGa5f3KnvM31cvvjJmsj4UeOgIdy3rDAjV', + y: 'fidHhtecNCGCfLqmrLjDena1NSzWzWH1u_oUdMKGo5XSabxzD7-8JZxjpc8sR9cl' + } }, + { private: fixtures.readKey('ec_p521_private.pem', 'ascii'), + public: fixtures.readKey('ec_p521_public.pem', 'ascii'), + keyType: 'ec', + namedCurve: 'secp521r1', + jwk: { + crv: 'P-521', + d: 'ABIIbmn3Gm_Y11uIDkC3g2ijpRxIrJEBY4i_JJYo5OougzTl3BX2ifRluPJMaaHcNer' + + 'bQH_WdVkLLX86ShlHrRyJ', + kty: 'EC', + x: 'AaLFgjwZtznM3N7qsfb86awVXe6c6djUYOob1FN-kllekv0KEXV0bwcDjPGQz5f6MxL' + + 'CbhMeHRavUS6P10rsTtBn', + y: 'Ad3flexBeAfXceNzRBH128kFbOWD6W41NjwKRqqIF26vmgW_8COldGKZjFkOSEASxPB' + + 'cvA2iFJRUyQ3whC00j0Np' + } }, +].forEach((info) => { + const { keyType, namedCurve } = info; + + { + const key = createPrivateKey(info.private); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.asymmetricKeyType, keyType); + assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve }); + assert.strictEqual(key.symmetricKeySize, undefined); + assert.strictEqual( + key.export({ type: 'pkcs8', format: 'pem' }), info.private); + assert.deepStrictEqual( + key.export({ format: 'jwk' }), info.jwk); + } + + { + const key = createPrivateKey({ key: info.jwk, format: 'jwk' }); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.asymmetricKeyType, keyType); + assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve }); + assert.strictEqual(key.symmetricKeySize, undefined); + assert.strictEqual( + key.export({ type: 'pkcs8', format: 'pem' }), info.private); + assert.deepStrictEqual( + key.export({ format: 'jwk' }), info.jwk); + } + + { + for (const input of [ + info.private, info.public, { key: info.jwk, format: 'jwk' }]) { + const key = createPublicKey(input); + assert.strictEqual(key.type, 'public'); + assert.strictEqual(key.asymmetricKeyType, keyType); + assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve }); + assert.strictEqual(key.symmetricKeySize, undefined); + assert.strictEqual( + key.export({ type: 'spki', format: 'pem' }), info.public); + const jwk = { ...info.jwk }; + delete jwk.d; + assert.deepStrictEqual( + key.export({ format: 'jwk' }), jwk); + } + } +}); + +{ + // Reading an encrypted key without a passphrase should fail. + assert.throws(() => createPrivateKey(privateDsa), common.hasOpenSSL3 ? { + name: 'Error', + message: 'error:07880109:common libcrypto routines::interrupted or ' + + 'cancelled', + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + // Reading an encrypted key with a passphrase that exceeds OpenSSL's buffer + // size limit should fail with an appropriate error code. + assert.throws(() => createPrivateKey({ + key: privateDsa, + format: 'pem', + passphrase: Buffer.alloc(1025, 'a') + }), common.hasOpenSSL3 ? { name: 'Error' } : { + code: 'ERR_OSSL_PEM_BAD_PASSWORD_READ', + name: 'Error' + }); + + // The buffer has a size of 1024 bytes, so this passphrase should be permitted + // (but will fail decryption). + assert.throws(() => createPrivateKey({ + key: privateDsa, + format: 'pem', + passphrase: Buffer.alloc(1024, 'a') + }), { + message: /bad decrypt/ + }); + + const publicKey = createPublicKey(publicDsa); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'dsa'); + assert.strictEqual(publicKey.symmetricKeySize, undefined); + assert.throws( + () => publicKey.export({ format: 'jwk' }), + { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' }); + + const privateKey = createPrivateKey({ + key: privateDsa, + format: 'pem', + passphrase: 'secret' + }); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'dsa'); + assert.strictEqual(privateKey.symmetricKeySize, undefined); + assert.throws( + () => privateKey.export({ format: 'jwk' }), + { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' }); +} + +{ + // Test RSA-PSS. + { + // This key pair does not restrict the message digest algorithm or salt + // length. + const publicPem = fixtures.readKey('rsa_pss_public_2048.pem'); + const privatePem = fixtures.readKey('rsa_pss_private_2048.pem'); + + const publicKey = createPublicKey(publicPem); + const privateKey = createPrivateKey(privatePem); + + // Because no RSASSA-PSS-params appears in the PEM, no defaults should be + // added for the PSS parameters. This is different from an empty + // RSASSA-PSS-params sequence (see test below). + const expectedKeyDetails = { + modulusLength: 2048, + publicExponent: 65537n + }; + + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + + assert.throws( + () => publicKey.export({ format: 'jwk' }), + { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' }); + assert.throws( + () => privateKey.export({ format: 'jwk' }), + { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' }); + + for (const key of [privatePem, privateKey]) { + // Any algorithm should work. + for (const algo of ['sha1', 'sha256']) { + // Any salt length should work. + for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) { + const signature = createSign(algo) + .update('foo') + .sign({ key, saltLength }); + + for (const pkey of [key, publicKey, publicPem]) { + const okay = createVerify(algo) + .update('foo') + .verify({ key: pkey, saltLength }, signature); + + assert.ok(okay); + } + } + } + } + + // Exporting the key using PKCS#1 should not work since this would discard + // any algorithm restrictions. + assert.throws(() => { + publicKey.export({ format: 'pem', type: 'pkcs1' }); + }, { + code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' + }); + } + + { + // This key pair enforces sha1 as the message digest and the MGF1 + // message digest and a salt length of 20 bytes. + + const publicPem = fixtures.readKey('rsa_pss_public_2048_sha1_sha1_20.pem'); + const privatePem = + fixtures.readKey('rsa_pss_private_2048_sha1_sha1_20.pem'); + + const publicKey = createPublicKey(publicPem); + const privateKey = createPrivateKey(privatePem); + + // Unlike the previous key pair, this key pair contains an RSASSA-PSS-params + // sequence. However, because all values in the RSASSA-PSS-params are set to + // their defaults (see RFC 3447), the ASN.1 structure contains an empty + // sequence. Node.js should add the default values to the key details. + const expectedKeyDetails = { + modulusLength: 2048, + publicExponent: 65537n, + hashAlgorithm: 'sha1', + mgf1HashAlgorithm: 'sha1', + saltLength: 20 + }; + + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + } + + { + // This key pair enforces sha256 as the message digest and the MGF1 + // message digest and a salt length of at least 16 bytes. + const publicPem = + fixtures.readKey('rsa_pss_public_2048_sha256_sha256_16.pem'); + const privatePem = + fixtures.readKey('rsa_pss_private_2048_sha256_sha256_16.pem'); + + const publicKey = createPublicKey(publicPem); + const privateKey = createPrivateKey(privatePem); + + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + + for (const key of [privatePem, privateKey]) { + // Signing with anything other than sha256 should fail. + assert.throws(() => { + createSign('sha1').sign(key); + }, /digest not allowed/); + + // Signing with salt lengths less than 16 bytes should fail. + for (const saltLength of [8, 10, 12]) { + assert.throws(() => { + createSign('sha1').sign({ key, saltLength }); + }, /pss saltlen too small/); + } + + // Signing with sha256 and appropriate salt lengths should work. + for (const saltLength of [undefined, 16, 18, 20]) { + const signature = createSign('sha256') + .update('foo') + .sign({ key, saltLength }); + + for (const pkey of [key, publicKey, publicPem]) { + const okay = createVerify('sha256') + .update('foo') + .verify({ key: pkey, saltLength }, signature); + + assert.ok(okay); + } + } + } + } + + { + // This key enforces sha512 as the message digest and sha256 as the MGF1 + // message digest. + const publicPem = + fixtures.readKey('rsa_pss_public_2048_sha512_sha256_20.pem'); + const privatePem = + fixtures.readKey('rsa_pss_private_2048_sha512_sha256_20.pem'); + + const publicKey = createPublicKey(publicPem); + const privateKey = createPrivateKey(privatePem); + + const expectedKeyDetails = { + modulusLength: 2048, + publicExponent: 65537n, + hashAlgorithm: 'sha512', + mgf1HashAlgorithm: 'sha256', + saltLength: 20 + }; + + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + + // Node.js usually uses the same hash function for the message and for MGF1. + // However, when a different MGF1 message digest algorithm has been + // specified as part of the key, it should automatically switch to that. + // This behavior is required by sections 3.1 and 3.3 of RFC4055. + for (const key of [privatePem, privateKey]) { + // sha256 matches the MGF1 hash function and should be used internally, + // but it should not be permitted as the main message digest algorithm. + for (const algo of ['sha1', 'sha256']) { + assert.throws(() => { + createSign(algo).sign(key); + }, /digest not allowed/); + } + + // sha512 should produce a valid signature. + const signature = createSign('sha512') + .update('foo') + .sign(key); + + for (const pkey of [key, publicKey, publicPem]) { + const okay = createVerify('sha512') + .update('foo') + .verify(pkey, signature); + + assert.ok(okay); + } + } + } +} + +{ + // Exporting an encrypted private key requires a cipher + const privateKey = createPrivateKey(privatePem); + assert.throws(() => { + privateKey.export({ + format: 'pem', type: 'pkcs8', passphrase: 'super-secret' + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.cipher' is invalid. Received undefined" + }); +} + +{ + // SecretKeyObject export buffer format (default) + const buffer = Buffer.from('Hello World'); + const keyObject = createSecretKey(buffer); + assert.deepStrictEqual(keyObject.export(), buffer); + assert.deepStrictEqual(keyObject.export({}), buffer); + assert.deepStrictEqual(keyObject.export({ format: 'buffer' }), buffer); + assert.deepStrictEqual(keyObject.export({ format: undefined }), buffer); +} + +{ + // Exporting an "oct" JWK from a SecretKeyObject + const buffer = Buffer.from('Hello World'); + const keyObject = createSecretKey(buffer); + assert.deepStrictEqual( + keyObject.export({ format: 'jwk' }), + { kty: 'oct', k: 'SGVsbG8gV29ybGQ' } + ); +} + +{ + // Exporting a JWK unsupported curve EC key + const supported = ['prime256v1', 'secp256k1', 'secp384r1', 'secp521r1']; + // Find an unsupported curve regardless of whether a FIPS compliant crypto + // provider is currently in use. + const namedCurve = getCurves().find((curve) => !supported.includes(curve)); + assert(namedCurve); + const keyPair = generateKeyPairSync('ec', { namedCurve }); + const { publicKey, privateKey } = keyPair; + assert.throws( + () => publicKey.export({ format: 'jwk' }), + { + code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE', + message: `Unsupported JWK EC curve: ${namedCurve}.` + }); + assert.throws( + () => privateKey.export({ format: 'jwk' }), + { + code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE', + message: `Unsupported JWK EC curve: ${namedCurve}.` + }); +} + +{ + const first = Buffer.from('Hello'); + const second = Buffer.from('World'); + const keyObject = createSecretKey(first); + assert(createSecretKey(first).equals(createSecretKey(first))); + assert(!createSecretKey(first).equals(createSecretKey(second))); + + assert.throws(() => keyObject.equals(0), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "otherKeyObject" argument must be an instance of KeyObject. Received type number (0)' + }); + + assert(keyObject.equals(keyObject)); + assert(!keyObject.equals(createPublicKey(publicPem))); + assert(!keyObject.equals(createPrivateKey(privatePem))); +} + +{ + const first = generateKeyPairSync('ed25519'); + const second = generateKeyPairSync('ed25519'); + const secret = generateKeySync('aes', { length: 128 }); + + assert(first.publicKey.equals(first.publicKey)); + assert(first.publicKey.equals(createPublicKey( + first.publicKey.export({ format: 'pem', type: 'spki' })))); + assert(!first.publicKey.equals(second.publicKey)); + assert(!first.publicKey.equals(second.privateKey)); + assert(!first.publicKey.equals(secret)); + + assert(first.privateKey.equals(first.privateKey)); + assert(first.privateKey.equals(createPrivateKey( + first.privateKey.export({ format: 'pem', type: 'pkcs8' })))); + assert(!first.privateKey.equals(second.privateKey)); + assert(!first.privateKey.equals(second.publicKey)); + assert(!first.privateKey.equals(secret)); +} + +{ + const first = generateKeyPairSync('ed25519'); + const second = generateKeyPairSync('ed448'); + + assert(!first.publicKey.equals(second.publicKey)); + assert(!first.publicKey.equals(second.privateKey)); + assert(!first.privateKey.equals(second.privateKey)); + assert(!first.privateKey.equals(second.publicKey)); +} + +{ + const first = createSecretKey(Buffer.alloc(0)); + const second = createSecretKey(new ArrayBuffer(0)); + const third = createSecretKey(Buffer.alloc(1)); + assert(first.equals(first)); + assert(first.equals(second)); + assert(!first.equals(third)); + assert(!third.equals(first)); +} + +{ + // This should not cause a crash: https://github.com/nodejs/node/issues/44471 + for (const key of ['', 'foo', null, undefined, true, Boolean]) { + assert.throws(() => { + createPublicKey({ key, format: 'jwk' }); + }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ }); + assert.throws(() => { + createPrivateKey({ key, format: 'jwk' }); + }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ }); + } +} diff --git a/test/crypto/test-crypto-keygen-deprecation.js b/test/crypto/test-crypto-keygen-deprecation.js new file mode 100644 index 0000000..42c6bd6 --- /dev/null +++ b/test/crypto/test-crypto-keygen-deprecation.js @@ -0,0 +1,53 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// Flags: --pending-deprecation + +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const DeprecationWarning = []; +DeprecationWarning.push([ + '"options.hash" is deprecated, use "options.hashAlgorithm" instead.', + 'DEP0154']); +DeprecationWarning.push([ + '"options.mgf1Hash" is deprecated, use "options.mgf1HashAlgorithm" instead.', + 'DEP0154']); + +common.expectWarning({ DeprecationWarning }); + +const assert = require('assert'); +const { generateKeyPair } = require('crypto'); + +{ + // This test makes sure deprecated options still work as intended + + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hash: 'sha256', + mgf1Hash: 'sha256' + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + })); +} diff --git a/test/crypto/test-crypto-keygen.js b/test/crypto/test-crypto-keygen.js new file mode 100644 index 0000000..52e7586 --- /dev/null +++ b/test/crypto/test-crypto-keygen.js @@ -0,0 +1,1821 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + constants, + createPrivateKey, + createSign, + createVerify, + generateKeyPair, + generateKeyPairSync, + getCurves, + publicEncrypt, + privateDecrypt, + sign, + verify +} = require('crypto'); +const { inspect, promisify } = require('util'); + +// Asserts that the size of the given key (in chars or bytes) is within 10% of +// the expected size. +function assertApproximateSize(key, expectedSize) { + const u = typeof key === 'string' ? 'chars' : 'bytes'; + const min = Math.floor(0.9 * expectedSize); + const max = Math.ceil(1.1 * expectedSize); + assert(key.length >= min, + `Key (${key.length} ${u}) is shorter than expected (${min} ${u})`); + assert(key.length <= max, + `Key (${key.length} ${u}) is longer than expected (${max} ${u})`); +} + +// Tests that a key pair can be used for encryption / decryption. +function testEncryptDecrypt(publicKey, privateKey) { + const message = 'Hello Node.js world!'; + const plaintext = Buffer.from(message, 'utf8'); + for (const key of [publicKey, privateKey]) { + const ciphertext = publicEncrypt(key, plaintext); + const received = privateDecrypt(privateKey, ciphertext); + assert.strictEqual(received.toString('utf8'), message); + } +} + +// Tests that a key pair can be used for signing / verification. +function testSignVerify(publicKey, privateKey) { + const message = Buffer.from('Hello Node.js world!'); + + function oldSign(algo, data, key) { + return createSign(algo).update(data).sign(key); + } + + function oldVerify(algo, data, key, signature) { + return createVerify(algo).update(data).verify(key, signature); + } + + for (const signFn of [sign, oldSign]) { + const signature = signFn('SHA256', message, privateKey); + for (const verifyFn of [verify, oldVerify]) { + for (const key of [publicKey, privateKey]) { + const okay = verifyFn('SHA256', message, key, signature); + assert(okay); + } + } + } +} + +// Constructs a regular expression for a PEM-encoded key with the given label. +function getRegExpForPEM(label, cipher) { + const head = `\\-\\-\\-\\-\\-BEGIN ${label}\\-\\-\\-\\-\\-`; + const rfc1421Header = cipher == null ? '' : + `\nProc-Type: 4,ENCRYPTED\nDEK-Info: ${cipher},[^\n]+\n`; + const body = '([a-zA-Z0-9\\+/=]{64}\n)*[a-zA-Z0-9\\+/=]{1,64}'; + const end = `\\-\\-\\-\\-\\-END ${label}\\-\\-\\-\\-\\-`; + return new RegExp(`^${head}${rfc1421Header}\n${body}\n${end}\n$`); +} + +const pkcs1PubExp = getRegExpForPEM('RSA PUBLIC KEY'); +const pkcs1PrivExp = getRegExpForPEM('RSA PRIVATE KEY'); +const pkcs1EncExp = (cipher) => getRegExpForPEM('RSA PRIVATE KEY', cipher); +const spkiExp = getRegExpForPEM('PUBLIC KEY'); +const pkcs8Exp = getRegExpForPEM('PRIVATE KEY'); +const pkcs8EncExp = getRegExpForPEM('ENCRYPTED PRIVATE KEY'); +const sec1Exp = getRegExpForPEM('EC PRIVATE KEY'); +const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); + +{ + // To make the test faster, we will only test sync key generation once and + // with a relatively small key. + const ret = generateKeyPairSync('rsa', { + publicExponent: 3, + modulusLength: 512, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem' + } + }); + + assert.strictEqual(Object.keys(ret).length, 2); + const { publicKey, privateKey } = ret; + + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, pkcs1PubExp); + assertApproximateSize(publicKey, 162); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, pkcs8Exp); + assertApproximateSize(privateKey, 512); + + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); +} + +{ + // Test sync key generation with key objects with a non-standard + // publicExponent + const { publicKey, privateKey } = generateKeyPairSync('rsa', { + publicExponent: 3, + modulusLength: 512 + }); + + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 3n + }); + + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 3n + }); +} + +{ + // Test sync key generation with key objects. + const { publicKey, privateKey } = generateKeyPairSync('rsa', { + modulusLength: 512 + }); + + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n + }); + + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n + }); +} + +{ + const publicKeyEncoding = { + type: 'pkcs1', + format: 'der' + }; + + // Test async RSA key generation. + generateKeyPair('rsa', { + publicExponent: 0x10001, + modulusLength: 512, + publicKeyEncoding, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } + }, common.mustSucceed((publicKeyDER, privateKey) => { + assert(Buffer.isBuffer(publicKeyDER)); + assertApproximateSize(publicKeyDER, 74); + + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, pkcs1PrivExp); + assertApproximateSize(privateKey, 512); + + const publicKey = { key: publicKeyDER, ...publicKeyEncoding }; + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); + + // Now do the same with an encrypted private key. + generateKeyPair('rsa', { + publicExponent: 0x10001, + modulusLength: 512, + publicKeyEncoding, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem', + cipher: 'aes-256-cbc', + passphrase: 'secret' + } + }, common.mustSucceed((publicKeyDER, privateKey) => { + assert(Buffer.isBuffer(publicKeyDER)); + assertApproximateSize(publicKeyDER, 74); + + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, pkcs1EncExp('AES-256-CBC')); + + // Since the private key is encrypted, signing shouldn't work anymore. + const publicKey = { key: publicKeyDER, ...publicKeyEncoding }; + const expectedError = common.hasOpenSSL3 ? { + name: 'Error', + message: 'error:07880109:common libcrypto routines::interrupted or ' + + 'cancelled' + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }; + assert.throws(() => testSignVerify(publicKey, privateKey), expectedError); + + const key = { key: privateKey, passphrase: 'secret' }; + testEncryptDecrypt(publicKey, key); + testSignVerify(publicKey, key); + })); + + // Now do the same with an encrypted private key, but encoded as DER. + generateKeyPair('rsa', { + publicExponent: 0x10001, + modulusLength: 512, + publicKeyEncoding, + privateKeyEncoding: { + type: 'pkcs8', + format: 'der', + cipher: 'aes-256-cbc', + passphrase: 'secret' + } + }, common.mustSucceed((publicKeyDER, privateKeyDER) => { + assert(Buffer.isBuffer(publicKeyDER)); + assertApproximateSize(publicKeyDER, 74); + + assert(Buffer.isBuffer(privateKeyDER)); + + // Since the private key is encrypted, signing shouldn't work anymore. + const publicKey = { key: publicKeyDER, ...publicKeyEncoding }; + assert.throws(() => { + testSignVerify(publicKey, { + key: privateKeyDER, + format: 'der', + type: 'pkcs8' + }); + }, { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + // Signing should work with the correct password. + + const privateKey = { + key: privateKeyDER, + format: 'der', + type: 'pkcs8', + passphrase: 'secret' + }; + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); + + // Now do the same with an encrypted private key, but encoded as DER. + generateKeyPair('rsa', { + publicExponent: 0x10001, + modulusLength: 512, + publicKeyEncoding, + privateKeyEncoding: { + type: 'pkcs8', + format: 'der' + } + }, common.mustSucceed((publicKeyDER, privateKeyDER) => { + assert(Buffer.isBuffer(publicKeyDER)); + assertApproximateSize(publicKeyDER, 74); + + assert(Buffer.isBuffer(privateKeyDER)); + + const publicKey = { key: publicKeyDER, ...publicKeyEncoding }; + const privateKey = { + key: privateKeyDER, + format: 'der', + type: 'pkcs8', + passphrase: 'secret' + }; + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); +} + +{ + // Test RSA-PSS. + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256' + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + + // Unlike RSA, RSA-PSS does not allow encryption. + assert.throws(() => { + testEncryptDecrypt(publicKey, privateKey); + }, /operation not supported for this keytype/); + + // RSA-PSS also does not permit signing with PKCS1 padding. + assert.throws(() => { + testSignVerify({ + key: publicKey, + padding: constants.RSA_PKCS1_PADDING + }, { + key: privateKey, + padding: constants.RSA_PKCS1_PADDING + }); + }, /illegal or unsupported padding mode/); + + // The padding should correctly default to RSA_PKCS1_PSS_PADDING now. + testSignVerify(publicKey, privateKey); + })); +} + +{ + // 'rsa-pss' should not add a RSASSA-PSS-params sequence by default. + // Regression test for: https://github.com/nodejs/node/issues/39936 + + generateKeyPair('rsa-pss', { + modulusLength: 512 + }, common.mustSucceed((publicKey, privateKey) => { + const expectedKeyDetails = { + modulusLength: 512, + publicExponent: 65537n + }; + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + + // To allow backporting the fix to versions that do not support + // asymmetricKeyDetails for RSA-PSS params, also verify that the exported + // AlgorithmIdentifier member of the SubjectPublicKeyInfo has the expected + // length of 11 bytes (as opposed to > 11 bytes if node added params). + const spki = publicKey.export({ format: 'der', type: 'spki' }); + assert.strictEqual(spki[3], 11, spki.toString('hex')); + })); +} + +{ + // RFC 8017, 9.1.: "Assuming that the mask generation function is based on a + // hash function, it is RECOMMENDED that the hash function be the same as the + // one that is applied to the message." + + generateKeyPair('rsa-pss', { + modulusLength: 512, + hashAlgorithm: 'sha256', + saltLength: 16 + }, common.mustSucceed((publicKey, privateKey) => { + const expectedKeyDetails = { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }; + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + })); +} + +{ + // RFC 8017, A.2.3.: "For a given hashAlgorithm, the default value of + // saltLength is the octet length of the hash value." + + generateKeyPair('rsa-pss', { + modulusLength: 512, + hashAlgorithm: 'sha512' + }, common.mustSucceed((publicKey, privateKey) => { + const expectedKeyDetails = { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha512', + mgf1HashAlgorithm: 'sha512', + saltLength: 64 + }; + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + })); + + // It is still possible to explicitly set saltLength to 0. + generateKeyPair('rsa-pss', { + modulusLength: 512, + hashAlgorithm: 'sha512', + saltLength: 0 + }, common.mustSucceed((publicKey, privateKey) => { + const expectedKeyDetails = { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha512', + mgf1HashAlgorithm: 'sha512', + saltLength: 0 + }; + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + })); +} + +{ + const privateKeyEncoding = { + type: 'pkcs8', + format: 'der' + }; + + // Test async DSA key generation. + generateKeyPair('dsa', { + modulusLength: common.hasOpenSSL3 ? 2048 : 512, + divisorLength: 256, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + cipher: 'aes-128-cbc', + passphrase: 'secret', + ...privateKeyEncoding + } + }, common.mustSucceed((publicKey, privateKeyDER) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + // The private key is DER-encoded. + assert(Buffer.isBuffer(privateKeyDER)); + + assertApproximateSize(publicKey, common.hasOpenSSL3 ? 1194 : 440); + assertApproximateSize(privateKeyDER, common.hasOpenSSL3 ? 721 : 336); + + // Since the private key is encrypted, signing shouldn't work anymore. + assert.throws(() => { + return testSignVerify(publicKey, { + key: privateKeyDER, + ...privateKeyEncoding + }); + }, { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + // Signing should work with the correct password. + testSignVerify(publicKey, { + key: privateKeyDER, + ...privateKeyEncoding, + passphrase: 'secret' + }); + })); +} +{ + // Test async DSA key object generation. + generateKeyPair('dsa', { + modulusLength: common.hasOpenSSL3 ? 2048 : 512, + divisorLength: 256 + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'dsa'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: common.hasOpenSSL3 ? 2048 : 512, + divisorLength: 256 + }); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'dsa'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: common.hasOpenSSL3 ? 2048 : 512, + divisorLength: 256 + }); + })); +} + +{ + // Test async elliptic curve key generation, e.g. for ECDSA, with a SEC1 + // private key. + generateKeyPair('ec', { + namedCurve: 'prime256v1', + paramEncoding: 'named', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'sec1', + format: 'pem' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, sec1Exp); + + testSignVerify(publicKey, privateKey); + })); + + // Test async elliptic curve key generation, e.g. for ECDSA, with a SEC1 + // private key with paramEncoding explicit. + generateKeyPair('ec', { + namedCurve: 'prime256v1', + paramEncoding: 'explicit', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'sec1', + format: 'pem' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, sec1Exp); + + testSignVerify(publicKey, privateKey); + })); + + // Do the same with an encrypted private key. + generateKeyPair('ec', { + namedCurve: 'prime256v1', + paramEncoding: 'named', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'sec1', + format: 'pem', + cipher: 'aes-128-cbc', + passphrase: 'secret' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, sec1EncExp('AES-128-CBC')); + + // Since the private key is encrypted, signing shouldn't work anymore. + assert.throws(() => testSignVerify(publicKey, privateKey), + common.hasOpenSSL3 ? { + message: 'error:07880109:common libcrypto ' + + 'routines::interrupted or cancelled' + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + testSignVerify(publicKey, { key: privateKey, passphrase: 'secret' }); + })); + + // Do the same with an encrypted private key with paramEncoding explicit. + generateKeyPair('ec', { + namedCurve: 'prime256v1', + paramEncoding: 'explicit', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'sec1', + format: 'pem', + cipher: 'aes-128-cbc', + passphrase: 'secret' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, sec1EncExp('AES-128-CBC')); + + // Since the private key is encrypted, signing shouldn't work anymore. + assert.throws(() => testSignVerify(publicKey, privateKey), + common.hasOpenSSL3 ? { + message: 'error:07880109:common libcrypto ' + + 'routines::interrupted or cancelled' + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + testSignVerify(publicKey, { key: privateKey, passphrase: 'secret' }); + })); +} + +{ + // Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted + // private key. + generateKeyPair('ec', { + namedCurve: 'P-256', + paramEncoding: 'named', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + cipher: 'aes-128-cbc', + passphrase: 'top secret' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, pkcs8EncExp); + + // Since the private key is encrypted, signing shouldn't work anymore. + assert.throws(() => testSignVerify(publicKey, privateKey), + common.hasOpenSSL3 ? { + message: 'error:07880109:common libcrypto ' + + 'routines::interrupted or cancelled' + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + testSignVerify(publicKey, { + key: privateKey, + passphrase: 'top secret' + }); + })); + + // Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted + // private key with paramEncoding explicit. + generateKeyPair('ec', { + namedCurve: 'P-256', + paramEncoding: 'explicit', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + cipher: 'aes-128-cbc', + passphrase: 'top secret' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, pkcs8EncExp); + + // Since the private key is encrypted, signing shouldn't work anymore. + assert.throws(() => testSignVerify(publicKey, privateKey), + common.hasOpenSSL3 ? { + message: 'error:07880109:common libcrypto ' + + 'routines::interrupted or cancelled' + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + testSignVerify(publicKey, { + key: privateKey, + passphrase: 'top secret' + }); + })); + + // Test async elliptic curve key generation with 'jwk' encoding + [ + ['ec', ['P-384', 'P-256', 'P-521', 'secp256k1']], + ['rsa'], + ['ed25519'], + ['ed448'], + ['x25519'], + ['x448'], + ].forEach((types) => { + const [type, options] = types; + switch (type) { + case 'ec': { + return options.forEach((curve) => { + generateKeyPair(type, { + namedCurve: curve, + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(publicKey.x, privateKey.x); + assert.strictEqual(publicKey.y, privateKey.y); + assert(!publicKey.d); + assert(privateKey.d); + assert.strictEqual(publicKey.kty, 'EC'); + assert.strictEqual(publicKey.kty, privateKey.kty); + assert.strictEqual(publicKey.crv, curve); + assert.strictEqual(publicKey.crv, privateKey.crv); + })); + }); + } + case 'rsa': { + return generateKeyPair(type, { + modulusLength: 4096, + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(publicKey.kty, 'RSA'); + assert.strictEqual(publicKey.kty, privateKey.kty); + assert.strictEqual(typeof publicKey.n, 'string'); + assert.strictEqual(publicKey.n, privateKey.n); + assert.strictEqual(typeof publicKey.e, 'string'); + assert.strictEqual(publicKey.e, privateKey.e); + assert.strictEqual(typeof privateKey.d, 'string'); + assert.strictEqual(typeof privateKey.p, 'string'); + assert.strictEqual(typeof privateKey.q, 'string'); + assert.strictEqual(typeof privateKey.dp, 'string'); + assert.strictEqual(typeof privateKey.dq, 'string'); + assert.strictEqual(typeof privateKey.qi, 'string'); + })); + } + case 'ed25519': + case 'ed448': + case 'x25519': + case 'x448': { + generateKeyPair(type, { + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(publicKey.x, privateKey.x); + assert(!publicKey.d); + assert(privateKey.d); + assert.strictEqual(publicKey.kty, 'OKP'); + assert.strictEqual(publicKey.kty, privateKey.kty); + const expectedCrv = `${type.charAt(0).toUpperCase()}${type.slice(1)}`; + assert.strictEqual(publicKey.crv, expectedCrv); + assert.strictEqual(publicKey.crv, privateKey.crv); + })); + } + } + }); +} + +// Test invalid parameter encoding. +{ + assert.throws(() => generateKeyPairSync('ec', { + namedCurve: 'P-256', + paramEncoding: 'otherEncoding', + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + cipher: 'aes-128-cbc', + passphrase: 'top secret' + } + }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.paramEncoding' is invalid. " + + "Received 'otherEncoding'" + }); + assert.throws(() => generateKeyPairSync('dsa', { + modulusLength: 4096, + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }), { + name: 'Error', + code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', + message: 'Unsupported JWK Key Type.' + }); + assert.throws(() => generateKeyPairSync('ec', { + namedCurve: 'secp224r1', + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }), { + name: 'Error', + code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE', + message: 'Unsupported JWK EC curve: secp224r1.' + }); +} + +{ + // Test the util.promisified API with async RSA key generation. + promisify(generateKeyPair)('rsa', { + publicExponent: 0x10001, + modulusLength: 512, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } + }).then(common.mustCall((keys) => { + const { publicKey, privateKey } = keys; + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, pkcs1PubExp); + assertApproximateSize(publicKey, 180); + + assert.strictEqual(typeof privateKey, 'string'); + assert.match(privateKey, pkcs1PrivExp); + assertApproximateSize(privateKey, 512); + + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); +} + +{ + // Test invalid key types. + for (const type of [undefined, null, 0]) { + assert.throws(() => generateKeyPairSync(type, {}), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "type" argument must be of type string.' + + common.invalidArgTypeHelper(type) + }); + } + + assert.throws(() => generateKeyPairSync('rsa2', {}), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The argument 'type' must be a supported key type. Received 'rsa2'" + }); +} + +{ + // Test keygen without options object. + assert.throws(() => generateKeyPair('rsa', common.mustNotCall()), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options" argument must be of type object. ' + + 'Received undefined' + }); + + // Even if no options are required, it should be impossible to pass anything + // but an object (or undefined). + assert.throws(() => generateKeyPair('ed448', 0, common.mustNotCall()), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options" argument must be of type object. ' + + 'Received type number (0)' + }); +} + +{ + // If no publicKeyEncoding is specified, a key object should be returned. + generateKeyPair('rsa', { + modulusLength: 1024, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); + + // The private key should still be a string. + assert.strictEqual(typeof privateKey, 'string'); + + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); + + // If no privateKeyEncoding is specified, a key object should be returned. + generateKeyPair('rsa', { + modulusLength: 1024, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } + }, common.mustSucceed((publicKey, privateKey) => { + // The public key should still be a string. + assert.strictEqual(typeof publicKey, 'string'); + + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); + + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); +} + +{ + // Invalid publicKeyEncoding. + for (const enc of [0, 'a', true]) { + assert.throws(() => generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: enc, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } + }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.publicKeyEncoding' is invalid. " + + `Received ${inspect(enc)}` + }); + } + + // Missing publicKeyEncoding.type. + for (const type of [undefined, null, 0, true, {}]) { + assert.throws(() => generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type, + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } + }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.publicKeyEncoding.type' is invalid. " + + `Received ${inspect(type)}` + }); + } + + // Missing / invalid publicKeyEncoding.format. + for (const format of [undefined, null, 0, false, 'a', {}]) { + assert.throws(() => generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'pkcs1', + format + }, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } + }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.publicKeyEncoding.format' is invalid. " + + `Received ${inspect(format)}` + }); + } + + // Invalid privateKeyEncoding. + for (const enc of [0, 'a', true]) { + assert.throws(() => generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: enc + }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.privateKeyEncoding' is invalid. " + + `Received ${inspect(enc)}` + }); + } + + // Missing / invalid privateKeyEncoding.type. + for (const type of [undefined, null, 0, true, {}]) { + assert.throws(() => generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type, + format: 'pem' + } + }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.privateKeyEncoding.type' is invalid. " + + `Received ${inspect(type)}` + }); + } + + // Missing / invalid privateKeyEncoding.format. + for (const format of [undefined, null, 0, false, 'a', {}]) { + assert.throws(() => generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs1', + format + } + }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.privateKeyEncoding.format' is invalid. " + + `Received ${inspect(format)}` + }); + } + + // Cipher of invalid type. + for (const cipher of [0, true, {}]) { + assert.throws(() => generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem', + cipher + } + }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.privateKeyEncoding.cipher' is invalid. " + + `Received ${inspect(cipher)}` + }); + } + + // Invalid cipher. + assert.throws(() => generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + cipher: 'foo', + passphrase: 'secret' + } + }), { + name: 'Error', + code: 'ERR_CRYPTO_UNKNOWN_CIPHER', + message: 'Unknown cipher' + }); + + // Cipher, but no valid passphrase. + for (const passphrase of [undefined, null, 5, false, true]) { + assert.throws(() => generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + cipher: 'aes-128-cbc', + passphrase + } + }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.privateKeyEncoding.passphrase' " + + `is invalid. Received ${inspect(passphrase)}` + }); + } + + // Test invalid callbacks. + for (const cb of [undefined, null, 0, {}]) { + assert.throws(() => generateKeyPair('rsa', { + modulusLength: 512, + publicKeyEncoding: { type: 'pkcs1', format: 'pem' }, + privateKeyEncoding: { type: 'pkcs1', format: 'pem' } + }, cb), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE' + }); + } +} + +// Test RSA parameters. +{ + // Test invalid modulus lengths. (non-number) + for (const modulusLength of [undefined, null, 'a', true, {}, []]) { + assert.throws(() => generateKeyPair('rsa', { + modulusLength + }, common.mustNotCall()), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: + 'The "options.modulusLength" property must be of type number.' + + common.invalidArgTypeHelper(modulusLength) + }); + } + + // Test invalid modulus lengths. (non-integer) + for (const modulusLength of [512.1, 1.3, 1.1, 5000.9, 100.5]) { + assert.throws(() => generateKeyPair('rsa', { + modulusLength + }, common.mustNotCall()), { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + message: + 'The value of "options.modulusLength" is out of range. ' + + 'It must be an integer. ' + + `Received ${inspect(modulusLength)}` + }); + } + + // Test invalid modulus lengths. (out of range) + for (const modulusLength of [-1, -9, 4294967297]) { + assert.throws(() => generateKeyPair('rsa', { + modulusLength + }, common.mustNotCall()), { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + }); + } + + // Test invalid exponents. (non-number) + for (const publicExponent of ['a', true, {}, []]) { + assert.throws(() => generateKeyPair('rsa', { + modulusLength: 4096, + publicExponent + }, common.mustNotCall()), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: + 'The "options.publicExponent" property must be of type number.' + + common.invalidArgTypeHelper(publicExponent) + }); + } + + // Test invalid exponents. (non-integer) + for (const publicExponent of [3.5, 1.1, 50.5, 510.5]) { + assert.throws(() => generateKeyPair('rsa', { + modulusLength: 4096, + publicExponent + }, common.mustNotCall()), { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + message: + 'The value of "options.publicExponent" is out of range. ' + + 'It must be an integer. ' + + `Received ${inspect(publicExponent)}` + }); + } + + // Test invalid exponents. (out of range) + for (const publicExponent of [-5, -3, 4294967297]) { + assert.throws(() => generateKeyPair('rsa', { + modulusLength: 4096, + publicExponent + }, common.mustNotCall()), { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + }); + } +} + +// Test DSA parameters. +{ + // Test invalid modulus lengths. (non-number) + for (const modulusLength of [undefined, null, 'a', true, {}, []]) { + assert.throws(() => generateKeyPair('dsa', { + modulusLength + }, common.mustNotCall()), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: + 'The "options.modulusLength" property must be of type number.' + + common.invalidArgTypeHelper(modulusLength) + }); + } + + // Test invalid modulus lengths. (non-integer) + for (const modulusLength of [512.1, 1.3, 1.1, 5000.9, 100.5]) { + assert.throws(() => generateKeyPair('dsa', { + modulusLength + }, common.mustNotCall()), { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + }); + } + + // Test invalid modulus lengths. (out of range) + for (const modulusLength of [-1, -9, 4294967297]) { + assert.throws(() => generateKeyPair('dsa', { + modulusLength + }, common.mustNotCall()), { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + }); + } + + // Test invalid divisor lengths. (non-number) + for (const divisorLength of ['a', true, {}, []]) { + assert.throws(() => generateKeyPair('dsa', { + modulusLength: 2048, + divisorLength + }, common.mustNotCall()), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: + 'The "options.divisorLength" property must be of type number.' + + common.invalidArgTypeHelper(divisorLength) + }); + } + + // Test invalid divisor lengths. (non-integer) + for (const divisorLength of [4096.1, 5.1, 6.9, 9.5]) { + assert.throws(() => generateKeyPair('dsa', { + modulusLength: 2048, + divisorLength + }, common.mustNotCall()), { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + message: + 'The value of "options.divisorLength" is out of range. ' + + 'It must be an integer. ' + + `Received ${inspect(divisorLength)}` + }); + } + + // Test invalid divisor lengths. (out of range) + for (const divisorLength of [-6, -9, 2147483648]) { + assert.throws(() => generateKeyPair('dsa', { + modulusLength: 2048, + divisorLength + }, common.mustNotCall()), { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + message: + 'The value of "options.divisorLength" is out of range. ' + + 'It must be >= 0 && <= 2147483647. ' + + `Received ${inspect(divisorLength)}` + }); + } +} + +// Test EC parameters. +{ + // Test invalid curves. + assert.throws(() => { + generateKeyPairSync('ec', { + namedCurve: 'abcdef', + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'sec1', format: 'pem' } + }); + }, { + name: 'TypeError', + message: 'Invalid EC curve name' + }); + + // Test error type when curve is not a string + for (const namedCurve of [true, {}, [], 123]) { + assert.throws(() => { + generateKeyPairSync('ec', { + namedCurve, + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'sec1', format: 'pem' } + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: + 'The "options.namedCurve" property must be of type string.' + + common.invalidArgTypeHelper(namedCurve) + }); + } + + // It should recognize both NIST and standard curve names. + generateKeyPair('ec', { + namedCurve: 'P-256', + }, common.mustSucceed((publicKey, privateKey) => { + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + namedCurve: 'prime256v1' + }); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + namedCurve: 'prime256v1' + }); + })); + + generateKeyPair('ec', { + namedCurve: 'secp256k1', + }, common.mustSucceed((publicKey, privateKey) => { + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + namedCurve: 'secp256k1' + }); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + namedCurve: 'secp256k1' + }); + })); +} + +// Test EdDSA key generation. +{ + if (!/^1\.1\.0/.test(process.versions.openssl)) { + ['ed25519', 'ed448', 'x25519', 'x448'].forEach((keyType) => { + generateKeyPair(keyType, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, keyType); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {}); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, keyType); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {}); + })); + }); + } +} + +// Test classic Diffie-Hellman key generation. +{ + generateKeyPair('dh', { + primeLength: 1024 + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'dh'); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'dh'); + })); + + assert.throws(() => { + generateKeyPair('dh', common.mustNotCall()); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options" argument must be of type object. Received undefined' + }); + + assert.throws(() => { + generateKeyPair('dh', {}, common.mustNotCall()); + }, { + name: 'TypeError', + code: 'ERR_MISSING_OPTION', + message: 'At least one of the group, prime, or primeLength options is ' + + 'required' + }); + + assert.throws(() => { + generateKeyPair('dh', { + group: 'modp0' + }, common.mustNotCall()); + }, { + name: 'Error', + code: 'ERR_CRYPTO_UNKNOWN_DH_GROUP', + message: 'Unknown DH group' + }); + + assert.throws(() => { + generateKeyPair('dh', { + primeLength: 2147483648 + }, common.mustNotCall()); + }, { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "options.primeLength" is out of range. ' + + 'It must be >= 0 && <= 2147483647. ' + + 'Received 2147483648', + }); + + assert.throws(() => { + generateKeyPair('dh', { + primeLength: -1 + }, common.mustNotCall()); + }, { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "options.primeLength" is out of range. ' + + 'It must be >= 0 && <= 2147483647. ' + + 'Received -1', + }); + + assert.throws(() => { + generateKeyPair('dh', { + primeLength: 2, + generator: 2147483648, + }, common.mustNotCall()); + }, { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "options.generator" is out of range. ' + + 'It must be >= 0 && <= 2147483647. ' + + 'Received 2147483648', + }); + + assert.throws(() => { + generateKeyPair('dh', { + primeLength: 2, + generator: -1, + }, common.mustNotCall()); + }, { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "options.generator" is out of range. ' + + 'It must be >= 0 && <= 2147483647. ' + + 'Received -1', + }); + + // Test incompatible options. + const allOpts = { + group: 'modp5', + prime: Buffer.alloc(0), + primeLength: 1024, + generator: 2 + }; + const incompatible = [ + ['group', 'prime'], + ['group', 'primeLength'], + ['group', 'generator'], + ['prime', 'primeLength'], + ]; + for (const [opt1, opt2] of incompatible) { + assert.throws(() => { + generateKeyPairSync('dh', { + [opt1]: allOpts[opt1], + [opt2]: allOpts[opt2] + }); + }, { + name: 'TypeError', + code: 'ERR_INCOMPATIBLE_OPTION_PAIR', + message: `Option "${opt1}" cannot be used in combination with option ` + + `"${opt2}"` + }); + } +} + +// Test invalid key encoding types. +{ + // Invalid public key type. + for (const type of ['foo', 'pkcs8', 'sec1']) { + assert.throws(() => { + generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { type, format: 'pem' }, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' } + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.publicKeyEncoding.type' is invalid. " + + `Received ${inspect(type)}` + }); + } + + // Invalid hash value. + for (const hashValue of [123, true, {}, []]) { + assert.throws(() => { + generateKeyPairSync('rsa-pss', { + modulusLength: 4096, + hashAlgorithm: hashValue + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: + 'The "options.hashAlgorithm" property must be of type string.' + + common.invalidArgTypeHelper(hashValue) + }); + } + + // too long salt length + assert.throws(() => { + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 2147483648, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256' + }, common.mustNotCall()); + }, { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "options.saltLength" is out of range. ' + + 'It must be >= 0 && <= 2147483647. ' + + 'Received 2147483648' + }); + + assert.throws(() => { + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: -1, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256' + }, common.mustNotCall()); + }, { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "options.saltLength" is out of range. ' + + 'It must be >= 0 && <= 2147483647. ' + + 'Received -1' + }); + + // Invalid private key type. + for (const type of ['foo', 'spki']) { + assert.throws(() => { + generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type, format: 'pem' } + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.privateKeyEncoding.type' is invalid. " + + `Received ${inspect(type)}` + }); + } + + // Key encoding doesn't match key type. + for (const type of ['dsa', 'ec']) { + assert.throws(() => { + generateKeyPairSync(type, { + modulusLength: 4096, + namedCurve: 'P-256', + publicKeyEncoding: { type: 'pkcs1', format: 'pem' }, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' } + }); + }, { + name: 'Error', + code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS', + message: 'The selected key encoding pkcs1 can only be used for RSA keys.' + }); + + assert.throws(() => { + generateKeyPairSync(type, { + modulusLength: 4096, + namedCurve: 'P-256', + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'pkcs1', format: 'pem' } + }); + }, { + name: 'Error', + code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS', + message: 'The selected key encoding pkcs1 can only be used for RSA keys.' + }); + } + + for (const type of ['rsa', 'dsa']) { + assert.throws(() => { + generateKeyPairSync(type, { + modulusLength: 4096, + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'sec1', format: 'pem' } + }); + }, { + name: 'Error', + code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS', + message: 'The selected key encoding sec1 can only be used for EC keys.' + }); + } + + // Attempting to encrypt a DER-encoded, non-PKCS#8 key. + for (const type of ['pkcs1', 'sec1']) { + assert.throws(() => { + generateKeyPairSync(type === 'pkcs1' ? 'rsa' : 'ec', { + modulusLength: 4096, + namedCurve: 'P-256', + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { + type, + format: 'der', + cipher: 'aes-128-cbc', + passphrase: 'hello' + } + }); + }, { + name: 'Error', + code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS', + message: `The selected key encoding ${type} does not support encryption.` + }); + } +} + +{ + // Test RSA-PSS. + assert.throws( + () => { + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: undefined + }); + }, + { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + } + ); + + for (const mgf1HashAlgorithm of [null, 0, false, {}, []]) { + assert.throws( + () => { + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm + }, common.mustNotCall()); + }, + { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: + 'The "options.mgf1HashAlgorithm" property must be of type string.' + + common.invalidArgTypeHelper(mgf1HashAlgorithm) + + } + ); + } + + assert.throws(() => generateKeyPair('rsa-pss', { + modulusLength: 512, + hashAlgorithm: 'sha2', + }, common.mustNotCall()), { + name: 'TypeError', + code: 'ERR_CRYPTO_INVALID_DIGEST', + message: 'Invalid digest: sha2' + }); + + assert.throws(() => generateKeyPair('rsa-pss', { + modulusLength: 512, + mgf1HashAlgorithm: 'sha2', + }, common.mustNotCall()), { + name: 'TypeError', + code: 'ERR_CRYPTO_INVALID_DIGEST', + message: 'Invalid MGF1 digest: sha2' + }); +} + +// Passing an empty passphrase string should not cause OpenSSL's default +// passphrase prompt in the terminal. +// See https://github.com/nodejs/node/issues/35898. + +for (const type of ['pkcs1', 'pkcs8']) { + generateKeyPair('rsa', { + modulusLength: 1024, + privateKeyEncoding: { + type, + format: 'pem', + cipher: 'aes-256-cbc', + passphrase: '' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + + for (const passphrase of ['', Buffer.alloc(0)]) { + const privateKeyObject = createPrivateKey({ + passphrase, + key: privateKey + }); + assert.strictEqual(privateKeyObject.asymmetricKeyType, 'rsa'); + } + + // Encrypting with an empty passphrase is not the same as not encrypting + // the key, and not specifying a passphrase should fail when decoding it. + assert.throws(() => { + return testSignVerify(publicKey, privateKey); + }, common.hasOpenSSL3 ? { + name: 'Error', + code: 'ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED', + message: 'error:07880109:common libcrypto routines::interrupted or cancelled' + } : { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + })); +} + +// Passing an empty passphrase string should not throw ERR_OSSL_CRYPTO_MALLOC_FAILURE even on OpenSSL 3. +// Regression test for https://github.com/nodejs/node/issues/41428. +generateKeyPair('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + cipher: 'aes-256-cbc', + passphrase: '' + } +}, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.strictEqual(typeof privateKey, 'string'); +})); + +{ + // This test creates EC key pairs on curves without associated OIDs. + // Specifying a key encoding should not crash. + + if (process.versions.openssl >= '1.1.1i') { + for (const namedCurve of ['Oakley-EC2N-3', 'Oakley-EC2N-4']) { + if (!getCurves().includes(namedCurve)) + continue; + + const expectedErrorCode = + common.hasOpenSSL3 ? 'ERR_OSSL_MISSING_OID' : 'ERR_OSSL_EC_MISSING_OID'; + const params = { + namedCurve, + publicKeyEncoding: { + format: 'der', + type: 'spki' + } + }; + + assert.throws(() => { + generateKeyPairSync('ec', params); + }, { + code: expectedErrorCode + }); + + generateKeyPair('ec', params, common.mustCall((err) => { + assert.strictEqual(err.code, expectedErrorCode); + })); + } + } +} + +{ + // This test makes sure deprecated and new options may be used + // simultaneously so long as they're identical values. + + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hash: 'sha256', + hashAlgorithm: 'sha256', + mgf1Hash: 'sha256', + mgf1HashAlgorithm: 'sha256' + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + })); +} + +{ + // This test makes sure deprecated and new options must + // be the same value. + + assert.throws(() => generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + mgf1Hash: 'sha256', + mgf1HashAlgorithm: 'sha1' + }, common.mustNotCall()), { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hash: 'sha256', + hashAlgorithm: 'sha1' + }, common.mustNotCall()), { code: 'ERR_INVALID_ARG_VALUE' }); +} diff --git a/test/crypto/test-crypto-lazy-transform-writable.js b/test/crypto/test-crypto-lazy-transform-writable.js new file mode 100644 index 0000000..c344028 --- /dev/null +++ b/test/crypto/test-crypto-lazy-transform-writable.js @@ -0,0 +1,38 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const Stream = require('stream'); + +const hasher1 = crypto.createHash('sha256'); +const hasher2 = crypto.createHash('sha256'); + +// Calculate the expected result. +hasher1.write(Buffer.from('hello world')); +hasher1.end(); + +const expected = hasher1.read().toString('hex'); + +class OldStream extends Stream { + constructor() { + super(); + this.readable = true; + } +} + +const stream = new OldStream(); + +stream.pipe(hasher2).on('finish', common.mustCall(function() { + const hash = hasher2.read().toString('hex'); + assert.strictEqual(hash, expected); +})); + +stream.emit('data', Buffer.from('hello')); +stream.emit('data', Buffer.from(' world')); +stream.emit('end'); diff --git a/test/crypto/test-crypto-modp1-error.js b/test/crypto/test-crypto-modp1-error.js new file mode 100644 index 0000000..aa6389b --- /dev/null +++ b/test/crypto/test-crypto-modp1-error.js @@ -0,0 +1,28 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +assert.throws( + function() { + crypto.getDiffieHellman('modp1').setPrivateKey(''); + }, + new RegExp('^TypeError: crypto\\.getDiffieHellman\\(\\.\\.\\.\\)\\.' + + 'setPrivateKey is not a function$'), + 'crypto.getDiffieHellman(\'modp1\').setPrivateKey(\'\') ' + + 'failed to throw the expected error.' +); +assert.throws( + function() { + crypto.getDiffieHellman('modp1').setPublicKey(''); + }, + new RegExp('^TypeError: crypto\\.getDiffieHellman\\(\\.\\.\\.\\)\\.' + + 'setPublicKey is not a function$'), + 'crypto.getDiffieHellman(\'modp1\').setPublicKey(\'\') ' + + 'failed to throw the expected error.' +); diff --git a/test/crypto/test-crypto-op-during-process-exit.js b/test/crypto/test-crypto-op-during-process-exit.js new file mode 100644 index 0000000..1284971 --- /dev/null +++ b/test/crypto/test-crypto-op-during-process-exit.js @@ -0,0 +1,30 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) { common.skip('missing crypto'); } +const assert = require('assert'); +const { generateKeyPair } = require('crypto'); + +if (common.isWindows) { + // Remove this conditional once the libuv change is in Node.js. + common.skip('crashing due to https://github.com/libuv/libuv/pull/2983'); +} + +// Regression test for a race condition: process.exit() might lead to OpenSSL +// cleaning up state from the exit() call via calling its destructor, but +// running OpenSSL operations on another thread might lead to them attempting +// to initialize OpenSSL, leading to a crash. +// This test crashed consistently on x64 Linux on Node v14.9.0. + +generateKeyPair('rsa', { + modulusLength: 2048, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } +}, (err/* , publicKey, privateKey */) => { + assert.ifError(err); +}); + +setTimeout(() => process.exit(), common.platformTimeout(10)); diff --git a/test/crypto/test-crypto-padding-aes256.js b/test/crypto/test-crypto-padding-aes256.js new file mode 100644 index 0000000..e9450ce --- /dev/null +++ b/test/crypto/test-crypto-padding-aes256.js @@ -0,0 +1,60 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +const iv = Buffer.from('00000000000000000000000000000000', 'hex'); +const key = Buffer.from('0123456789abcdef0123456789abcdef' + + '0123456789abcdef0123456789abcdef', 'hex'); + +function encrypt(val, pad) { + const c = crypto.createCipheriv('aes256', key, iv); + c.setAutoPadding(pad); + return c.update(val, 'utf8', 'latin1') + c.final('latin1'); +} + +function decrypt(val, pad) { + const c = crypto.createDecipheriv('aes256', key, iv); + c.setAutoPadding(pad); + return c.update(val, 'latin1', 'utf8') + c.final('utf8'); +} + +// echo 0123456789abcdef0123456789abcdef \ +// | openssl enc -e -aes256 -nopad -K -iv \ +// | openssl enc -d -aes256 -nopad -K -iv +let plaintext = '0123456789abcdef0123456789abcdef'; // Multiple of block size +let encrypted = encrypt(plaintext, false); +let decrypted = decrypt(encrypted, false); +assert.strictEqual(decrypted, plaintext); + +// echo 0123456789abcdef0123456789abcde \ +// | openssl enc -e -aes256 -K -iv \ +// | openssl enc -d -aes256 -K -iv +plaintext = '0123456789abcdef0123456789abcde'; // not a multiple +encrypted = encrypt(plaintext, true); +decrypted = decrypt(encrypted, true); +assert.strictEqual(decrypted, plaintext); diff --git a/test/crypto/test-crypto-padding.js b/test/crypto/test-crypto-padding.js new file mode 100644 index 0000000..cbb5ff5 --- /dev/null +++ b/test/crypto/test-crypto-padding.js @@ -0,0 +1,126 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +// Input data. +const ODD_LENGTH_PLAIN = 'Hello node world!'; +const EVEN_LENGTH_PLAIN = 'Hello node world!AbC09876dDeFgHi'; + +const KEY_PLAIN = 'S3c.r.e.t.K.e.Y!'; +const IV_PLAIN = 'blahFizz2011Buzz'; + +const CIPHER_NAME = 'aes-128-cbc'; + +// Expected result data. + +// echo -n 'Hello node world!' | \ +// openssl enc -aes-128-cbc -e -K 5333632e722e652e742e4b2e652e5921 \ +// -iv 626c616846697a7a3230313142757a7a | xxd -p -c256 +const ODD_LENGTH_ENCRYPTED = + '7f57859550d4d2fdb9806da2a750461a9fe77253cd1cbd4b07beee4e070d561f'; + +// echo -n 'Hello node world!AbC09876dDeFgHi' | \ +// openssl enc -aes-128-cbc -e -K 5333632e722e652e742e4b2e652e5921 \ +// -iv 626c616846697a7a3230313142757a7a | xxd -p -c256 +const EVEN_LENGTH_ENCRYPTED = + '7f57859550d4d2fdb9806da2a750461ab46e71b3d78ebe2d9684dfc87f7575b988' + + '6119866912cb8c7bcaf76c5ebc2378'; + +// echo -n 'Hello node world!AbC09876dDeFgHi' | \ +// openssl enc -aes-128-cbc -e -K 5333632e722e652e742e4b2e652e5921 \ +// -iv 626c616846697a7a3230313142757a7a -nopad | xxd -p -c256 +const EVEN_LENGTH_ENCRYPTED_NOPAD = + '7f57859550d4d2fdb9806da2a750461ab46e71b3d78ebe2d9684dfc87f7575b9'; + + +// Helper wrappers. +function enc(plain, pad) { + const encrypt = crypto.createCipheriv(CIPHER_NAME, KEY_PLAIN, IV_PLAIN); + encrypt.setAutoPadding(pad); + let hex = encrypt.update(plain, 'ascii', 'hex'); + hex += encrypt.final('hex'); + return hex; +} + +function dec(encd, pad) { + const decrypt = crypto.createDecipheriv(CIPHER_NAME, KEY_PLAIN, IV_PLAIN); + decrypt.setAutoPadding(pad); + let plain = decrypt.update(encd, 'hex'); + plain += decrypt.final('latin1'); + return plain; +} + +// Test encryption +assert.strictEqual(enc(ODD_LENGTH_PLAIN, true), ODD_LENGTH_ENCRYPTED); +assert.strictEqual(enc(EVEN_LENGTH_PLAIN, true), EVEN_LENGTH_ENCRYPTED); + +assert.throws(function() { + // Input must have block length %. + enc(ODD_LENGTH_PLAIN, false); +}, common.hasOpenSSL3 ? { + message: 'error:1C80006B:Provider routines::wrong final block length', + code: 'ERR_OSSL_WRONG_FINAL_BLOCK_LENGTH', + reason: 'wrong final block length', +} : { + message: 'error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:' + + 'data not multiple of block length', + code: 'ERR_OSSL_EVP_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH', + reason: 'data not multiple of block length', +} +); + +assert.strictEqual( + enc(EVEN_LENGTH_PLAIN, false), EVEN_LENGTH_ENCRYPTED_NOPAD +); + +// Test decryption. +assert.strictEqual(dec(ODD_LENGTH_ENCRYPTED, true), ODD_LENGTH_PLAIN); +assert.strictEqual(dec(EVEN_LENGTH_ENCRYPTED, true), EVEN_LENGTH_PLAIN); + +// Returns including original padding. +assert.strictEqual(dec(ODD_LENGTH_ENCRYPTED, false).length, 32); +assert.strictEqual(dec(EVEN_LENGTH_ENCRYPTED, false).length, 48); + +assert.throws(function() { + // Must have at least 1 byte of padding (PKCS): + assert.strictEqual(dec(EVEN_LENGTH_ENCRYPTED_NOPAD, true), EVEN_LENGTH_PLAIN); +}, common.hasOpenSSL3 ? { + message: 'error:1C800064:Provider routines::bad decrypt', + reason: 'bad decrypt', + code: 'ERR_OSSL_BAD_DECRYPT', +} : { + message: 'error:06065064:digital envelope routines:EVP_DecryptFinal_ex:' + + 'bad decrypt', + reason: 'bad decrypt', + code: 'ERR_OSSL_EVP_BAD_DECRYPT', +}); + +// No-pad encrypted string should return the same: +assert.strictEqual( + dec(EVEN_LENGTH_ENCRYPTED_NOPAD, false), EVEN_LENGTH_PLAIN +); diff --git a/test/crypto/test-crypto-pbkdf2.js b/test/crypto/test-crypto-pbkdf2.js new file mode 100644 index 0000000..fad242d --- /dev/null +++ b/test/crypto/test-crypto-pbkdf2.js @@ -0,0 +1,243 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +import common from '../common'; +if (!common.hasCrypto) + common.skip('missing crypto'); + +import assert from 'assert'; +import crypto from 'crypto'; + +function runPBKDF2(password, salt, iterations, keylen, hash) { + const syncResult = + crypto.pbkdf2Sync(password, salt, iterations, keylen, hash); + + crypto.pbkdf2(password, salt, iterations, keylen, hash, + common.mustSucceed((asyncResult) => { + assert.deepStrictEqual(asyncResult, syncResult); + })); + + return syncResult; +} + +function testPBKDF2(password, salt, iterations, keylen, expected, encoding) { + const actual = runPBKDF2(password, salt, iterations, keylen, 'sha256'); + assert.strictEqual(actual.toString(encoding || 'latin1'), expected); +} + +// +// Test PBKDF2 with RFC 6070 test vectors (except #4) +// + +testPBKDF2('password', 'salt', 1, 20, + '\x12\x0f\xb6\xcf\xfc\xf8\xb3\x2c\x43\xe7\x22\x52' + + '\x56\xc4\xf8\x37\xa8\x65\x48\xc9'); + +testPBKDF2('password', 'salt', 2, 20, + '\xae\x4d\x0c\x95\xaf\x6b\x46\xd3\x2d\x0a\xdf\xf9' + + '\x28\xf0\x6d\xd0\x2a\x30\x3f\x8e'); + +testPBKDF2('password', 'salt', 4096, 20, + '\xc5\xe4\x78\xd5\x92\x88\xc8\x41\xaa\x53\x0d\xb6' + + '\x84\x5c\x4c\x8d\x96\x28\x93\xa0'); + +testPBKDF2('passwordPASSWORDpassword', + 'saltSALTsaltSALTsaltSALTsaltSALTsalt', + 4096, + 25, + '\x34\x8c\x89\xdb\xcb\xd3\x2b\x2f\x32\xd8\x14\xb8\x11' + + '\x6e\x84\xcf\x2b\x17\x34\x7e\xbc\x18\x00\x18\x1c'); + +testPBKDF2('pass\0word', 'sa\0lt', 4096, 16, + '\x89\xb6\x9d\x05\x16\xf8\x29\x89\x3c\x69\x62\x26\x65' + + '\x0a\x86\x87'); + +testPBKDF2('password', 'salt', 32, 32, + '64c486c55d30d4c5a079b8823b7d7cb37ff0556f537da8410233bcec330ed956', + 'hex'); + +// Error path should not leak memory (check with valgrind). +assert.throws( + () => crypto.pbkdf2('password', 'salt', 1, 20, 'sha1'), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } +); + +for (const iterations of [-1, 0]) { + assert.throws( + () => crypto.pbkdf2Sync('password', 'salt', iterations, 20, 'sha1'), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + } + ); +} + +['str', null, undefined, [], {}].forEach((notNumber) => { + assert.throws( + () => { + crypto.pbkdf2Sync('password', 'salt', 1, notNumber, 'sha256'); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "keylen" argument must be of type number.' + + `${common.invalidArgTypeHelper(notNumber)}` + }); +}); + +[Infinity, -Infinity, NaN].forEach((input) => { + assert.throws( + () => { + crypto.pbkdf2('password', 'salt', 1, input, 'sha256', + common.mustNotCall()); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "keylen" is out of range. It ' + + `must be an integer. Received ${input}` + }); +}); + +[-1, 4294967297].forEach((input) => { + assert.throws( + () => { + crypto.pbkdf2('password', 'salt', 1, input, 'sha256', + common.mustNotCall()); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + }); +}); + +// Should not get FATAL ERROR with empty password and salt +// https://github.com/nodejs/node/issues/8571 +crypto.pbkdf2('', '', 1, 32, 'sha256', common.mustSucceed()); + +assert.throws( + () => crypto.pbkdf2('password', 'salt', 8, 8, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "digest" argument must be of type string. ' + + 'Received undefined' + }); + +assert.throws( + () => crypto.pbkdf2Sync('password', 'salt', 8, 8), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "digest" argument must be of type string. ' + + 'Received undefined' + }); + +assert.throws( + () => crypto.pbkdf2Sync('password', 'salt', 8, 8, null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "digest" argument must be of type string. ' + + 'Received null' + }); +[1, {}, [], true, undefined, null].forEach((input) => { + assert.throws( + () => crypto.pbkdf2(input, 'salt', 8, 8, 'sha256', common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } + ); + + assert.throws( + () => crypto.pbkdf2('pass', input, 8, 8, 'sha256', common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } + ); + + assert.throws( + () => crypto.pbkdf2Sync(input, 'salt', 8, 8, 'sha256'), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } + ); + + assert.throws( + () => crypto.pbkdf2Sync('pass', input, 8, 8, 'sha256'), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } + ); +}); + +['test', {}, [], true, undefined, null].forEach((i) => { + const received = common.invalidArgTypeHelper(i); + assert.throws( + () => crypto.pbkdf2('pass', 'salt', i, 8, 'sha256', common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: `The "iterations" argument must be of type number.${received}` + } + ); + + assert.throws( + () => crypto.pbkdf2Sync('pass', 'salt', i, 8, 'sha256'), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: `The "iterations" argument must be of type number.${received}` + } + ); +}); + +// Any TypedArray should work for password and salt. +for (const SomeArray of [Uint8Array, Uint16Array, Uint32Array, Float32Array, + Float64Array, ArrayBuffer, SharedArrayBuffer]) { + runPBKDF2(new SomeArray(10), 'salt', 8, 8, 'sha256'); + runPBKDF2('pass', new SomeArray(10), 8, 8, 'sha256'); +} + +assert.throws( + () => crypto.pbkdf2('pass', 'salt', 8, 8, 'md55', common.mustNotCall()), + { + code: 'ERR_CRYPTO_INVALID_DIGEST', + name: 'TypeError', + message: 'Invalid digest: md55' + } +); + +assert.throws( + () => crypto.pbkdf2Sync('pass', 'salt', 8, 8, 'md55'), + { + code: 'ERR_CRYPTO_INVALID_DIGEST', + name: 'TypeError', + message: 'Invalid digest: md55' + } +); + +/*if (!common.hasOpenSSL3) { + const kNotPBKDF2Supported = ['shake128', 'shake256']; + crypto.getHashes() + .filter((hash) => !kNotPBKDF2Supported.includes(hash)) + .forEach((hash) => { + runPBKDF2(new Uint8Array(10), 'salt', 8, 8, hash); + }); +}*/ + +{ + // This should not crash. + assert.throws( + () => crypto.pbkdf2Sync('1', '2', 1, 1, '%'), + { + code: 'ERR_CRYPTO_INVALID_DIGEST', + name: 'TypeError', + message: 'Invalid digest: %' + } + ); +} diff --git a/test/crypto/test-crypto-prime.js b/test/crypto/test-crypto-prime.js new file mode 100644 index 0000000..0db1386 --- /dev/null +++ b/test/crypto/test-crypto-prime.js @@ -0,0 +1,285 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); + +const { + generatePrime, + generatePrimeSync, + checkPrime, + checkPrimeSync, +} = require('crypto'); + +const { promisify } = require('util'); +const pgeneratePrime = promisify(generatePrime); +const pCheckPrime = promisify(checkPrime); + +['hello', false, {}, []].forEach((i) => { + assert.throws(() => generatePrime(i), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrimeSync(i), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +['hello', false, 123].forEach((i) => { + assert.throws(() => generatePrime(80, i, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrimeSync(80, i), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +['hello', false, 123].forEach((i) => { + assert.throws(() => generatePrime(80, {}), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +[-1, 0, 2 ** 31, 2 ** 31 + 1, 2 ** 32 - 1, 2 ** 32].forEach((size) => { + assert.throws(() => generatePrime(size, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + message: />= 1 && <= 2147483647/ + }); + assert.throws(() => generatePrimeSync(size), { + code: 'ERR_OUT_OF_RANGE', + message: />= 1 && <= 2147483647/ + }); +}); + +['test', -1, {}, []].forEach((i) => { + assert.throws(() => generatePrime(8, { safe: i }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrime(8, { rem: i }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrime(8, { add: i }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrimeSync(8, { safe: i }), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrimeSync(8, { rem: i }), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => generatePrimeSync(8, { add: i }), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +{ + // Negative BigInts should not be converted to 0 silently. + + assert.throws(() => generatePrime(20, { add: -1n }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "options.add" is out of range. It must be >= 0. ' + + 'Received -1n' + }); + + assert.throws(() => generatePrime(20, { rem: -1n }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "options.rem" is out of range. It must be >= 0. ' + + 'Received -1n' + }); + + assert.throws(() => checkPrime(-1n, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "candidate" is out of range. It must be >= 0. ' + + 'Received -1n' + }); +} + +generatePrime(80, common.mustSucceed((prime) => { + assert(checkPrimeSync(prime)); + checkPrime(prime, common.mustSucceed((result) => { + assert(result); + })); +})); + +assert(checkPrimeSync(generatePrimeSync(80))); + +generatePrime(80, {}, common.mustSucceed((prime) => { + assert(checkPrimeSync(prime)); +})); + +assert(checkPrimeSync(generatePrimeSync(80, {}))); + +generatePrime(32, { safe: true }, common.mustSucceed((prime) => { + assert(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + const check = (val - 1) / 2; + buf.writeUInt32BE(check); + assert(checkPrimeSync(buf)); +})); + +{ + const prime = generatePrimeSync(32, { safe: true }); + assert(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + const check = (val - 1) / 2; + buf.writeUInt32BE(check); + assert(checkPrimeSync(buf)); +} + +const add = 12; +const rem = 11; +const add_buf = Buffer.from([add]); +const rem_buf = Buffer.from([rem]); +generatePrime( + 32, + { add: add_buf, rem: rem_buf }, + common.mustSucceed((prime) => { + assert(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + assert.strictEqual(val % add, rem); + })); + +{ + const prime = generatePrimeSync(32, { add: add_buf, rem: rem_buf }); + assert(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + assert.strictEqual(val % add, rem); +} + +{ + const prime = generatePrimeSync(32, { add: BigInt(add), rem: BigInt(rem) }); + assert(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + assert.strictEqual(val % add, rem); +} + +{ + // The behavior when specifying only add without rem should depend on the + // safe option. + + if (process.versions.openssl >= '1.1.1f') { + generatePrime(128, { + bigint: true, + add: 5n + }, common.mustSucceed((prime) => { + assert(checkPrimeSync(prime)); + assert.strictEqual(prime % 5n, 1n); + })); + + generatePrime(128, { + bigint: true, + safe: true, + add: 5n + }, common.mustSucceed((prime) => { + assert(checkPrimeSync(prime)); + assert.strictEqual(prime % 5n, 3n); + })); + } +} + +{ + // This is impossible because it implies (prime % 2**64) == 1 and + // prime < 2**64, meaning prime = 1, but 1 is not prime. + for (const add of [2n ** 64n, 2n ** 65n]) { + assert.throws(() => { + generatePrimeSync(64, { add }); + }, { + code: 'ERR_OUT_OF_RANGE', + message: 'invalid options.add' + }); + } + + // Any parameters with rem >= add lead to an impossible condition. + for (const rem of [7n, 8n, 3000n]) { + assert.throws(() => { + generatePrimeSync(64, { add: 7n, rem }); + }, { + code: 'ERR_OUT_OF_RANGE', + message: 'invalid options.rem' + }); + } + + // This is possible, but not allowed. It implies prime == 7, which means that + // we did not actually generate a random prime. + assert.throws(() => { + generatePrimeSync(3, { add: 8n, rem: 7n }); + }, { + code: 'ERR_OUT_OF_RANGE' + }); + + if (process.versions.openssl >= '1.1.1f') { + // This is possible and allowed (but makes little sense). + assert.strictEqual(generatePrimeSync(4, { + add: 15n, + rem: 13n, + bigint: true + }), 13n); + } +} + +[1, 'hello', {}, []].forEach((i) => { + assert.throws(() => checkPrime(i), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +['hello', {}, []].forEach((i) => { + assert.throws(() => checkPrime(2, { checks: i }), { + code: 'ERR_INVALID_ARG_TYPE' + }, common.mustNotCall()); + assert.throws(() => checkPrimeSync(2, { checks: i }), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +assert(!checkPrimeSync(Buffer.from([0x1]))); +assert(checkPrimeSync(Buffer.from([0x2]))); +assert(checkPrimeSync(Buffer.from([0x3]))); +assert(!checkPrimeSync(Buffer.from([0x4]))); + +assert( + !checkPrimeSync( + Buffer.from([0x1]), + { + fast: true, + trialDivision: true, + checks: 10 + })); + +(async function() { + const prime = await pgeneratePrime(36); + assert(await pCheckPrime(prime)); +})().then(common.mustCall()); + +assert.throws(() => { + generatePrimeSync(32, { bigint: '' }); +}, { code: 'ERR_INVALID_ARG_TYPE' }); + +assert.throws(() => { + generatePrime(32, { bigint: '' }, common.mustNotCall()); +}, { code: 'ERR_INVALID_ARG_TYPE' }); + +{ + const prime = generatePrimeSync(3, { bigint: true }); + assert.strictEqual(typeof prime, 'bigint'); + assert.strictEqual(prime, 7n); + assert(checkPrimeSync(prime)); + checkPrime(prime, common.mustSucceed(assert)); +} + +{ + generatePrime(3, { bigint: true }, common.mustSucceed((prime) => { + assert.strictEqual(typeof prime, 'bigint'); + assert.strictEqual(prime, 7n); + assert(checkPrimeSync(prime)); + checkPrime(prime, common.mustSucceed(assert)); + })); +} diff --git a/test/crypto/test-crypto-private-decrypt-gh32240.js b/test/crypto/test-crypto-private-decrypt-gh32240.js new file mode 100644 index 0000000..51fc008 --- /dev/null +++ b/test/crypto/test-crypto-private-decrypt-gh32240.js @@ -0,0 +1,43 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; + +// Verify that privateDecrypt() does not leave an error on the +// openssl error stack that is visible to subsequent operations. + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPairSync, + publicEncrypt, + privateDecrypt, +} = require('crypto'); + +const pair = generateKeyPairSync('rsa', { modulusLength: 512 }); + +const expected = Buffer.from('shibboleth'); +const encrypted = publicEncrypt(pair.publicKey, expected); + +const pkey = pair.privateKey.export({ type: 'pkcs1', format: 'pem' }); +const pkeyEncrypted = + pair.privateKey.export({ + type: 'pkcs1', + format: 'pem', + cipher: 'aes128', + passphrase: 'secret', + }); + +function decrypt(key) { + const decrypted = privateDecrypt(key, encrypted); + assert.deepStrictEqual(decrypted, expected); +} + +decrypt(pkey); +assert.throws(() => decrypt(pkeyEncrypted), common.hasOpenSSL3 ? + { message: 'error:07880109:common libcrypto routines::interrupted or ' + + 'cancelled' } : + { code: 'ERR_MISSING_PASSPHRASE' }); +decrypt(pkey); // Should not throw. diff --git a/test/crypto/test-crypto-psychic-signatures.js b/test/crypto/test-crypto-psychic-signatures.js new file mode 100644 index 0000000..c1ab073 --- /dev/null +++ b/test/crypto/test-crypto-psychic-signatures.js @@ -0,0 +1,102 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); + +const crypto = require('crypto'); + +// Tests for CVE-2022-21449 +// https://neilmadden.blog/2022/04/19/psychic-signatures-in-java/ +// Dubbed "Psychic Signatures", these signatures bypassed the ECDSA signature +// verification implementation in Java in 15, 16, 17, and 18. OpenSSL is not +// (and was not) vulnerable so these are a precaution. + +const vectors = { + 'ieee-p1363': [ + Buffer.from('0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000', 'hex'), + Buffer.from('ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551' + + 'ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551', 'hex'), + ], + 'der': [ + Buffer.from('3046022100' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '022100' + + '0000000000000000000000000000000000000000000000000000000000000000', 'hex'), + Buffer.from('3046022100' + + 'ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551' + + '022100' + + 'ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551', 'hex'), + ], +}; + +const keyPair = crypto.generateKeyPairSync('ec', { + namedCurve: 'P-256', + publicKeyEncoding: { + format: 'der', + type: 'spki' + }, +}); + +const data = Buffer.from('Hello!'); + +for (const [encoding, signatures] of Object.entries(vectors)) { + for (const signature of signatures) { + const key = { + key: keyPair.publicKey, + format: 'der', + type: 'spki', + dsaEncoding: encoding, + }; + + // one-shot sync + assert.strictEqual( + crypto.verify( + 'sha256', + data, + key, + signature, + ), + false, + ); + + // one-shot async + crypto.verify( + 'sha256', + data, + key, + signature, + common.mustSucceed((verified) => assert.strictEqual(verified, false)), + ); + + // stream + assert.strictEqual( + crypto.createVerify('sha256') + .update(data) + .verify(key, signature), + false, + ); + + // webcrypto + crypto.webcrypto.subtle.importKey( + 'spki', + keyPair.publicKey, + { name: 'ECDSA', namedCurve: 'P-256' }, + false, + ['verify'], + ).then((publicKey) => { + return crypto.webcrypto.subtle.verify( + { name: 'ECDSA', hash: 'SHA-256' }, + publicKey, + signature, + data, + ); + }).then(common.mustCall((verified) => { + assert.strictEqual(verified, false); + })); + } +} diff --git a/test/crypto/test-crypto-random.js b/test/crypto/test-crypto-random.js new file mode 100644 index 0000000..f9f9fef --- /dev/null +++ b/test/crypto/test-crypto-random.js @@ -0,0 +1,529 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Flags: --pending-deprecation +'use strict'; +import common from '../common'; + +if (!common.hasCrypto) + common.skip('missing crypto'); + +import assert from 'assert'; +import crypto from 'crypto'; +// const cryptop = require('crypto').webcrypto; +import { kMaxLength } from 'buffer'; + +const kMaxInt32 = 2 ** 31 - 1; +const kMaxPossibleLength = Math.min(kMaxLength, kMaxInt32); + +common.expectWarning('DeprecationWarning', + 'crypto.pseudoRandomBytes is deprecated.', 'DEP0115'); + +{ + [crypto.randomBytes/*, crypto.pseudoRandomBytes*/].forEach((f) => { + [undefined, null, false, true, {}, []].forEach((value) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "size" argument must be of type number.' + + common.invalidArgTypeHelper(value) + }; + assert.throws(() => f(value), errObj); + assert.throws(() => f(value, common.mustNotCall()), errObj); + }); + + [-1, NaN, 2 ** 32, 2 ** 31].forEach((value) => { + const errObj = { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "size" is out of range. It must be >= 0 && <= ' + + `${kMaxPossibleLength}. Received ${value}` + }; + assert.throws(() => f(value), errObj); + assert.throws(() => f(value, common.mustNotCall()), errObj); + }); + + [0, 1, 2, 4, 16, 256, 1024, 101.2].forEach((len) => { + f(len, common.mustCall((ex, buf) => { + assert.strictEqual(ex, null); + assert.strictEqual(buf.length, Math.floor(len)); + assert.ok(Buffer.isBuffer(buf)); + })); + }); + }); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + const after = crypto.randomFillSync(buf).toString('hex'); + assert.notStrictEqual(before, after); +} + +{ + const buf = new Uint8Array(new Array(10).fill(0)); + const before = Buffer.from(buf).toString('hex'); + crypto.randomFillSync(buf); + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); +} + +{ + [ + new Uint16Array(10), + new Uint32Array(10), + new Float32Array(10), + new Float64Array(10), + new DataView(new ArrayBuffer(10)), + ].forEach((buf) => { + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFillSync(buf); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); + }); +} + +/*{ + [ + new Uint16Array(10), + new Uint32Array(10), + ].forEach((buf) => { + const before = Buffer.from(buf.buffer).toString('hex'); + cryptop.getRandomValues(buf); + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); + }); +}*/ + +{ + [ + new ArrayBuffer(10), + new SharedArrayBuffer(10), + ].forEach((buf) => { + const before = Buffer.from(buf).toString('hex'); + crypto.randomFillSync(buf); + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + }); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + crypto.randomFill(buf, common.mustSucceed((buf) => { + const after = buf.toString('hex'); + assert.notStrictEqual(before, after); + })); +} + +{ + const buf = new Uint8Array(new Array(10).fill(0)); + const before = Buffer.from(buf).toString('hex'); + crypto.randomFill(buf, common.mustSucceed((buf) => { + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + })); +} + +{ + [ + new Uint16Array(10), + new Uint32Array(10), + new Float32Array(10), + new Float64Array(10), + new DataView(new ArrayBuffer(10)), + ].forEach((buf) => { + const before = Buffer.from(buf.buffer).toString('hex'); + crypto.randomFill(buf, common.mustSucceed((buf) => { + const after = Buffer.from(buf.buffer).toString('hex'); + assert.notStrictEqual(before, after); + })); + }); +} + +{ + [ + new ArrayBuffer(10), + new SharedArrayBuffer(10), + ].forEach((buf) => { + const before = Buffer.from(buf).toString('hex'); + crypto.randomFill(buf, common.mustSucceed((buf) => { + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + })); + }); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + crypto.randomFillSync(buf, 5, 5); + const after = buf.toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); +} + +{ + const buf = new Uint8Array(new Array(10).fill(0)); + const before = Buffer.from(buf).toString('hex'); + crypto.randomFillSync(buf, 5, 5); + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + crypto.randomFillSync(buf, 5); + const after = buf.toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); +} + +{ + const buf = Buffer.alloc(10); + const before = buf.toString('hex'); + crypto.randomFill(buf, 5, 5, common.mustSucceed((buf) => { + const after = buf.toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); + })); +} + +{ + const buf = new Uint8Array(new Array(10).fill(0)); + const before = Buffer.from(buf).toString('hex'); + crypto.randomFill(buf, 5, 5, common.mustSucceed((buf) => { + const after = Buffer.from(buf).toString('hex'); + assert.notStrictEqual(before, after); + assert.deepStrictEqual(before.slice(0, 5), after.slice(0, 5)); + })); +} + +{ + [ + Buffer.alloc(10), + new Uint8Array(new Array(10).fill(0)), + ].forEach((buf) => { + const len = Buffer.byteLength(buf); + assert.strictEqual(len, 10, `Expected byteLength of 10, got ${len}`); + + const typeErrObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "offset" argument must be of type number. ' + + "Received type string ('test')" + }; + + assert.throws(() => crypto.randomFillSync(buf, 'test'), typeErrObj); + + assert.throws( + () => crypto.randomFill(buf, 'test', common.mustNotCall()), + typeErrObj); + + typeErrObj.message = typeErrObj.message.replace('offset', 'size'); + assert.throws(() => crypto.randomFillSync(buf, 0, 'test'), typeErrObj); + + assert.throws( + () => crypto.randomFill(buf, 0, 'test', common.mustNotCall()), + typeErrObj + ); + + [NaN, kMaxPossibleLength + 1, -10, (-1 >>> 0) + 1].forEach((offsetSize) => { + const errObj = { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 && <= 10. Received ${offsetSize}` + }; + + assert.throws(() => crypto.randomFillSync(buf, offsetSize), errObj); + + assert.throws( + () => crypto.randomFill(buf, offsetSize, common.mustNotCall()), + errObj); + + errObj.message = 'The value of "size" is out of range. It must be >= ' + + `0 && <= ${kMaxPossibleLength}. Received ${offsetSize}`; + assert.throws(() => crypto.randomFillSync(buf, 1, offsetSize), errObj); + + assert.throws( + () => crypto.randomFill(buf, 1, offsetSize, common.mustNotCall()), + errObj + ); + }); + + const rangeErrObj = { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "size + offset" is out of range. ' + + 'It must be <= 10. Received 11' + }; + assert.throws(() => crypto.randomFillSync(buf, 1, 10), rangeErrObj); + + assert.throws( + () => crypto.randomFill(buf, 1, 10, common.mustNotCall()), + rangeErrObj + ); + }); +} + +// https://github.com/nodejs/node-v0.x-archive/issues/5126, +// "FATAL ERROR: v8::Object::SetIndexedPropertiesToExternalArrayData() length +// exceeds max acceptable value" +assert.throws( + () => crypto.randomBytes((-1 >>> 0) + 1), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "size" is out of range. ' + + `It must be >= 0 && <= ${kMaxPossibleLength}. Received 4294967296` + } +); + +[1, true, NaN, null, undefined, {}, []].forEach((i) => { + const buf = Buffer.alloc(10); + assert.throws( + () => crypto.randomFillSync(i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => crypto.randomFill(i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => crypto.randomFill(buf, 0, 10, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); +}); + +[1, true, NaN, null, {}, []].forEach((i) => { + assert.throws( + () => crypto.randomBytes(1, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } + ); +}); + +/*['pseudoRandomBytes', 'prng', 'rng'].forEach((f) => { + const desc = Object.getOwnPropertyDescriptor(crypto, f); + assert.ok(desc); + assert.strictEqual(desc.configurable, true); + assert.strictEqual(desc.enumerable, false); +});*/ + + +{ + // Asynchronous API + const randomInts = []; + for (let i = 0; i < 100; i++) { + crypto.randomInt(3, common.mustSucceed((n) => { + assert.ok(n >= 0); + assert.ok(n < 3); + randomInts.push(n); + if (randomInts.length === 100) { + assert.ok(!randomInts.includes(-1)); + assert.ok(randomInts.includes(0)); + assert.ok(randomInts.includes(1)); + assert.ok(randomInts.includes(2)); + assert.ok(!randomInts.includes(3)); + } + })); + } +} +{ + // Synchronous API + const randomInts = []; + for (let i = 0; i < 100; i++) { + const n = crypto.randomInt(3); + assert.ok(n >= 0); + assert.ok(n < 3); + randomInts.push(n); + } + + assert.ok(!randomInts.includes(-1)); + assert.ok(randomInts.includes(0)); + assert.ok(randomInts.includes(1)); + assert.ok(randomInts.includes(2)); + assert.ok(!randomInts.includes(3)); +} +{ + // Positive range + const randomInts = []; + for (let i = 0; i < 100; i++) { + crypto.randomInt(1, 3, common.mustSucceed((n) => { + assert.ok(n >= 1); + assert.ok(n < 3); + randomInts.push(n); + if (randomInts.length === 100) { + assert.ok(!randomInts.includes(0)); + assert.ok(randomInts.includes(1)); + assert.ok(randomInts.includes(2)); + assert.ok(!randomInts.includes(3)); + } + })); + } +} +{ + // Negative range + const randomInts = []; + for (let i = 0; i < 100; i++) { + crypto.randomInt(-10, -8, common.mustSucceed((n) => { + assert.ok(n >= -10); + assert.ok(n < -8); + randomInts.push(n); + if (randomInts.length === 100) { + assert.ok(!randomInts.includes(-11)); + assert.ok(randomInts.includes(-10)); + assert.ok(randomInts.includes(-9)); + assert.ok(!randomInts.includes(-8)); + } + })); + } +} +{ + + ['10', true, NaN, null, {}, []].forEach((i) => { + const invalidMinError = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "min" argument must be a safe integer.' + + `${common.invalidArgTypeHelper(i)}`, + }; + const invalidMaxError = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "max" argument must be a safe integer.' + + `${common.invalidArgTypeHelper(i)}`, + }; + + assert.throws( + () => crypto.randomInt(i, 100), + invalidMinError + ); + assert.throws( + () => crypto.randomInt(i, 100, common.mustNotCall()), + invalidMinError + ); + assert.throws( + () => crypto.randomInt(i), + invalidMaxError + ); + assert.throws( + () => crypto.randomInt(i, common.mustNotCall()), + invalidMaxError + ); + assert.throws( + () => crypto.randomInt(0, i, common.mustNotCall()), + invalidMaxError + ); + assert.throws( + () => crypto.randomInt(0, i), + invalidMaxError + ); + }); + + const maxInt = Number.MAX_SAFE_INTEGER; + const minInt = Number.MIN_SAFE_INTEGER; + + crypto.randomInt(minInt, minInt + 5, common.mustSucceed()); + crypto.randomInt(maxInt - 5, maxInt, common.mustSucceed()); + + assert.throws( + () => crypto.randomInt(minInt - 1, minInt + 5, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "min" argument must be a safe integer.' + + `${common.invalidArgTypeHelper(minInt - 1)}`, + } + ); + + assert.throws( + () => crypto.randomInt(maxInt + 1, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "max" argument must be a safe integer.' + + `${common.invalidArgTypeHelper(maxInt + 1)}`, + } + ); + + crypto.randomInt(1, common.mustSucceed()); + crypto.randomInt(0, 1, common.mustSucceed()); + for (const arg of [[0], [1, 1], [3, 2], [-5, -5], [11, -10]]) { + assert.throws(() => crypto.randomInt(...arg, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "max" is out of range. It must be greater than ' + + `the value of "min" (${arg[arg.length - 2] || 0}). ` + + `Received ${arg[arg.length - 1]}` + }); + } + + const MAX_RANGE = 0xFFFF_FFFF_FFFF; + crypto.randomInt(MAX_RANGE, common.mustSucceed()); + crypto.randomInt(1, MAX_RANGE + 1, common.mustSucceed()); + assert.throws( + () => crypto.randomInt(1, MAX_RANGE + 2, common.mustNotCall()), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "max - min" is out of range. ' + + `It must be <= ${MAX_RANGE}. ` + + 'Received 281_474_976_710_656' + } + ); + + assert.throws(() => crypto.randomInt(MAX_RANGE + 1, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "max" is out of range. ' + + `It must be <= ${MAX_RANGE}. ` + + 'Received 281_474_976_710_656' + }); + + [true, NaN, null, {}, [], 10].forEach((i) => { + const cbError = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }; + assert.throws(() => crypto.randomInt(0, 1, i), cbError); + }); +} + +{ + // Verify that it doesn't throw or abort + crypto.randomFill(new Uint16Array(10), 0, common.mustSucceed()); + crypto.randomFill(new Uint32Array(10), 0, common.mustSucceed()); + crypto.randomFill(new Uint32Array(10), 0, 1, common.mustSucceed()); +} diff --git a/test/crypto/test-crypto-randomfillsync-regression.js b/test/crypto/test-crypto-randomfillsync-regression.js new file mode 100644 index 0000000..92ba251 --- /dev/null +++ b/test/crypto/test-crypto-randomfillsync-regression.js @@ -0,0 +1,20 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +import common from '../common'; +if (!common.hasCrypto) + common.skip('missing crypto'); + +import { randomFillSync } from 'crypto'; +import assert from 'assert'; + +const ab = new ArrayBuffer(20); +const buf = Buffer.from(ab, 10); + +const before = buf.toString('hex'); + +randomFillSync(buf); + +const after = buf.toString('hex'); + +assert.notStrictEqual(before, after); diff --git a/test/crypto/test-crypto-randomuuid.js b/test/crypto/test-crypto-randomuuid.js new file mode 100644 index 0000000..02589f8 --- /dev/null +++ b/test/crypto/test-crypto-randomuuid.js @@ -0,0 +1,60 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; + +import common from '../common'; + +if (!common.hasCrypto) + common.skip('missing crypto'); + +import assert from 'assert'; +import { + randomUUID, +} from 'crypto'; + +const last = new Set([ + '00000000-0000-0000-0000-000000000000', +]); + +function testMatch(uuid) { + assert.match( + uuid, + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/); +} + +// Generate a number of UUID's to make sure we're +// not just generating the same value over and over +// and to make sure the batching changes the random +// bytes. +for (let n = 0; n < 130; n++) { + const uuid = randomUUID(); + assert(!last.has(uuid)); + last.add(uuid); + assert.strictEqual(typeof uuid, 'string'); + assert.strictEqual(uuid.length, 36); + testMatch(uuid); + + // Check that version 4 identifier was populated. + assert.strictEqual( + Buffer.from(uuid.substr(14, 2), 'hex')[0] & 0x40, 0x40); + + // Check that clock_seq_hi_and_reserved was populated with reserved bits. + assert.strictEqual( + Buffer.from(uuid.substr(19, 2), 'hex')[0] & 0b1100_0000, 0b1000_0000); +} + +// Test non-buffered UUID's +{ + testMatch(randomUUID({ disableEntropyCache: true })); + testMatch(randomUUID({ disableEntropyCache: true })); + testMatch(randomUUID({ disableEntropyCache: true })); + testMatch(randomUUID({ disableEntropyCache: true })); + + assert.throws(() => randomUUID(1), { + code: 'ERR_INVALID_ARG_TYPE' + }); + + assert.throws(() => randomUUID({ disableEntropyCache: '' }), { + code: 'ERR_INVALID_ARG_TYPE' + }); +} diff --git a/test/crypto/test-crypto-rsa-dsa.js b/test/crypto/test-crypto-rsa-dsa.js new file mode 100644 index 0000000..0023df5 --- /dev/null +++ b/test/crypto/test-crypto-rsa-dsa.js @@ -0,0 +1,476 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +const constants = crypto.constants; + +const fixtures = require('../common/fixtures'); + +// Test certificates +const certPem = fixtures.readKey('rsa_cert.crt'); +const keyPem = fixtures.readKey('rsa_private.pem'); +const rsaKeySize = 2048; +const rsaPubPem = fixtures.readKey('rsa_public.pem', 'ascii'); +const rsaKeyPem = fixtures.readKey('rsa_private.pem', 'ascii'); +const rsaKeyPemEncrypted = fixtures.readKey('rsa_private_encrypted.pem', + 'ascii'); +const dsaPubPem = fixtures.readKey('dsa_public.pem', 'ascii'); +const dsaKeyPem = fixtures.readKey('dsa_private.pem', 'ascii'); +const dsaKeyPemEncrypted = fixtures.readKey('dsa_private_encrypted.pem', + 'ascii'); +const rsaPkcs8KeyPem = fixtures.readKey('rsa_private_pkcs8.pem'); +const dsaPkcs8KeyPem = fixtures.readKey('dsa_private_pkcs8.pem'); + +const ec = new TextEncoder(); + +const openssl1DecryptError = { + message: 'error:06065064:digital envelope routines:EVP_DecryptFinal_ex:' + + 'bad decrypt', + code: 'ERR_OSSL_EVP_BAD_DECRYPT', + reason: 'bad decrypt', + function: 'EVP_DecryptFinal_ex', + library: 'digital envelope routines', +}; + +const decryptError = common.hasOpenSSL3 ? + { message: 'error:1C800064:Provider routines::bad decrypt' } : + openssl1DecryptError; + +const decryptPrivateKeyError = common.hasOpenSSL3 ? { + message: 'error:1C800064:Provider routines::bad decrypt', +} : openssl1DecryptError; + +function getBufferCopy(buf) { + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); +} + +// Test RSA encryption/decryption +{ + const input = 'I AM THE WALRUS'; + const bufferToEncrypt = Buffer.from(input); + const bufferPassword = Buffer.from('password'); + + let encryptedBuffer = crypto.publicEncrypt(rsaPubPem, bufferToEncrypt); + + // Test other input types + let otherEncrypted; + { + const ab = getBufferCopy(ec.encode(rsaPubPem)); + const ab2enc = getBufferCopy(bufferToEncrypt); + + crypto.publicEncrypt(ab, ab2enc); + crypto.publicEncrypt(new Uint8Array(ab), new Uint8Array(ab2enc)); + crypto.publicEncrypt(new DataView(ab), new DataView(ab2enc)); + otherEncrypted = crypto.publicEncrypt({ + key: Buffer.from(ab).toString('hex'), + encoding: 'hex' + }, Buffer.from(ab2enc).toString('hex')); + } + + let decryptedBuffer = crypto.privateDecrypt(rsaKeyPem, encryptedBuffer); + const otherDecrypted = crypto.privateDecrypt(rsaKeyPem, otherEncrypted); + assert.strictEqual(decryptedBuffer.toString(), input); + assert.strictEqual(otherDecrypted.toString(), input); + + decryptedBuffer = crypto.privateDecrypt(rsaPkcs8KeyPem, encryptedBuffer); + assert.strictEqual(decryptedBuffer.toString(), input); + + let decryptedBufferWithPassword = crypto.privateDecrypt({ + key: rsaKeyPemEncrypted, + passphrase: 'password' + }, encryptedBuffer); + + const otherDecryptedBufferWithPassword = crypto.privateDecrypt({ + key: rsaKeyPemEncrypted, + passphrase: ec.encode('password') + }, encryptedBuffer); + + assert.strictEqual( + otherDecryptedBufferWithPassword.toString(), + decryptedBufferWithPassword.toString()); + + decryptedBufferWithPassword = crypto.privateDecrypt({ + key: rsaKeyPemEncrypted, + passphrase: 'password' + }, encryptedBuffer); + + assert.strictEqual(decryptedBufferWithPassword.toString(), input); + + encryptedBuffer = crypto.publicEncrypt({ + key: rsaKeyPemEncrypted, + passphrase: 'password' + }, bufferToEncrypt); + + decryptedBufferWithPassword = crypto.privateDecrypt({ + key: rsaKeyPemEncrypted, + passphrase: 'password' + }, encryptedBuffer); + assert.strictEqual(decryptedBufferWithPassword.toString(), input); + + encryptedBuffer = crypto.privateEncrypt({ + key: rsaKeyPemEncrypted, + passphrase: bufferPassword + }, bufferToEncrypt); + + decryptedBufferWithPassword = crypto.publicDecrypt({ + key: rsaKeyPemEncrypted, + passphrase: bufferPassword + }, encryptedBuffer); + assert.strictEqual(decryptedBufferWithPassword.toString(), input); + + // Now with explicit RSA_PKCS1_PADDING. + encryptedBuffer = crypto.privateEncrypt({ + padding: crypto.constants.RSA_PKCS1_PADDING, + key: rsaKeyPemEncrypted, + passphrase: bufferPassword + }, bufferToEncrypt); + + decryptedBufferWithPassword = crypto.publicDecrypt({ + padding: crypto.constants.RSA_PKCS1_PADDING, + key: rsaKeyPemEncrypted, + passphrase: bufferPassword + }, encryptedBuffer); + assert.strictEqual(decryptedBufferWithPassword.toString(), input); + + // Omitting padding should be okay because RSA_PKCS1_PADDING is the default. + decryptedBufferWithPassword = crypto.publicDecrypt({ + key: rsaKeyPemEncrypted, + passphrase: bufferPassword + }, encryptedBuffer); + assert.strictEqual(decryptedBufferWithPassword.toString(), input); + + // Now with RSA_NO_PADDING. Plaintext needs to match key size. + // OpenSSL 3.x has a rsa_check_padding that will cause an error if + // RSA_NO_PADDING is used. + if (!common.hasOpenSSL3) { + { + const plaintext = 'x'.repeat(rsaKeySize / 8); + encryptedBuffer = crypto.privateEncrypt({ + padding: crypto.constants.RSA_NO_PADDING, + key: rsaKeyPemEncrypted, + passphrase: bufferPassword + }, Buffer.from(plaintext)); + + decryptedBufferWithPassword = crypto.publicDecrypt({ + padding: crypto.constants.RSA_NO_PADDING, + key: rsaKeyPemEncrypted, + passphrase: bufferPassword + }, encryptedBuffer); + assert.strictEqual(decryptedBufferWithPassword.toString(), plaintext); + } + } + + encryptedBuffer = crypto.publicEncrypt(certPem, bufferToEncrypt); + + decryptedBuffer = crypto.privateDecrypt(keyPem, encryptedBuffer); + assert.strictEqual(decryptedBuffer.toString(), input); + + encryptedBuffer = crypto.publicEncrypt(keyPem, bufferToEncrypt); + + decryptedBuffer = crypto.privateDecrypt(keyPem, encryptedBuffer); + assert.strictEqual(decryptedBuffer.toString(), input); + + encryptedBuffer = crypto.privateEncrypt(keyPem, bufferToEncrypt); + + decryptedBuffer = crypto.publicDecrypt(keyPem, encryptedBuffer); + assert.strictEqual(decryptedBuffer.toString(), input); + + assert.throws(() => { + crypto.privateDecrypt({ + key: rsaKeyPemEncrypted, + passphrase: 'wrong' + }, bufferToEncrypt); + }, decryptError); + + assert.throws(() => { + crypto.publicEncrypt({ + key: rsaKeyPemEncrypted, + passphrase: 'wrong' + }, encryptedBuffer); + }, decryptError); + + encryptedBuffer = crypto.privateEncrypt({ + key: rsaKeyPemEncrypted, + passphrase: Buffer.from('password') + }, bufferToEncrypt); + + assert.throws(() => { + crypto.publicDecrypt({ + key: rsaKeyPemEncrypted, + passphrase: Buffer.from('wrong') + }, encryptedBuffer); + }, decryptError); +} + +function test_rsa(padding, encryptOaepHash, decryptOaepHash) { + const size = (padding === 'RSA_NO_PADDING') ? rsaKeySize / 8 : 32; + const input = Buffer.allocUnsafe(size); + for (let i = 0; i < input.length; i++) + input[i] = (i * 7 + 11) & 0xff; + const bufferToEncrypt = Buffer.from(input); + + padding = constants[padding]; + + const encryptedBuffer = crypto.publicEncrypt({ + key: rsaPubPem, + padding: padding, + oaepHash: encryptOaepHash + }, bufferToEncrypt); + + let decryptedBuffer = crypto.privateDecrypt({ + key: rsaKeyPem, + padding: padding, + oaepHash: decryptOaepHash + }, encryptedBuffer); + assert.deepStrictEqual(decryptedBuffer, input); + + decryptedBuffer = crypto.privateDecrypt({ + key: rsaPkcs8KeyPem, + padding: padding, + oaepHash: decryptOaepHash + }, encryptedBuffer); + assert.deepStrictEqual(decryptedBuffer, input); +} + +test_rsa('RSA_NO_PADDING'); +test_rsa('RSA_PKCS1_PADDING'); +test_rsa('RSA_PKCS1_OAEP_PADDING'); + +// Test OAEP with different hash functions. +test_rsa('RSA_PKCS1_OAEP_PADDING', undefined, 'sha1'); +test_rsa('RSA_PKCS1_OAEP_PADDING', 'sha1', undefined); +test_rsa('RSA_PKCS1_OAEP_PADDING', 'sha256', 'sha256'); +test_rsa('RSA_PKCS1_OAEP_PADDING', 'sha512', 'sha512'); +assert.throws(() => { + test_rsa('RSA_PKCS1_OAEP_PADDING', 'sha256', 'sha512'); +}, { + code: 'ERR_OSSL_RSA_OAEP_DECODING_ERROR' +}); + +// The following RSA-OAEP test cases were created using the WebCrypto API to +// ensure compatibility when using non-SHA1 hash functions. +{ + const { decryptionTests } = + JSON.parse(fixtures.readSync('rsa-oaep-test-vectors.js', 'utf8')); + + for (const { ct, oaepHash, oaepLabel } of decryptionTests) { + const label = oaepLabel ? Buffer.from(oaepLabel, 'hex') : undefined; + const copiedLabel = oaepLabel ? getBufferCopy(label) : undefined; + + const decrypted = crypto.privateDecrypt({ + key: rsaPkcs8KeyPem, + oaepHash, + oaepLabel: oaepLabel ? label : undefined + }, Buffer.from(ct, 'hex')); + + assert.strictEqual(decrypted.toString('utf8'), 'Hello Node.js'); + + const otherDecrypted = crypto.privateDecrypt({ + key: rsaPkcs8KeyPem, + oaepHash, + oaepLabel: copiedLabel + }, Buffer.from(ct, 'hex')); + + assert.strictEqual(otherDecrypted.toString('utf8'), 'Hello Node.js'); + } +} + +// Test invalid oaepHash and oaepLabel options. +for (const fn of [crypto.publicEncrypt, crypto.privateDecrypt]) { + assert.throws(() => { + fn({ + key: rsaPubPem, + oaepHash: 'Hello world' + }, Buffer.alloc(10)); + }, { + code: 'ERR_OSSL_EVP_INVALID_DIGEST' + }); + + for (const oaepHash of [0, false, null, Symbol(), () => {}]) { + assert.throws(() => { + fn({ + key: rsaPubPem, + oaepHash + }, Buffer.alloc(10)); + }, { + code: 'ERR_INVALID_ARG_TYPE' + }); + } + + for (const oaepLabel of [0, false, null, Symbol(), () => {}, {}]) { + assert.throws(() => { + fn({ + key: rsaPubPem, + oaepLabel + }, Buffer.alloc(10)); + }, { + code: 'ERR_INVALID_ARG_TYPE' + }); + } +} + +// Test RSA key signing/verification +let rsaSign = crypto.createSign('SHA1'); +let rsaVerify = crypto.createVerify('SHA1'); +assert.ok(rsaSign); +assert.ok(rsaVerify); + +const expectedSignature = fixtures.readKey( + 'rsa_public_sha1_signature_signedby_rsa_private_pkcs8.sha1', + 'hex' +); + +rsaSign.update(rsaPubPem); +let rsaSignature = rsaSign.sign(rsaKeyPem, 'hex'); +assert.strictEqual(rsaSignature, expectedSignature); + +rsaVerify.update(rsaPubPem); +assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true); + +// Test RSA PKCS#8 key signing/verification +rsaSign = crypto.createSign('SHA1'); +rsaSign.update(rsaPubPem); +rsaSignature = rsaSign.sign(rsaPkcs8KeyPem, 'hex'); +assert.strictEqual(rsaSignature, expectedSignature); + +rsaVerify = crypto.createVerify('SHA1'); +rsaVerify.update(rsaPubPem); +assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true); + +// Test RSA key signing/verification with encrypted key +rsaSign = crypto.createSign('SHA1'); +rsaSign.update(rsaPubPem); +const signOptions = { key: rsaKeyPemEncrypted, passphrase: 'password' }; +rsaSignature = rsaSign.sign(signOptions, 'hex'); +assert.strictEqual(rsaSignature, expectedSignature); + +rsaVerify = crypto.createVerify('SHA1'); +rsaVerify.update(rsaPubPem); +assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true); + +rsaSign = crypto.createSign('SHA1'); +rsaSign.update(rsaPubPem); +assert.throws(() => { + const signOptions = { key: rsaKeyPemEncrypted, passphrase: 'wrong' }; + rsaSign.sign(signOptions, 'hex'); +}, decryptPrivateKeyError); + +// +// Test RSA signing and verification +// +{ + const privateKey = fixtures.readKey('rsa_private_b.pem'); + const publicKey = fixtures.readKey('rsa_public_b.pem'); + + const input = 'I AM THE WALRUS'; + + const signature = fixtures.readKey( + 'I_AM_THE_WALRUS_sha256_signature_signedby_rsa_private_b.sha256', + 'hex' + ); + + const sign = crypto.createSign('SHA256'); + sign.update(input); + + const output = sign.sign(privateKey, 'hex'); + assert.strictEqual(output, signature); + + const verify = crypto.createVerify('SHA256'); + verify.update(input); + + assert.strictEqual(verify.verify(publicKey, signature, 'hex'), true); + + // Test the legacy signature algorithm name. + const sign2 = crypto.createSign('RSA-SHA256'); + sign2.update(input); + + const output2 = sign2.sign(privateKey, 'hex'); + assert.strictEqual(output2, signature); + + const verify2 = crypto.createVerify('SHA256'); + verify2.update(input); + + assert.strictEqual(verify2.verify(publicKey, signature, 'hex'), true); +} + + +// +// Test DSA signing and verification +// +{ + const input = 'I AM THE WALRUS'; + + // DSA signatures vary across runs so there is no static string to verify + // against. + const sign = crypto.createSign('SHA1'); + sign.update(input); + const signature = sign.sign(dsaKeyPem, 'hex'); + + const verify = crypto.createVerify('SHA1'); + verify.update(input); + + assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true); + + // Test the legacy 'DSS1' name. + const sign2 = crypto.createSign('DSS1'); + sign2.update(input); + const signature2 = sign2.sign(dsaKeyPem, 'hex'); + + const verify2 = crypto.createVerify('DSS1'); + verify2.update(input); + + assert.strictEqual(verify2.verify(dsaPubPem, signature2, 'hex'), true); +} + + +// +// Test DSA signing and verification with PKCS#8 private key +// +{ + const input = 'I AM THE WALRUS'; + + // DSA signatures vary across runs so there is no static string to verify + // against. + const sign = crypto.createSign('SHA1'); + sign.update(input); + const signature = sign.sign(dsaPkcs8KeyPem, 'hex'); + + const verify = crypto.createVerify('SHA1'); + verify.update(input); + + assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true); +} + + +// +// Test DSA signing and verification with encrypted key +// +const input = 'I AM THE WALRUS'; + +{ + const sign = crypto.createSign('SHA1'); + sign.update(input); + assert.throws(() => { + sign.sign({ key: dsaKeyPemEncrypted, passphrase: 'wrong' }, 'hex'); + }, decryptPrivateKeyError); +} + +{ + // DSA signatures vary across runs so there is no static string to verify + // against. + const sign = crypto.createSign('SHA1'); + sign.update(input); + const signOptions = { key: dsaKeyPemEncrypted, passphrase: 'password' }; + const signature = sign.sign(signOptions, 'hex'); + + const verify = crypto.createVerify('SHA1'); + verify.update(input); + + assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true); +} diff --git a/test/crypto/test-crypto-scrypt.js b/test/crypto/test-crypto-scrypt.js new file mode 100644 index 0000000..ee63873 --- /dev/null +++ b/test/crypto/test-crypto-scrypt.js @@ -0,0 +1,266 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// Flags: --expose-internals +'use strict'; +import common from '../common'; +if (!common.hasCrypto) + common.skip('missing crypto'); + +import assert from'assert'; +import crypto from 'crypto'; + +const good = [ + // Zero-length key is legal, functions as a parameter validation check. + { + pass: '', + salt: '', + keylen: 0, + N: 16, + p: 1, + r: 1, + expected: '', + }, + // Test vectors from https://tools.ietf.org/html/rfc7914#page-13 that + // should pass. Note that the test vector with N=1048576 is omitted + // because it takes too long to complete and uses over 1 GiB of memory. + { + pass: '', + salt: '', + keylen: 64, + N: 16, + p: 1, + r: 1, + expected: + '77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442' + + 'fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906', + }, + { + pass: 'password', + salt: 'NaCl', + keylen: 64, + N: 1024, + p: 16, + r: 8, + expected: + 'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162' + + '2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640', + }, + { + pass: 'pleaseletmein', + salt: 'SodiumChloride', + keylen: 64, + N: 16384, + p: 1, + r: 8, + expected: + '7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2' + + 'd5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887', + }, + { + pass: '', + salt: '', + keylen: 64, + cost: 16, + parallelization: 1, + blockSize: 1, + expected: + '77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442' + + 'fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906', + }, + { + pass: 'password', + salt: 'NaCl', + keylen: 64, + cost: 1024, + parallelization: 16, + blockSize: 8, + expected: + 'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162' + + '2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640', + }, + { + pass: 'pleaseletmein', + salt: 'SodiumChloride', + keylen: 64, + cost: 16384, + parallelization: 1, + blockSize: 8, + expected: + '7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2' + + 'd5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887', + }, +]; + +// Test vectors that should fail. +const bad = [ + { N: 1, p: 1, r: 1 }, // N < 2 + { N: 3, p: 1, r: 1 }, // Not power of 2. + { N: 1, cost: 1 }, // Both N and cost + { p: 1, parallelization: 1 }, // Both p and parallelization + { r: 1, blockSize: 1 }, // Both r and blocksize +]; + +// Test vectors where 128*N*r exceeds maxmem. +const toobig = [ + { N: 2 ** 16, p: 1, r: 1 }, // N >= 2**(r*16) + { N: 2, p: 2 ** 30, r: 1 }, // p > (2**30-1)/r + { N: 2 ** 20, p: 1, r: 8 }, + { N: 2 ** 10, p: 1, r: 8, maxmem: 2 ** 20 }, +]; + +const badargs = [ + { + args: [], + expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"password"/ }, + }, + { + args: [null], + expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"password"/ }, + }, + { + args: [''], + expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"salt"/ }, + }, + { + args: ['', null], + expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"salt"/ }, + }, + { + args: ['', ''], + expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"keylen"/ }, + }, + { + args: ['', '', null], + expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"keylen"/ }, + }, + { + args: ['', '', .42], + expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, + }, + { + args: ['', '', -42], + expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, + }, + { + args: ['', '', 2147485780], + expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, + }, +]; + +for (const options of good) { + const { pass, salt, keylen, expected } = options; + const actual = crypto.scryptSync(pass, salt, keylen, options); + assert.strictEqual(actual.toString('hex'), expected); + crypto.scrypt(pass, salt, keylen, options, common.mustSucceed((actual) => { + assert.strictEqual(actual.toString('hex'), expected); + })); +} + +for (const options of bad) { + const expected = { + message: /Invalid scrypt param/, + }; + assert.throws(() => crypto.scrypt('pass', 'salt', 1, options, () => {}), + expected); + assert.throws(() => crypto.scryptSync('pass', 'salt', 1, options), + expected); +} + +for (const options of toobig) { + const expected = { + message: /Invalid scrypt param/ + }; + assert.throws(() => crypto.scrypt('pass', 'salt', 1, options, () => {}), + expected); + assert.throws(() => crypto.scryptSync('pass', 'salt', 1, options), + expected); +} + +{ + const defaults = { N: 16384, p: 1, r: 8 }; + const expected = crypto.scryptSync('pass', 'salt', 1, defaults); + const actual = crypto.scryptSync('pass', 'salt', 1); + assert.deepStrictEqual(actual.toString('hex'), expected.toString('hex')); + crypto.scrypt('pass', 'salt', 1, common.mustSucceed((actual) => { + assert.deepStrictEqual(actual.toString('hex'), expected.toString('hex')); + })); +} +/* deprecated +{ + const defaultEncoding = crypto.DEFAULT_ENCODING; + const defaults = { N: 16384, p: 1, r: 8 }; + const expected = crypto.scryptSync('pass', 'salt', 1, defaults); + + const testEncoding = 'latin1'; + crypto.DEFAULT_ENCODING = testEncoding; + const actual = crypto.scryptSync('pass', 'salt', 1); + assert.deepStrictEqual(actual, expected.toString(testEncoding)); + + crypto.scrypt('pass', 'salt', 1, common.mustSucceed((actual) => { + assert.deepStrictEqual(actual, expected.toString(testEncoding)); + })); + + crypto.DEFAULT_ENCODING = defaultEncoding; +} +*/ +for (const { args, expected } of badargs) { + assert.throws(() => crypto.scrypt(...args), expected); + assert.throws(() => crypto.scryptSync(...args), expected); +} + +{ + const expected = { code: 'ERR_INVALID_ARG_TYPE' }; + assert.throws(() => crypto.scrypt('', '', 42, null), expected); + assert.throws(() => crypto.scrypt('', '', 42, {}, null), expected); + assert.throws(() => crypto.scrypt('', '', 42, {}), expected); + assert.throws(() => crypto.scrypt('', '', 42, {}, {}), expected); +} + +{ + // Values for maxmem that do not fit in 32 bits but that are still safe + // integers should be allowed. + crypto.scrypt('', '', 4, { maxmem: 2 ** 52 }, + common.mustSucceed((actual) => { + assert.strictEqual(actual.toString('hex'), 'd72c87d0'); + })); + + // Values that exceed Number.isSafeInteger should not be allowed. + assert.throws(() => crypto.scryptSync('', '', 0, { maxmem: 2 ** 53 }), { + code: 'ERR_OUT_OF_RANGE' + }); +} + +{ + // Regression test for https://github.com/nodejs/node/issues/28836. + + function testParameter(name, value) { + let accessCount = 0; + + // Find out how often the value is accessed. + crypto.scryptSync('', '', 1, { + get [name]() { + accessCount++; + return value; + } + }); + + // Try to crash the process on the last access. + assert.throws(() => { + crypto.scryptSync('', '', 1, { + get [name]() { + if (--accessCount === 0) + return ''; + return value; + } + }); + }, { + code: 'ERR_INVALID_ARG_TYPE' + }); + } + + [ + ['N', 16384], ['cost', 16384], + ['r', 8], ['blockSize', 8], + ['p', 1], ['parallelization', 1], + ].forEach((arg) => testParameter(...arg)); +} diff --git a/test/crypto/test-crypto-secret-keygen.js b/test/crypto/test-crypto-secret-keygen.js new file mode 100644 index 0000000..119291b --- /dev/null +++ b/test/crypto/test-crypto-secret-keygen.js @@ -0,0 +1,139 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); + +const { + generateKey, + generateKeySync +} = require('crypto'); + +[1, true, [], {}, Infinity, null, undefined].forEach((i) => { + assert.throws(() => generateKey(i, 1, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "type" argument must be / + }); + assert.throws(() => generateKeySync(i, 1), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "type" argument must be / + }); +}); + +['', true, [], null, undefined].forEach((i) => { + assert.throws(() => generateKey('aes', i, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options" argument must be / + }); + assert.throws(() => generateKeySync('aes', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options" argument must be / + }); +}); + +['', true, {}, [], null, undefined].forEach((length) => { + assert.throws(() => generateKey('hmac', { length }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.length" property must be / + }); + assert.throws(() => generateKeySync('hmac', { length }), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.length" property must be / + }); +}); + +assert.throws(() => generateKey('aes', { length: 256 }), { + code: 'ERR_INVALID_ARG_TYPE' +}); + +assert.throws(() => generateKey('hmac', { length: -1 }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws(() => generateKey('hmac', { length: 4 }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws(() => generateKey('hmac', { length: 7 }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws( + () => generateKey('hmac', { length: 2 ** 31 }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' + }); + +assert.throws(() => generateKeySync('hmac', { length: -1 }), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws(() => generateKeySync('hmac', { length: 4 }), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws(() => generateKeySync('hmac', { length: 7 }), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws( + () => generateKeySync('hmac', { length: 2 ** 31 }), { + code: 'ERR_OUT_OF_RANGE' + }); + +assert.throws(() => generateKeySync('aes', { length: 123 }), { + code: 'ERR_INVALID_ARG_VALUE', + message: /The property 'options\.length' must be one of: 128, 192, 256/ +}); + +{ + const key = generateKeySync('aes', { length: 128 }); + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, 128 / 8); + + generateKey('aes', { length: 128 }, common.mustSucceed((key) => { + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, 128 / 8); + })); +} + +{ + const key = generateKeySync('aes', { length: 256 }); + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, 256 / 8); + + generateKey('aes', { length: 256 }, common.mustSucceed((key) => { + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, 256 / 8); + })); +} + +{ + const key = generateKeySync('hmac', { length: 123 }); + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, Math.floor(123 / 8)); + + generateKey('hmac', { length: 123 }, common.mustSucceed((key) => { + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, Math.floor(123 / 8)); + })); +} + +assert.throws( + () => generateKey('unknown', { length: 123 }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_VALUE', + message: /The argument 'type' must be a supported key type/ + }); + +assert.throws(() => generateKeySync('unknown', { length: 123 }), { + code: 'ERR_INVALID_ARG_VALUE', + message: /The argument 'type' must be a supported key type/ +}); diff --git a/test/crypto/test-crypto-secure-heap.js b/test/crypto/test-crypto-secure-heap.js new file mode 100644 index 0000000..76141b3 --- /dev/null +++ b/test/crypto/test-crypto-secure-heap.js @@ -0,0 +1,78 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +if (common.isWindows) + common.skip('Not supported on Windows'); + +if (process.config.variables.asan) + common.skip('ASAN does not play well with secure heap allocations'); + +const assert = require('assert'); +const { fork } = require('child_process'); +const fixtures = require('../common/fixtures'); +const { + secureHeapUsed, + createDiffieHellman, +} = require('crypto'); + +if (process.argv[2] === 'child') { + + const a = secureHeapUsed(); + + assert(a); + assert.strictEqual(typeof a, 'object'); + assert.strictEqual(a.total, 65536); + assert.strictEqual(a.min, 4); + assert.strictEqual(a.used, 0); + + { + const size = common.hasFipsCrypto || common.hasOpenSSL3 ? 1024 : 256; + const dh1 = createDiffieHellman(size); + const p1 = dh1.getPrime('buffer'); + const dh2 = createDiffieHellman(p1, 'buffer'); + const key1 = dh1.generateKeys(); + const key2 = dh2.generateKeys('hex'); + dh1.computeSecret(key2, 'hex', 'base64'); + dh2.computeSecret(key1, 'latin1', 'buffer'); + + const b = secureHeapUsed(); + assert(b); + assert.strictEqual(typeof b, 'object'); + assert.strictEqual(b.total, 65536); + assert.strictEqual(b.min, 4); + // The amount used can vary on a number of factors + assert(b.used > 0); + assert(b.utilization > 0.0); + } + + return; +} + +const child = fork( + process.argv[1], + ['child'], + { execArgv: ['--secure-heap=65536', '--secure-heap-min=4'] }); + +child.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); +})); + +{ + const child = fork(fixtures.path('a.js'), { + execArgv: ['--secure-heap=3', '--secure-heap-min=3'], + stdio: 'pipe' + }); + let res = ''; + child.on('exit', common.mustCall((code) => { + assert.notStrictEqual(code, 0); + assert.match(res, /--secure-heap must be a power of 2/); + assert.match(res, /--secure-heap-min must be a power of 2/); + })); + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (chunk) => res += chunk); +} diff --git a/test/crypto/test-crypto-sign-verify.js b/test/crypto/test-crypto-sign-verify.js new file mode 100644 index 0000000..c994a96 --- /dev/null +++ b/test/crypto/test-crypto-sign-verify.js @@ -0,0 +1,778 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const exec = require('child_process').exec; +const crypto = require('crypto'); +const fixtures = require('../common/fixtures'); + +// Test certificates +const certPem = fixtures.readKey('rsa_cert.crt'); +const keyPem = fixtures.readKey('rsa_private.pem'); +const keySize = 2048; + +{ + const Sign = crypto.Sign; + const instance = Sign('SHA256'); + assert(instance instanceof Sign, 'Sign is expected to return a new ' + + 'instance when called without `new`'); +} + +{ + const Verify = crypto.Verify; + const instance = Verify('SHA256'); + assert(instance instanceof Verify, 'Verify is expected to return a new ' + + 'instance when called without `new`'); +} + +// Test handling of exceptional conditions +{ + const library = { + configurable: true, + set() { + throw new Error('bye, bye, library'); + } + }; + Object.defineProperty(Object.prototype, 'library', library); + + assert.throws(() => { + crypto.createSign('sha1').sign( + `-----BEGIN RSA PRIVATE KEY----- + AAAAAAAAAAAA + -----END RSA PRIVATE KEY-----`); + }, { message: 'bye, bye, library' }); + + delete Object.prototype.library; + + const errorStack = { + configurable: true, + set() { + throw new Error('bye, bye, error stack'); + } + }; + Object.defineProperty(Object.prototype, 'opensslErrorStack', errorStack); + + assert.throws(() => { + crypto.createSign('SHA1') + .update('Test123') + .sign({ + key: keyPem, + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING + }); + }, { message: common.hasOpenSSL3 ? + 'error:1C8000A5:Provider routines::illegal or unsupported padding mode' : + 'bye, bye, error stack' }); + + delete Object.prototype.opensslErrorStack; +} + +assert.throws( + () => crypto.createVerify('SHA256').verify({ + key: certPem, + padding: null, + }, ''), + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: "The property 'options.padding' is invalid. Received null", + }); + +assert.throws( + () => crypto.createVerify('SHA256').verify({ + key: certPem, + saltLength: null, + }, ''), + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: "The property 'options.saltLength' is invalid. Received null", + }); + +// Test signing and verifying +{ + const s1 = crypto.createSign('SHA1') + .update('Test123') + .sign(keyPem, 'base64'); + let s1stream = crypto.createSign('SHA1'); + s1stream.end('Test123'); + s1stream = s1stream.sign(keyPem, 'base64'); + assert.strictEqual(s1, s1stream, `${s1} should equal ${s1stream}`); + + const verified = crypto.createVerify('SHA1') + .update('Test') + .update('123') + .verify(certPem, s1, 'base64'); + assert.strictEqual(verified, true); +} + +{ + const s2 = crypto.createSign('SHA256') + .update('Test123') + .sign(keyPem, 'latin1'); + let s2stream = crypto.createSign('SHA256'); + s2stream.end('Test123'); + s2stream = s2stream.sign(keyPem, 'latin1'); + assert.strictEqual(s2, s2stream, `${s2} should equal ${s2stream}`); + + let verified = crypto.createVerify('SHA256') + .update('Test') + .update('123') + .verify(certPem, s2, 'latin1'); + assert.strictEqual(verified, true); + + const verStream = crypto.createVerify('SHA256'); + verStream.write('Tes'); + verStream.write('t12'); + verStream.end('3'); + verified = verStream.verify(certPem, s2, 'latin1'); + assert.strictEqual(verified, true); +} + +{ + const s3 = crypto.createSign('SHA1') + .update('Test123') + .sign(keyPem, 'buffer'); + let verified = crypto.createVerify('SHA1') + .update('Test') + .update('123') + .verify(certPem, s3); + assert.strictEqual(verified, true); + + const verStream = crypto.createVerify('SHA1'); + verStream.write('Tes'); + verStream.write('t12'); + verStream.end('3'); + verified = verStream.verify(certPem, s3); + assert.strictEqual(verified, true); +} + +// Special tests for RSA_PKCS1_PSS_PADDING +{ + function testPSS(algo, hLen) { + // Maximum permissible salt length + const max = keySize / 8 - hLen - 2; + + function getEffectiveSaltLength(saltLength) { + switch (saltLength) { + case crypto.constants.RSA_PSS_SALTLEN_DIGEST: + return hLen; + case crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN: + return max; + default: + return saltLength; + } + } + + const signSaltLengths = [ + crypto.constants.RSA_PSS_SALTLEN_DIGEST, + getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_DIGEST), + crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN, + getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN), + 0, 16, 32, 64, 128, + ]; + + const verifySaltLengths = [ + crypto.constants.RSA_PSS_SALTLEN_DIGEST, + getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_DIGEST), + getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN), + 0, 16, 32, 64, 128, + ]; + const errMessage = /^Error:.*data too large for key size$/; + + const data = Buffer.from('Test123'); + + signSaltLengths.forEach((signSaltLength) => { + if (signSaltLength > max) { + // If the salt length is too big, an Error should be thrown + assert.throws(() => { + crypto.createSign(algo) + .update(data) + .sign({ + key: keyPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: signSaltLength + }); + }, errMessage); + assert.throws(() => { + crypto.sign(algo, data, { + key: keyPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: signSaltLength + }); + }, errMessage); + } else { + // Otherwise, a valid signature should be generated + const s4 = crypto.createSign(algo) + .update(data) + .sign({ + key: keyPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: signSaltLength + }); + const s4_2 = crypto.sign(algo, data, { + key: keyPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: signSaltLength + }); + + [s4, s4_2].forEach((sig) => { + let verified; + verifySaltLengths.forEach((verifySaltLength) => { + // Verification should succeed if and only if the salt length is + // correct + verified = crypto.createVerify(algo) + .update(data) + .verify({ + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: verifySaltLength + }, sig); + assert.strictEqual(verified, crypto.verify(algo, data, { + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: verifySaltLength + }, sig)); + const saltLengthCorrect = getEffectiveSaltLength(signSaltLength) === + getEffectiveSaltLength(verifySaltLength); + assert.strictEqual(verified, saltLengthCorrect); + }); + + // Verification using RSA_PSS_SALTLEN_AUTO should always work + verified = crypto.createVerify(algo) + .update(data) + .verify({ + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO + }, sig); + assert.strictEqual(verified, true); + assert.strictEqual(verified, crypto.verify(algo, data, { + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO + }, sig)); + + // Verifying an incorrect message should never work + const wrongData = Buffer.from('Test1234'); + verified = crypto.createVerify(algo) + .update(wrongData) + .verify({ + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO + }, sig); + assert.strictEqual(verified, false); + assert.strictEqual(verified, crypto.verify(algo, wrongData, { + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO + }, sig)); + }); + } + }); + } + + testPSS('SHA1', 20); + testPSS('SHA256', 32); +} + +// Test vectors for RSA_PKCS1_PSS_PADDING provided by the RSA Laboratories: +// https://www.emc.com/emc-plus/rsa-labs/standards-initiatives/pkcs-rsa-cryptography-standard.htm +{ + // We only test verification as we cannot specify explicit salts when signing + function testVerify(cert, vector) { + const verified = crypto.createVerify('SHA1') + .update(Buffer.from(vector.message, 'hex')) + .verify({ + key: cert, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: vector.salt.length / 2 + }, vector.signature, 'hex'); + assert.strictEqual(verified, true); + } + + const examples = JSON.parse(fixtures.readSync('pss-vectors.json', 'utf8')); + + for (const key in examples) { + const example = examples[key]; + const publicKey = example.publicKey.join('\n'); + example.tests.forEach((test) => testVerify(publicKey, test)); + } +} + +// Test exceptions for invalid `padding` and `saltLength` values +{ + [null, NaN, 'boom', {}, [], true, false] + .forEach((invalidValue) => { + assert.throws(() => { + crypto.createSign('SHA256') + .update('Test123') + .sign({ + key: keyPem, + padding: invalidValue + }); + }, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError' + }); + + assert.throws(() => { + crypto.createSign('SHA256') + .update('Test123') + .sign({ + key: keyPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: invalidValue + }); + }, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError' + }); + }); + + assert.throws(() => { + crypto.createSign('SHA1') + .update('Test123') + .sign({ + key: keyPem, + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING + }); + }, common.hasOpenSSL3 ? { + code: 'ERR_OSSL_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE', + message: /illegal or unsupported padding mode/, + } : { + code: 'ERR_OSSL_RSA_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE', + message: /illegal or unsupported padding mode/, + opensslErrorStack: [ + 'error:06089093:digital envelope routines:EVP_PKEY_CTX_ctrl:' + + 'command not supported', + ], + }); +} + +// Test throws exception when key options is null +{ + assert.throws(() => { + crypto.createSign('SHA1').update('Test123').sign(null, 'base64'); + }, { + code: 'ERR_CRYPTO_SIGN_KEY_REQUIRED', + name: 'Error' + }); +} + +{ + const sign = crypto.createSign('SHA1'); + const verify = crypto.createVerify('SHA1'); + + [1, [], {}, undefined, null, true, Infinity].forEach((input) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "algorithm" argument must be of type string.' + + `${common.invalidArgTypeHelper(input)}` + }; + assert.throws(() => crypto.createSign(input), errObj); + assert.throws(() => crypto.createVerify(input), errObj); + + errObj.message = 'The "data" argument must be of type string or an ' + + 'instance of Buffer, TypedArray, or DataView.' + + common.invalidArgTypeHelper(input); + assert.throws(() => sign.update(input), errObj); + assert.throws(() => verify.update(input), errObj); + assert.throws(() => sign._write(input, 'utf8', () => {}), errObj); + assert.throws(() => verify._write(input, 'utf8', () => {}), errObj); + }); + + [ + Uint8Array, Uint16Array, Uint32Array, Float32Array, Float64Array, + ].forEach((clazz) => { + // These should all just work + sign.update(new clazz()); + verify.update(new clazz()); + }); + + [1, {}, [], Infinity].forEach((input) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }; + assert.throws(() => sign.sign(input), errObj); + assert.throws(() => verify.verify(input), errObj); + assert.throws(() => verify.verify('test', input), errObj); + }); +} + +{ + assert.throws( + () => crypto.createSign('sha8'), + /Invalid digest/); + assert.throws( + () => crypto.sign('sha8', Buffer.alloc(1), keyPem), + /Invalid digest/); +} + +[ + { private: fixtures.readKey('ed25519_private.pem', 'ascii'), + public: fixtures.readKey('ed25519_public.pem', 'ascii'), + algo: null, + sigLen: 64 }, + { private: fixtures.readKey('ed448_private.pem', 'ascii'), + public: fixtures.readKey('ed448_public.pem', 'ascii'), + algo: null, + sigLen: 114 }, + { private: fixtures.readKey('rsa_private_2048.pem', 'ascii'), + public: fixtures.readKey('rsa_public_2048.pem', 'ascii'), + algo: 'sha1', + sigLen: 256 }, +].forEach((pair) => { + const algo = pair.algo; + + { + const data = Buffer.from('Hello world'); + const sig = crypto.sign(algo, data, pair.private); + assert.strictEqual(sig.length, pair.sigLen); + + assert.strictEqual(crypto.verify(algo, data, pair.private, sig), + true); + assert.strictEqual(crypto.verify(algo, data, pair.public, sig), + true); + } + + { + const data = Buffer.from('Hello world'); + const privKeyObj = crypto.createPrivateKey(pair.private); + const pubKeyObj = crypto.createPublicKey(pair.public); + + const sig = crypto.sign(algo, data, privKeyObj); + assert.strictEqual(sig.length, pair.sigLen); + + assert.strictEqual(crypto.verify(algo, data, privKeyObj, sig), true); + assert.strictEqual(crypto.verify(algo, data, pubKeyObj, sig), true); + } + + { + const data = Buffer.from('Hello world'); + const otherData = Buffer.from('Goodbye world'); + const otherSig = crypto.sign(algo, otherData, pair.private); + assert.strictEqual(crypto.verify(algo, data, pair.private, otherSig), + false); + } + + [ + Uint8Array, Uint16Array, Uint32Array, Float32Array, Float64Array, + ].forEach((clazz) => { + const data = new clazz(); + const sig = crypto.sign(algo, data, pair.private); + assert.strictEqual(crypto.verify(algo, data, pair.private, sig), + true); + }); +}); + +[1, {}, [], true, Infinity].forEach((input) => { + const data = Buffer.alloc(1); + const sig = Buffer.alloc(1); + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }; + + assert.throws(() => crypto.sign(null, input, 'asdf'), errObj); + assert.throws(() => crypto.verify(null, input, 'asdf', sig), errObj); + + assert.throws(() => crypto.sign(null, data, input), errObj); + assert.throws(() => crypto.verify(null, data, input, sig), errObj); + + errObj.message = 'The "signature" argument must be an instance of ' + + 'Buffer, TypedArray, or DataView.' + + common.invalidArgTypeHelper(input); + assert.throws(() => crypto.verify(null, data, 'test', input), errObj); +}); + +{ + const data = Buffer.from('Hello world'); + const keys = [['ec-key.pem', 64], ['dsa_private_1025.pem', 40]]; + + for (const [file, length] of keys) { + const privKey = fixtures.readKey(file); + [ + crypto.createSign('sha1').update(data).sign(privKey), + crypto.sign('sha1', data, privKey), + crypto.sign('sha1', data, { key: privKey, dsaEncoding: 'der' }), + ].forEach((sig) => { + // Signature length variability due to DER encoding + assert(sig.length >= length + 4 && sig.length <= length + 8); + + assert.strictEqual( + crypto.createVerify('sha1').update(data).verify(privKey, sig), + true + ); + assert.strictEqual(crypto.verify('sha1', data, privKey, sig), true); + }); + + // Test (EC)DSA signature conversion. + const opts = { key: privKey, dsaEncoding: 'ieee-p1363' }; + let sig = crypto.sign('sha1', data, opts); + // Unlike DER signatures, IEEE P1363 signatures have a predictable length. + assert.strictEqual(sig.length, length); + assert.strictEqual(crypto.verify('sha1', data, opts, sig), true); + assert.strictEqual(crypto.createVerify('sha1') + .update(data) + .verify(opts, sig), true); + + // Test invalid signature lengths. + for (const i of [-2, -1, 1, 2, 4, 8]) { + sig = crypto.randomBytes(length + i); + let result; + try { + result = crypto.verify('sha1', data, opts, sig); + } catch (err) { + assert.match(err.message, /asn1 encoding/); + assert.strictEqual(err.library, 'asn1 encoding routines'); + continue; + } + assert.strictEqual(result, false); + } + } + + // Test verifying externally signed messages. + const extSig = Buffer.from('494c18ab5c8a62a72aea5041966902bcfa229821af2bf65' + + '0b5b4870d1fe6aebeaed9460c62210693b5b0a300033823' + + '33d9529c8abd8c5948940af944828be16c', 'hex'); + for (const ok of [true, false]) { + assert.strictEqual( + crypto.verify('sha256', data, { + key: fixtures.readKey('ec-key.pem'), + dsaEncoding: 'ieee-p1363' + }, extSig), + ok + ); + + assert.strictEqual( + crypto.createVerify('sha256').update(data).verify({ + key: fixtures.readKey('ec-key.pem'), + dsaEncoding: 'ieee-p1363' + }, extSig), + ok + ); + + extSig[Math.floor(Math.random() * extSig.length)] ^= 1; + } + + // Non-(EC)DSA keys should ignore the option. + const sig = crypto.sign('sha1', data, { + key: keyPem, + dsaEncoding: 'ieee-p1363' + }); + assert.strictEqual(crypto.verify('sha1', data, certPem, sig), true); + assert.strictEqual( + crypto.verify('sha1', data, { + key: certPem, + dsaEncoding: 'ieee-p1363' + }, sig), + true + ); + assert.strictEqual( + crypto.verify('sha1', data, { + key: certPem, + dsaEncoding: 'der' + }, sig), + true + ); + + for (const dsaEncoding of ['foo', null, {}, 5, true, NaN]) { + assert.throws(() => { + crypto.sign('sha1', data, { + key: certPem, + dsaEncoding + }); + }, { + code: 'ERR_INVALID_ARG_VALUE' + }); + } +} + + +// RSA-PSS Sign test by verifying with 'openssl dgst -verify' +// Note: this particular test *must* be the last in this file as it will exit +// early if no openssl binary is found +{ + if (!common.opensslCli) + common.skip('node compiled without OpenSSL CLI.'); + + const pubfile = fixtures.path('keys', 'rsa_public_2048.pem'); + const privkey = fixtures.readKey('rsa_private_2048.pem'); + + const msg = 'Test123'; + const s5 = crypto.createSign('SHA256') + .update(msg) + .sign({ + key: privkey, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING + }); + + const tmpdir = require('../common/tmpdir'); + tmpdir.refresh(); + + const sigfile = path.join(tmpdir.path, 's5.sig'); + fs.writeFileSync(sigfile, s5); + const msgfile = path.join(tmpdir.path, 's5.msg'); + fs.writeFileSync(msgfile, msg); + + const cmd = + `"${common.opensslCli}" dgst -sha256 -verify "${pubfile}" -signature "${ + sigfile}" -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-2 "${ + msgfile}"`; + + exec(cmd, common.mustCall((err, stdout, stderr) => { + assert(stdout.includes('Verified OK')); + })); +} + +{ + // Test RSA-PSS. + { + // This key pair does not restrict the message digest algorithm or salt + // length. + const publicPem = fixtures.readKey('rsa_pss_public_2048.pem'); + const privatePem = fixtures.readKey('rsa_pss_private_2048.pem'); + + const publicKey = crypto.createPublicKey(publicPem); + const privateKey = crypto.createPrivateKey(privatePem); + + for (const key of [privatePem, privateKey]) { + // Any algorithm should work. + for (const algo of ['sha1', 'sha256']) { + // Any salt length should work. + for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) { + const signature = crypto.sign(algo, 'foo', { key, saltLength }); + + for (const pkey of [key, publicKey, publicPem]) { + const okay = crypto.verify( + algo, + 'foo', + { key: pkey, saltLength }, + signature + ); + + assert.ok(okay); + } + } + } + } + } + + { + // This key pair enforces sha256 as the message digest and the MGF1 + // message digest and a salt length of at least 16 bytes. + const publicPem = + fixtures.readKey('rsa_pss_public_2048_sha256_sha256_16.pem'); + const privatePem = + fixtures.readKey('rsa_pss_private_2048_sha256_sha256_16.pem'); + + const publicKey = crypto.createPublicKey(publicPem); + const privateKey = crypto.createPrivateKey(privatePem); + + for (const key of [privatePem, privateKey]) { + // Signing with anything other than sha256 should fail. + assert.throws(() => { + crypto.sign('sha1', 'foo', key); + }, /digest not allowed/); + + // Signing with salt lengths less than 16 bytes should fail. + for (const saltLength of [8, 10, 12]) { + assert.throws(() => { + crypto.sign('sha256', 'foo', { key, saltLength }); + }, /pss saltlen too small/); + } + + // Signing with sha256 and appropriate salt lengths should work. + for (const saltLength of [undefined, 16, 18, 20]) { + const signature = crypto.sign('sha256', 'foo', { key, saltLength }); + + for (const pkey of [key, publicKey, publicPem]) { + const okay = crypto.verify( + 'sha256', + 'foo', + { key: pkey, saltLength }, + signature + ); + + assert.ok(okay); + } + } + } + } + + { + // This key enforces sha512 as the message digest and sha256 as the MGF1 + // message digest. + const publicPem = + fixtures.readKey('rsa_pss_public_2048_sha512_sha256_20.pem'); + const privatePem = + fixtures.readKey('rsa_pss_private_2048_sha512_sha256_20.pem'); + + const publicKey = crypto.createPublicKey(publicPem); + const privateKey = crypto.createPrivateKey(privatePem); + + // Node.js usually uses the same hash function for the message and for MGF1. + // However, when a different MGF1 message digest algorithm has been + // specified as part of the key, it should automatically switch to that. + // This behavior is required by sections 3.1 and 3.3 of RFC4055. + for (const key of [privatePem, privateKey]) { + // sha256 matches the MGF1 hash function and should be used internally, + // but it should not be permitted as the main message digest algorithm. + for (const algo of ['sha1', 'sha256']) { + assert.throws(() => { + crypto.sign(algo, 'foo', key); + }, /digest not allowed/); + } + + // sha512 should produce a valid signature. + const signature = crypto.sign('sha512', 'foo', key); + + for (const pkey of [key, publicKey, publicPem]) { + const okay = crypto.verify('sha512', 'foo', pkey, signature); + + assert.ok(okay); + } + } + } +} + +// The sign function should not swallow OpenSSL errors. +// Regression test for https://github.com/nodejs/node/issues/40794. +{ + assert.throws(() => { + const { privateKey } = crypto.generateKeyPairSync('rsa', { + modulusLength: 512 + }); + crypto.sign('sha512', 'message', privateKey); + }, { + code: 'ERR_OSSL_RSA_DIGEST_TOO_BIG_FOR_RSA_KEY', + message: /digest too big for rsa key/ + }); +} + +{ + // This should not cause a crash: https://github.com/nodejs/node/issues/44471 + for (const key of ['', 'foo', null, undefined, true, Boolean]) { + assert.throws(() => { + crypto.verify('sha256', 'foo', { key, format: 'jwk' }, Buffer.alloc(0)); + }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ }); + assert.throws(() => { + crypto.createVerify('sha256').verify({ key, format: 'jwk' }, Buffer.alloc(0)); + }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ }); + assert.throws(() => { + crypto.sign('sha256', 'foo', { key, format: 'jwk' }); + }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ }); + assert.throws(() => { + crypto.createSign('sha256').sign({ key, format: 'jwk' }); + }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ }); + } +} diff --git a/test/crypto/test-crypto-stream.js b/test/crypto/test-crypto-stream.js new file mode 100644 index 0000000..33908a6 --- /dev/null +++ b/test/crypto/test-crypto-stream.js @@ -0,0 +1,85 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const stream = require('stream'); +const crypto = require('crypto'); + +if (!common.hasFipsCrypto) { + // Small stream to buffer converter + class Stream2buffer extends stream.Writable { + constructor(callback) { + super(); + + this._buffers = []; + this.once('finish', function() { + callback(null, Buffer.concat(this._buffers)); + }); + } + + _write(data, encoding, done) { + this._buffers.push(data); + return done(null); + } + } + + // Create an md5 hash of "Hallo world" + const hasher1 = crypto.createHash('md5'); + hasher1.pipe(new Stream2buffer(common.mustCall(function end(err, hash) { + assert.strictEqual(err, null); + assert.strictEqual( + hash.toString('hex'), '06460dadb35d3d503047ce750ceb2d07' + ); + }))); + hasher1.end('Hallo world'); + + // Simpler check for unpipe, setEncoding, pause and resume + crypto.createHash('md5').unpipe({}); + crypto.createHash('md5').setEncoding('utf8'); + crypto.createHash('md5').pause(); + crypto.createHash('md5').resume(); +} + +// Decipher._flush() should emit an error event, not an exception. +const key = Buffer.from('48fb56eb10ffeb13fc0ef551bbca3b1b', 'hex'); +const badkey = Buffer.from('12341234123412341234123412341234', 'hex'); +const iv = Buffer.from('6d358219d1f488f5f4eb12820a66d146', 'hex'); +const cipher = crypto.createCipheriv('aes-128-cbc', key, iv); +const decipher = crypto.createDecipheriv('aes-128-cbc', badkey, iv); + +cipher.pipe(decipher) + .on('error', common.expectsError(common.hasOpenSSL3 ? { + message: /bad decrypt/, + library: 'Provider routines', + reason: 'bad decrypt', + } : { + message: /bad decrypt/, + function: 'EVP_DecryptFinal_ex', + library: 'digital envelope routines', + reason: 'bad decrypt', + })); + +cipher.end('Papaya!'); // Should not cause an unhandled exception. diff --git a/test/crypto/test-crypto-subtle-zero-length.js b/test/crypto/test-crypto-subtle-zero-length.js new file mode 100644 index 0000000..4bd4cd3 --- /dev/null +++ b/test/crypto/test-crypto-subtle-zero-length.js @@ -0,0 +1,41 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto').webcrypto; + +(async () => { + const k = await crypto.subtle.importKey( + 'raw', + new Uint8Array(32), + { name: 'AES-GCM' }, + false, + [ 'encrypt', 'decrypt' ]); + assert(k instanceof crypto.CryptoKey); + + const e = await crypto.subtle.encrypt({ + name: 'AES-GCM', + iv: new Uint8Array(12), + }, k, new Uint8Array(0)); + assert(e instanceof ArrayBuffer); + assert.deepStrictEqual( + Buffer.from(e), + Buffer.from([ + 0x53, 0x0f, 0x8a, 0xfb, 0xc7, 0x45, 0x36, 0xb9, + 0xa9, 0x63, 0xb4, 0xf1, 0xc4, 0xcb, 0x73, 0x8b ])); + + const v = await crypto.subtle.decrypt({ + name: 'AES-GCM', + iv: new Uint8Array(12), + }, k, e); + assert(v instanceof ArrayBuffer); + assert.strictEqual(v.byteLength, 0); +})().then(common.mustCall()).catch((e) => { + assert.ifError(e); +}); diff --git a/test/crypto/test-crypto-timing-safe-equal.js b/test/crypto/test-crypto-timing-safe-equal.js new file mode 100644 index 0000000..5218bb7 --- /dev/null +++ b/test/crypto/test-crypto-timing-safe-equal.js @@ -0,0 +1,95 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +import common from '../common'; +if (!common.hasCrypto) + common.skip('missing crypto'); + +import assert from 'assert'; +import crypto from 'crypto'; + +// 'should consider equal strings to be equal' +assert.strictEqual( + crypto.timingSafeEqual(Buffer.from('foo'), Buffer.from('foo')), + true +); + +// 'should consider unequal strings to be unequal' +assert.strictEqual( + crypto.timingSafeEqual(Buffer.from('foo'), Buffer.from('bar')), + false +); + +{ + // Test TypedArrays with different lengths but equal byteLengths. + const buf = crypto.randomBytes(16).buffer; + const a1 = new Uint8Array(buf); + const a2 = new Uint16Array(buf); + const a3 = new Uint32Array(buf); + + for (const left of [a1, a2, a3]) { + for (const right of [a1, a2, a3]) { + assert.strictEqual(crypto.timingSafeEqual(left, right), true); + } + } +} + +{ + // When the inputs are floating-point numbers, timingSafeEqual neither has + // equality nor SameValue semantics. It just compares the underlying bytes, + // ignoring the TypedArray type completely. + + const cmp = (fn) => (a, b) => a.every((x, i) => fn(x, b[i])); + const eq = cmp((a, b) => a === b); + const is = cmp(Object.is); + + function test(a, b, { equal, sameValue, timingSafeEqual }) { + assert.strictEqual(eq(a, b), equal); + assert.strictEqual(is(a, b), sameValue); + assert.strictEqual(crypto.timingSafeEqual(a, b), timingSafeEqual); + } + + test(new Float32Array([NaN]), new Float32Array([NaN]), { + equal: false, + sameValue: true, + timingSafeEqual: true + }); + + test(new Float64Array([0]), new Float64Array([-0]), { + equal: true, + sameValue: false, + timingSafeEqual: false + }); + + const x = new BigInt64Array([0x7ff0000000000001n, 0xfff0000000000001n]); + test(new Float64Array(x.buffer), new Float64Array([NaN, NaN]), { + equal: false, + sameValue: true, + timingSafeEqual: false + }); +} + +assert.throws( + () => crypto.timingSafeEqual(Buffer.from([1, 2, 3]), Buffer.from([1, 2])), + { + code: 'ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH', + name: 'RangeError', + message: 'Input buffers must have the same byte length' + } +); + +assert.throws( + () => crypto.timingSafeEqual('not a buffer', Buffer.from([1, 2])), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } +); + +assert.throws( + () => crypto.timingSafeEqual(Buffer.from([1, 2]), 'not a buffer'), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } +); diff --git a/test/crypto/test-crypto-update-encoding.js b/test/crypto/test-crypto-update-encoding.js new file mode 100644 index 0000000..c05b4ca --- /dev/null +++ b/test/crypto/test-crypto-update-encoding.js @@ -0,0 +1,24 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const crypto = require('crypto'); + +const zeros = Buffer.alloc; +const key = zeros(16); +const iv = zeros(16); + +const cipher = () => crypto.createCipheriv('aes-128-cbc', key, iv); +const decipher = () => crypto.createDecipheriv('aes-128-cbc', key, iv); +const hash = () => crypto.createSign('sha256'); +const hmac = () => crypto.createHmac('sha256', key); +const sign = () => crypto.createSign('sha256'); +const verify = () => crypto.createVerify('sha256'); + +for (const f of [cipher, decipher, hash, hmac, sign, verify]) + for (const n of [15, 16]) + f().update(zeros(n), 'hex'); // Should ignore inputEncoding. diff --git a/test/crypto/test-crypto-verify-failure.js b/test/crypto/test-crypto-verify-failure.js new file mode 100644 index 0000000..5352989 --- /dev/null +++ b/test/crypto/test-crypto-verify-failure.js @@ -0,0 +1,67 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const crypto = require('crypto'); +const tls = require('tls'); +const fixtures = require('../common/fixtures'); + +const certPem = fixtures.readKey('rsa_cert.crt'); + +const options = { + key: fixtures.readKey('agent1-key.pem'), + cert: fixtures.readKey('agent1-cert.pem') +}; + +const server = tls.Server(options, (socket) => { + setImmediate(() => { + verify(); + setImmediate(() => { + socket.destroy(); + }); + }); +}); + +function verify() { + crypto.createVerify('SHA1') + .update('Test') + .verify(certPem, 'asdfasdfas', 'base64'); +} + +server.listen(0, common.mustCall(() => { + tls.connect({ + port: server.address().port, + rejectUnauthorized: false + }, common.mustCall(() => { + verify(); + })) + .on('error', common.mustNotCall()) + .on('close', common.mustCall(() => { + server.close(); + })).resume(); +})); + +server.unref(); diff --git a/test/crypto/test-crypto-webcrypto-aes-decrypt-tag-too-small.js b/test/crypto/test-crypto-webcrypto-aes-decrypt-tag-too-small.js new file mode 100644 index 0000000..8c116b8 --- /dev/null +++ b/test/crypto/test-crypto-webcrypto-aes-decrypt-tag-too-small.js @@ -0,0 +1,31 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto').webcrypto; + +crypto.subtle.importKey( + 'raw', + new Uint8Array(32), + { + name: 'AES-GCM' + }, + false, + [ 'encrypt', 'decrypt' ]) + .then((k) => { + assert.rejects(() => { + return crypto.subtle.decrypt({ + name: 'AES-GCM', + iv: new Uint8Array(12), + }, k, new Uint8Array(0)); + }, { + name: 'OperationError', + message: /The provided data is too small/, + }); + }); diff --git a/test/crypto/test-crypto-worker-thread.js b/test/crypto/test-crypto-worker-thread.js new file mode 100644 index 0000000..8f29578 --- /dev/null +++ b/test/crypto/test-crypto-worker-thread.js @@ -0,0 +1,19 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +// Issue https://github.com/nodejs/node/issues/35263 +// Description: Test that passing keyobject to worker thread does not crash. +const { createSecretKey } = require('crypto'); + +const { Worker, isMainThread, workerData } = require('worker_threads'); + +if (isMainThread) { + const key = createSecretKey(Buffer.from('hello')); + new Worker(__filename, { workerData: key }); +} else { + console.log(workerData); +} diff --git a/test/crypto/test-crypto-x509.js b/test/crypto/test-crypto-x509.js new file mode 100644 index 0000000..71e308e --- /dev/null +++ b/test/crypto/test-crypto-x509.js @@ -0,0 +1,264 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { + X509Certificate, + createPrivateKey, +} = require('crypto'); + +const { + isX509Certificate +} = require('internal/crypto/x509'); + +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const { readFileSync } = require('fs'); + +const cert = readFileSync(fixtures.path('keys', 'agent1-cert.pem')); +const key = readFileSync(fixtures.path('keys', 'agent1-key.pem')); +const ca = readFileSync(fixtures.path('keys', 'ca1-cert.pem')); + +const privateKey = createPrivateKey(key); + +[1, {}, false, null].forEach((i) => { + assert.throws(() => new X509Certificate(i), { + code: 'ERR_INVALID_ARG_TYPE' + }); +}); + +const subjectCheck = `C=US +ST=CA +L=SF +O=Joyent +OU=Node.js +CN=agent1 +emailAddress=ry@tinyclouds.org`; + +const issuerCheck = `C=US +ST=CA +L=SF +O=Joyent +OU=Node.js +CN=ca1 +emailAddress=ry@tinyclouds.org`; + +let infoAccessCheck = `OCSP - URI:http://ocsp.nodejs.org/ +CA Issuers - URI:http://ca.nodejs.org/ca.cert`; +if (!common.hasOpenSSL3) + infoAccessCheck += '\n'; + +const der = Buffer.from( + '308202d830820241a003020102020900ecc9b856270da9a830' + + '0d06092a864886f70d01010b0500307a310b30090603550406' + + '13025553310b300906035504080c024341310b300906035504' + + '070c025346310f300d060355040a0c064a6f79656e74311030' + + '0e060355040b0c074e6f64652e6a73310c300a06035504030c' + + '036361313120301e06092a864886f70d010901161172794074' + + '696e79636c6f7564732e6f72673020170d3138313131363138' + + '343232315a180f32323932303833303138343232315a307d31' + + '0b3009060355040613025553310b300906035504080c024341' + + '310b300906035504070c025346310f300d060355040a0c064a' + + '6f79656e743110300e060355040b0c074e6f64652e6a73310f' + + '300d06035504030c066167656e74313120301e06092a864886' + + 'f70d010901161172794074696e79636c6f7564732e6f726730' + + '819f300d06092a864886f70d010101050003818d0030818902' + + '818100ef5440701637e28abb038e5641f828d834c342a9d25e' + + 'dbb86a2bf6fbd809cb8e037a98b71708e001242e4deb54c616' + + '4885f599dd87a23215745955be20417e33c4d0d1b80c9da3de' + + '419a2607195d2fb75657b0bbfb5eb7d0bba5122d1b6964c7b5' + + '70d50b8ec001eeb68dfb584437508f3129928d673b30a3e0bf' + + '4f50609e63710203010001a361305f305d06082b0601050507' + + '01010451304f302306082b060105050730018617687474703a' + + '2f2f6f6373702e6e6f64656a732e6f72672f302806082b0601' + + '0505073002861c687474703a2f2f63612e6e6f64656a732e6f' + + '72672f63612e63657274300d06092a864886f70d01010b0500' + + '038181007acabf1d99e1fb05edbdd54608886dd6c509fc5820' + + '2be8274f8139b60f8ea219666f7eff9737e92a732b318ef423' + + '7da94123dcac4f9a28e76fe663b26d42482ac6d66d380bbdfe' + + '0230083e743e7966671752b82f692e1034e9bfc9d0cd829888' + + '6c6c996e7c3d231e02ad5399a170b525b74f11d7ed13a7a815' + + 'f4b974253a8d66', 'hex'); + +{ + const x509 = new X509Certificate(cert); + + assert(isX509Certificate(x509)); + + assert(!x509.ca); + assert.strictEqual(x509.subject, subjectCheck); + assert.strictEqual(x509.subjectAltName, undefined); + assert.strictEqual(x509.issuer, issuerCheck); + assert.strictEqual(x509.infoAccess, infoAccessCheck); + assert.strictEqual(x509.validFrom, 'Nov 16 18:42:21 2018 GMT'); + assert.strictEqual(x509.validTo, 'Aug 30 18:42:21 2292 GMT'); + assert.strictEqual( + x509.fingerprint, + 'D7:FD:F6:42:92:A8:83:51:8E:80:48:62:66:DA:85:C2:EE:A6:A1:CD'); + assert.strictEqual( + x509.fingerprint256, + 'B0:BE:46:49:B8:29:63:E0:6F:63:C8:8A:57:9C:3F:9B:72:C6:F5:89:E3:0D:' + + '84:AC:5B:08:9A:20:89:B6:8F:D6' + ); + assert.strictEqual( + x509.fingerprint512, + 'D0:05:01:82:2C:D8:09:BE:27:94:E7:83:F1:88:BC:7A:8B:D0:39:97:54:B6:' + + 'D0:B4:46:5B:DE:13:5B:68:86:B6:F2:A8:95:22:D5:6E:8B:35:DA:89:29:CA:' + + 'A3:06:C5:CE:43:C1:7F:2D:7E:5F:44:A5:EE:A3:CB:97:05:A3:E3:68' + ); + assert.strictEqual(x509.keyUsage, undefined); + assert.strictEqual(x509.serialNumber, 'ECC9B856270DA9A8'); + + assert.deepStrictEqual(x509.raw, der); + + assert(x509.publicKey); + assert.strictEqual(x509.publicKey.type, 'public'); + + assert.strictEqual(x509.toString().replaceAll('\r\n', '\n'), + cert.toString().replaceAll('\r\n', '\n')); + assert.strictEqual(x509.toJSON(), x509.toString()); + + assert(x509.checkPrivateKey(privateKey)); + assert.throws(() => x509.checkPrivateKey(x509.publicKey), { + code: 'ERR_INVALID_ARG_VALUE' + }); + + assert.strictEqual(x509.checkIP('127.0.0.1'), undefined); + assert.strictEqual(x509.checkIP('::'), undefined); + assert.strictEqual(x509.checkHost('agent1'), 'agent1'); + assert.strictEqual(x509.checkHost('agent2'), undefined); + assert.strictEqual(x509.checkEmail('ry@tinyclouds.org'), 'ry@tinyclouds.org'); + assert.strictEqual(x509.checkEmail('sally@example.com'), undefined); + assert.throws(() => x509.checkHost('agent\x001'), { + code: 'ERR_INVALID_ARG_VALUE' + }); + assert.throws(() => x509.checkIP('[::]'), { + code: 'ERR_INVALID_ARG_VALUE' + }); + assert.throws(() => x509.checkEmail('not\x00hing'), { + code: 'ERR_INVALID_ARG_VALUE' + }); + + [1, false, null].forEach((i) => { + assert.throws(() => x509.checkHost('agent1', i), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => x509.checkHost('agent1', { subject: i }), { + code: 'ERR_INVALID_ARG_TYPE' + }); + }); + + [ + 'wildcards', + 'partialWildcards', + 'multiLabelWildcards', + 'singleLabelSubdomains', + ].forEach((key) => { + [1, '', null, {}].forEach((i) => { + assert.throws(() => x509.checkHost('agent1', { [key]: i }), { + code: 'ERR_INVALID_ARG_TYPE' + }); + }); + }); + + const ca_cert = new X509Certificate(ca); + + assert(x509.checkIssued(ca_cert)); + assert(!x509.checkIssued(x509)); + assert(x509.verify(ca_cert.publicKey)); + assert(!x509.verify(x509.publicKey)); + + assert.throws(() => x509.checkIssued({}), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => x509.checkIssued(''), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => x509.verify({}), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => x509.verify(''), { + code: 'ERR_INVALID_ARG_TYPE' + }); + assert.throws(() => x509.verify(privateKey), { + code: 'ERR_INVALID_ARG_VALUE' + }); + + // X509Certificate can be cloned via MessageChannel/MessagePort + const mc = new MessageChannel(); + mc.port1.onmessage = common.mustCall(({ data }) => { + assert(isX509Certificate(data)); + assert.deepStrictEqual(data.raw, x509.raw); + mc.port1.close(); + }); + mc.port2.postMessage(x509); + + // Verify that legacy encoding works + const legacyObjectCheck = { + subject: Object.assign(Object.create(null), { + C: 'US', + ST: 'CA', + L: 'SF', + O: 'Joyent', + OU: 'Node.js', + CN: 'agent1', + emailAddress: 'ry@tinyclouds.org', + }), + issuer: Object.assign(Object.create(null), { + C: 'US', + ST: 'CA', + L: 'SF', + O: 'Joyent', + OU: 'Node.js', + CN: 'ca1', + emailAddress: 'ry@tinyclouds.org', + }), + infoAccess: Object.assign(Object.create(null), { + 'OCSP - URI': ['http://ocsp.nodejs.org/'], + 'CA Issuers - URI': ['http://ca.nodejs.org/ca.cert'] + }), + modulus: 'EF5440701637E28ABB038E5641F828D834C342A9D25EDBB86A2BF' + + '6FBD809CB8E037A98B71708E001242E4DEB54C6164885F599DD87' + + 'A23215745955BE20417E33C4D0D1B80C9DA3DE419A2607195D2FB' + + '75657B0BBFB5EB7D0BBA5122D1B6964C7B570D50B8EC001EEB68D' + + 'FB584437508F3129928D673B30A3E0BF4F50609E6371', + bits: 1024, + exponent: '0x10001', + valid_from: 'Nov 16 18:42:21 2018 GMT', + valid_to: 'Aug 30 18:42:21 2292 GMT', + fingerprint: 'D7:FD:F6:42:92:A8:83:51:8E:80:48:62:66:DA:85:C2:EE:A6:A1:CD', + fingerprint256: + 'B0:BE:46:49:B8:29:63:E0:6F:63:C8:8A:57:9C:3F:9B:72:' + + 'C6:F5:89:E3:0D:84:AC:5B:08:9A:20:89:B6:8F:D6', + fingerprint512: + 'D0:05:01:82:2C:D8:09:BE:27:94:E7:83:F1:88:BC:7A:8B:' + + 'D0:39:97:54:B6:D0:B4:46:5B:DE:13:5B:68:86:B6:F2:A8:' + + '95:22:D5:6E:8B:35:DA:89:29:CA:A3:06:C5:CE:43:C1:7F:' + + '2D:7E:5F:44:A5:EE:A3:CB:97:05:A3:E3:68', + serialNumber: 'ECC9B856270DA9A8' + }; + + const legacyObject = x509.toLegacyObject(); + + assert.deepStrictEqual(legacyObject.raw, x509.raw); + assert.deepStrictEqual(legacyObject.subject, legacyObjectCheck.subject); + assert.deepStrictEqual(legacyObject.issuer, legacyObjectCheck.issuer); + assert.deepStrictEqual(legacyObject.infoAccess, legacyObjectCheck.infoAccess); + assert.strictEqual(legacyObject.modulus, legacyObjectCheck.modulus); + assert.strictEqual(legacyObject.bits, legacyObjectCheck.bits); + assert.strictEqual(legacyObject.exponent, legacyObjectCheck.exponent); + assert.strictEqual(legacyObject.valid_from, legacyObjectCheck.valid_from); + assert.strictEqual(legacyObject.valid_to, legacyObjectCheck.valid_to); + assert.strictEqual(legacyObject.fingerprint, legacyObjectCheck.fingerprint); + assert.strictEqual( + legacyObject.fingerprint256, + legacyObjectCheck.fingerprint256); + assert.strictEqual( + legacyObject.serialNumber, + legacyObjectCheck.serialNumber); +} diff --git a/test/crypto/test-crypto.js b/test/crypto/test-crypto.js new file mode 100644 index 0000000..c52931d --- /dev/null +++ b/test/crypto/test-crypto.js @@ -0,0 +1,329 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +common.expectWarning({ + DeprecationWarning: [ + ['crypto.createCipher is deprecated.', 'DEP0106'], + ] +}); + +const assert = require('assert'); +const crypto = require('crypto'); +const tls = require('tls'); +const fixtures = require('../common/fixtures'); + +// Test Certificates +const certPfx = fixtures.readKey('rsa_cert.pfx'); + +// 'this' safety +// https://github.com/joyent/node/issues/6690 +assert.throws(() => { + const credentials = tls.createSecureContext(); + const context = credentials.context; + const notcontext = { setOptions: context.setOptions }; + + // Methods of native objects should not segfault when reassigned to a new + // object and called illegally. This core dumped in 0.10 and was fixed in + // 0.11. + notcontext.setOptions(); +}, (err) => { + // Throws TypeError, so there is no opensslErrorStack property. + return err instanceof TypeError && + err.name === 'TypeError' && + /^TypeError: Illegal invocation$/.test(err) && + !('opensslErrorStack' in err); +}); + +// PFX tests +tls.createSecureContext({ pfx: certPfx, passphrase: 'sample' }); + +assert.throws(() => { + tls.createSecureContext({ pfx: certPfx }); +}, (err) => { + // Throws general Error, so there is no opensslErrorStack property. + return err instanceof Error && + err.name === 'Error' && + /^Error: mac verify failure$/.test(err) && + !('opensslErrorStack' in err); +}); + +assert.throws(() => { + tls.createSecureContext({ pfx: certPfx, passphrase: 'test' }); +}, (err) => { + // Throws general Error, so there is no opensslErrorStack property. + return err instanceof Error && + err.name === 'Error' && + /^Error: mac verify failure$/.test(err) && + !('opensslErrorStack' in err); +}); + +assert.throws(() => { + tls.createSecureContext({ pfx: 'sample', passphrase: 'test' }); +}, (err) => { + // Throws general Error, so there is no opensslErrorStack property. + return err instanceof Error && + err.name === 'Error' && + /^Error: not enough data$/.test(err) && + !('opensslErrorStack' in err); +}); + + +// update() should only take buffers / strings +assert.throws( + () => crypto.createHash('sha1').update({ foo: 'bar' }), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + + +function validateList(list) { + // The list must not be empty + assert(list.length > 0); + + // The list should be sorted. + // Array#sort() modifies the list in place so make a copy. + const sorted = [...list].sort(); + assert.deepStrictEqual(list, sorted); + + // Each element should be unique. + assert.strictEqual([...new Set(list)].length, list.length); + + // Each element should be a string. + assert(list.every((value) => typeof value === 'string')); +} + +// Assume that we have at least AES-128-CBC. +const cryptoCiphers = crypto.getCiphers(); +assert(crypto.getCiphers().includes('aes-128-cbc')); +validateList(cryptoCiphers); +// Make sure all of the ciphers are supported by OpenSSL +for (const algo of cryptoCiphers) { + const { ivLength, keyLength, mode } = crypto.getCipherInfo(algo); + let options; + if (mode === 'ccm') + options = { authTagLength: 8 }; + else if (mode === 'ocb' || algo === 'chacha20-poly1305') + options = { authTagLength: 16 }; + crypto.createCipheriv(algo, + crypto.randomBytes(keyLength), + crypto.randomBytes(ivLength || 0), + options); +} + +// Assume that we have at least AES256-SHA. +const tlsCiphers = tls.getCiphers(); +assert(tls.getCiphers().includes('aes256-sha')); +assert(tls.getCiphers().includes('tls_aes_128_ccm_8_sha256')); +// There should be no capital letters in any element. +const noCapitals = /^[^A-Z]+$/; +assert(tlsCiphers.every((value) => noCapitals.test(value))); +validateList(tlsCiphers); + +// Assert that we have sha1 and sha256 but not SHA1 and SHA256. +assert.notStrictEqual(crypto.getHashes().length, 0); +assert(crypto.getHashes().includes('sha1')); +assert(crypto.getHashes().includes('sha256')); +assert(!crypto.getHashes().includes('SHA1')); +assert(!crypto.getHashes().includes('SHA256')); +assert(crypto.getHashes().includes('RSA-SHA1')); +assert(!crypto.getHashes().includes('rsa-sha1')); +validateList(crypto.getHashes()); +// Make sure all of the hashes are supported by OpenSSL +for (const algo of crypto.getHashes()) + crypto.createHash(algo); + +// Assume that we have at least secp384r1. +assert.notStrictEqual(crypto.getCurves().length, 0); +assert(crypto.getCurves().includes('secp384r1')); +assert(!crypto.getCurves().includes('SECP384R1')); +validateList(crypto.getCurves()); + +// Modifying return value from get* functions should not mutate subsequent +// return values. +function testImmutability(fn) { + const list = fn(); + const copy = [...list]; + list.push('some-arbitrary-value'); + assert.deepStrictEqual(fn(), copy); +} + +testImmutability(crypto.getCiphers); +testImmutability(tls.getCiphers); +testImmutability(crypto.getHashes); +testImmutability(crypto.getCurves); + +const encodingError = { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: "The argument 'encoding' is invalid for data of length 1." + + " Received 'hex'", +}; + +// Regression tests for https://github.com/nodejs/node-v0.x-archive/pull/5725: +// hex input that's not a power of two should throw, not assert in C++ land. +['createCipher', 'createDecipher'].forEach((funcName) => { + assert.throws( + () => crypto[funcName]('aes192', 'test').update('0', 'hex'), + (error) => { + assert.ok(!('opensslErrorStack' in error)); + if (common.hasFipsCrypto) { + return error instanceof Error && + error.name === 'Error' && + /^Error: not supported in FIPS mode$/.test(error); + } + assert.throws(() => { throw error; }, encodingError); + return true; + } + ); +}); + +assert.throws( + () => crypto.createHash('sha1').update('0', 'hex'), + (error) => { + assert.ok(!('opensslErrorStack' in error)); + assert.throws(() => { throw error; }, encodingError); + return true; + } +); + +assert.throws( + () => crypto.createHmac('sha256', 'a secret').update('0', 'hex'), + (error) => { + assert.ok(!('opensslErrorStack' in error)); + assert.throws(() => { throw error; }, encodingError); + return true; + } +); + +assert.throws(() => { + const priv = [ + '-----BEGIN RSA PRIVATE KEY-----', + 'MIGrAgEAAiEA+3z+1QNF2/unumadiwEr+C5vfhezsb3hp4jAnCNRpPcCAwEAAQIgQNriSQK4', + 'EFwczDhMZp2dvbcz7OUUyt36z3S4usFPHSECEQD/41K7SujrstBfoCPzwC1xAhEA+5kt4BJy', + 'eKN7LggbF3Dk5wIQN6SL+fQ5H/+7NgARsVBp0QIRANxYRukavs4QvuyNhMx+vrkCEQCbf6j/', + 'Ig6/HueCK/0Jkmp+', + '-----END RSA PRIVATE KEY-----', + '', + ].join('\n'); + crypto.createSign('SHA256').update('test').sign(priv); +}, (err) => { + if (!common.hasOpenSSL3) + assert.ok(!('opensslErrorStack' in err)); + assert.throws(() => { throw err; }, common.hasOpenSSL3 ? { + name: 'Error', + message: 'error:02000070:rsa routines::digest too big for rsa key', + library: 'rsa routines', + } : { + name: 'Error', + message: /routines:RSA_sign:digest too big for rsa key$/, + library: 'rsa routines', + function: 'RSA_sign', + reason: 'digest too big for rsa key', + code: 'ERR_OSSL_RSA_DIGEST_TOO_BIG_FOR_RSA_KEY' + }); + return true; +}); + +if (!common.hasOpenSSL3) { + assert.throws(() => { + // The correct header inside `rsa_private_pkcs8_bad.pem` should have been + // -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- + // instead of + // -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- + const sha1_privateKey = fixtures.readKey('rsa_private_pkcs8_bad.pem', + 'ascii'); + // This would inject errors onto OpenSSL's error stack + crypto.createSign('sha1').sign(sha1_privateKey); + }, (err) => { + // Do the standard checks, but then do some custom checks afterwards. + assert.throws(() => { throw err; }, { + message: 'error:0D0680A8:asn1 encoding routines:asn1_check_tlen:' + + 'wrong tag', + library: 'asn1 encoding routines', + function: 'asn1_check_tlen', + reason: 'wrong tag', + code: 'ERR_OSSL_ASN1_WRONG_TAG', + }); + // Throws crypto error, so there is an opensslErrorStack property. + // The openSSL stack should have content. + assert(Array.isArray(err.opensslErrorStack)); + assert(err.opensslErrorStack.length > 0); + return true; + }); +} + +// Make sure memory isn't released before being returned +console.log(crypto.randomBytes(16)); + +assert.throws(() => { + tls.createSecureContext({ crl: 'not a CRL' }); +}, (err) => { + // Throws general error, so there is no opensslErrorStack property. + return err instanceof Error && + /^Error: Failed to parse CRL$/.test(err) && + !('opensslErrorStack' in err); +}); + +/** + * Check if the stream function uses utf8 as a default encoding. + */ + +function testEncoding(options, assertionHash) { + const hash = crypto.createHash('sha256', options); + let hashValue = ''; + + hash.on('data', (data) => { + hashValue += data.toString('hex'); + }); + + hash.on('end', common.mustCall(() => { + assert.strictEqual(hashValue, assertionHash); + })); + + hash.write('öäü'); + hash.end(); +} + +// Hash of "öäü" in utf8 format +const assertionHashUtf8 = + '4f53d15bee524f082380e6d7247cc541e7cb0d10c64efdcc935ceeb1e7ea345c'; + +// Hash of "öäü" in latin1 format +const assertionHashLatin1 = + 'cd37bccd5786e2e76d9b18c871e919e6eb11cc12d868f5ae41c40ccff8e44830'; + +testEncoding(undefined, assertionHashUtf8); +testEncoding({}, assertionHashUtf8); + +testEncoding({ + defaultEncoding: 'utf8' +}, assertionHashUtf8); + +testEncoding({ + defaultEncoding: 'latin1' +}, assertionHashLatin1); diff --git a/test/fixtures/keys/.gitattributes b/test/fixtures/keys/.gitattributes new file mode 100644 index 0000000..87a4fb1 --- /dev/null +++ b/test/fixtures/keys/.gitattributes @@ -0,0 +1,4 @@ +* -text + +Makefile text +*.cnf text diff --git a/test/fixtures/keys/I_AM_THE_WALRUS_sha256_signature_signedby_rsa_private_b.sha256 b/test/fixtures/keys/I_AM_THE_WALRUS_sha256_signature_signedby_rsa_private_b.sha256 new file mode 100644 index 0000000..59eb6c7 Binary files /dev/null and b/test/fixtures/keys/I_AM_THE_WALRUS_sha256_signature_signedby_rsa_private_b.sha256 differ diff --git a/test/fixtures/keys/Makefile b/test/fixtures/keys/Makefile new file mode 100644 index 0000000..71bc36a --- /dev/null +++ b/test/fixtures/keys/Makefile @@ -0,0 +1,821 @@ +all: \ + ca1-cert.pem \ + ca2-cert.pem \ + ca2-crl.pem \ + ca3-cert.pem \ + ca4-cert.pem \ + ca5-cert.pem \ + ca6-cert.pem \ + agent1-cert.pem \ + agent1.pfx \ + agent2-cert.pem \ + agent3-cert.pem \ + agent4-cert.pem \ + agent5-cert.pem \ + agent6-cert.pem \ + agent6.pfx \ + agent7-cert.pem \ + agent8-cert.pem \ + agent9-cert.pem \ + agent10-cert.pem \ + agent10.pfx \ + ec10-cert.pem \ + ec10.pfx \ + dh512.pem \ + dh1024.pem \ + dh2048.pem \ + dherror.pem \ + dsa_params.pem \ + dsa_private.pem \ + dsa_private_encrypted.pem \ + dsa_private_pkcs8.pem \ + dsa_public.pem \ + dsa1025.pem \ + dsa_private_1025.pem \ + dsa_private_encrypted_1025.pem \ + dsa_public_1025.pem \ + ec-cert.pem \ + ec.pfx \ + fake-cnnic-root-cert.pem \ + rsa_private.pem \ + rsa_private_encrypted.pem \ + rsa_private_pkcs8.pem \ + rsa_private_pkcs8_bad.pem \ + rsa_public.pem \ + rsa_ca.crt \ + rsa_cert.crt \ + rsa_cert.pfx \ + rsa_public_sha1_signature_signedby_rsa_private.sha1 \ + rsa_public_sha1_signature_signedby_rsa_private_pkcs8.sha1 \ + rsa_private_b.pem \ + I_AM_THE_WALRUS_sha256_signature_signedby_rsa_private_b.sha256 \ + rsa_public_b.pem \ + rsa_cert_foafssl_b.crt \ + rsa_cert_foafssl_b.modulus \ + rsa_cert_foafssl_b.exponent \ + rsa_spkac.spkac \ + rsa_spkac_invalid.spkac \ + rsa_private_1024.pem \ + rsa_private_2048.pem \ + rsa_private_4096.pem \ + rsa_public_1024.pem \ + rsa_public_2048.pem \ + rsa_public_4096.pem \ + rsa_pss_private_2048.pem \ + rsa_pss_private_2048_sha256_sha256_16.pem \ + rsa_pss_private_2048_sha512_sha256_20.pem \ + rsa_pss_private_2048_sha1_sha1_20.pem \ + rsa_pss_public_2048.pem \ + rsa_pss_public_2048_sha256_sha256_16.pem \ + rsa_pss_public_2048_sha512_sha256_20.pem \ + rsa_pss_public_2048_sha1_sha1_20.pem \ + ed25519_private.pem \ + ed25519_public.pem \ + x25519_private.pem \ + x25519_public.pem \ + ed448_private.pem \ + ed448_public.pem \ + x448_private.pem \ + x448_public.pem \ + ec_p256_private.pem \ + ec_p256_public.pem \ + ec_p384_private.pem \ + ec_p384_public.pem \ + ec_p521_private.pem \ + ec_p521_public.pem \ + ec_secp256k1_private.pem \ + ec_secp256k1_public.pem \ + incorrect_san_correct_subject-cert.pem \ + incorrect_san_correct_subject-key.pem \ + irrelevant_san_correct_subject-cert.pem \ + irrelevant_san_correct_subject-key.pem \ + +# +# Create Certificate Authority: ca1 +# ('password' is used for the CA password.) +# +ca1-cert.pem: ca1.cnf + openssl req -new -x509 -days 99999 -config ca1.cnf -keyout ca1-key.pem -out ca1-cert.pem + +# +# Create Certificate Authority: ca2 +# ('password' is used for the CA password.) +# +ca2-cert.pem: ca2.cnf + openssl req -new -x509 -days 99999 -config ca2.cnf -keyout ca2-key.pem -out ca2-cert.pem + echo '01' > ca2-serial + touch ca2-database.txt + +# +# Create Subordinate Certificate Authority: ca3 issued by ca1 +# ('password' is used for the CA password.) +# +ca3-key.pem: + openssl genrsa -out ca3-key.pem 1024 + +ca3-csr.pem: ca3.cnf ca3-key.pem + openssl req -new \ + -extensions v3_ca \ + -config ca3.cnf \ + -key ca3-key.pem \ + -out ca3-csr.pem + +ca3-cert.pem: ca3-csr.pem ca3-key.pem ca3.cnf ca1-cert.pem ca1-key.pem + openssl x509 -req \ + -extfile ca3.cnf \ + -extensions v3_ca \ + -days 99999 \ + -passin "pass:password" \ + -in ca3-csr.pem \ + -CA ca1-cert.pem \ + -CAkey ca1-key.pem \ + -CAcreateserial \ + -out ca3-cert.pem + +# +# Create Subordinate Certificate Authority: ca4 issued by ca2 +# ('password' is used for the CA password.) +# +ca4-key.pem: + openssl genrsa -out ca4-key.pem 1024 + +ca4-csr.pem: ca4.cnf ca4-key.pem + openssl req -new \ + -extensions v3_ca \ + -config ca4.cnf \ + -key ca4-key.pem \ + -out ca4-csr.pem + +ca4-cert.pem: ca4-csr.pem ca4-key.pem ca4.cnf ca2-cert.pem ca2-key.pem + openssl x509 -req \ + -extfile ca4.cnf \ + -extensions v3_ca \ + -days 99999 \ + -passin "pass:password" \ + -in ca4-csr.pem \ + -CA ca2-cert.pem \ + -CAkey ca2-key.pem \ + -CAcreateserial \ + -out ca4-cert.pem + +# +# Create Certificate Authority: ca5 with ECC +# ('password' is used for the CA password.) +# +ca5-key.pem: + openssl ecparam -genkey -out ca5-key.pem -name prime256v1 + +ca5-csr.pem: ca5.cnf ca5-key.pem + openssl req -new \ + -config ca5.cnf \ + -key ca5-key.pem \ + -out ca5-csr.pem + +ca5-cert.pem: ca5.cnf ca5-key.pem ca5-csr.pem + openssl x509 -req \ + -extfile ca5.cnf \ + -extensions v3_ca \ + -days 99999 \ + -passin "pass:password" \ + -in ca5-csr.pem \ + -signkey ca5-key.pem \ + -out ca5-cert.pem + +# +# Create Subordinate Certificate Authority: ca6 issued by ca5 with ECC +# ('password' is used for the CA password.) +# +ca6-key.pem: + openssl ecparam -genkey -out ca6-key.pem -name prime256v1 + +ca6-csr.pem: ca6.cnf ca6-key.pem + openssl req -new \ + -extensions v3_ca \ + -config ca6.cnf \ + -key ca6-key.pem \ + -out ca6-csr.pem + +ca6-cert.pem: ca6-csr.pem ca6-key.pem ca6.cnf ca5-cert.pem ca5-key.pem + openssl x509 -req \ + -extfile ca6.cnf \ + -extensions v3_ca \ + -days 99999 \ + -passin "pass:password" \ + -in ca6-csr.pem \ + -CA ca5-cert.pem \ + -CAkey ca5-key.pem \ + -CAcreateserial \ + -out ca6-cert.pem + +# +# Create Fake CNNIC Root Certificate Authority: fake-cnnic-root +# + +fake-cnnic-root-key.pem: + openssl genrsa -out fake-cnnic-root-key.pem 2048 + +fake-cnnic-root-cert.pem: fake-cnnic-root.cnf fake-cnnic-root-key.pem + openssl req -x509 -new \ + -key fake-cnnic-root-key.pem \ + -days 99999 \ + -out fake-cnnic-root-cert.pem \ + -config fake-cnnic-root.cnf + +# +# Create Fake StartCom Root Certificate Authority: fake-startcom-root +# +fake-startcom-root-key.pem: + openssl genrsa -out fake-startcom-root-key.pem 2048 + +fake-startcom-root-cert.pem: fake-startcom-root.cnf \ + fake-startcom-root-key.pem + openssl req -new -x509 -days 99999 -config \ + fake-startcom-root.cnf -key fake-startcom-root-key.pem -out \ + fake-startcom-root-cert.pem + echo '01' > fake-startcom-root-serial + touch fake-startcom-root-database.txt + +# +# agent1 is signed by ca1. +# + +agent1-key.pem: + openssl genrsa -out agent1-key.pem 1024 + +agent1-csr.pem: agent1.cnf agent1-key.pem + openssl req -new -config agent1.cnf -key agent1-key.pem -out agent1-csr.pem + +agent1-cert.pem: agent1-csr.pem ca1-cert.pem ca1-key.pem + openssl x509 -req \ + -extfile agent1.cnf \ + -extensions v3_ca \ + -days 99999 \ + -passin "pass:password" \ + -in agent1-csr.pem \ + -CA ca1-cert.pem \ + -CAkey ca1-key.pem \ + -CAcreateserial \ + -out agent1-cert.pem + +agent1.pfx: agent1-cert.pem agent1-key.pem ca1-cert.pem + openssl pkcs12 -export \ + -descert \ + -in agent1-cert.pem \ + -inkey agent1-key.pem \ + -certfile ca1-cert.pem \ + -out agent1.pfx \ + -password pass:sample + +agent1-verify: agent1-cert.pem ca1-cert.pem + openssl verify -CAfile ca1-cert.pem agent1-cert.pem + + +# +# agent2 has a self signed cert +# +# Generate new private key +agent2-key.pem: + openssl genrsa -out agent2-key.pem 1024 + +# Create a Certificate Signing Request for the key +agent2-csr.pem: agent2-key.pem agent2.cnf + openssl req -new -config agent2.cnf -key agent2-key.pem -out agent2-csr.pem + +# Create a Certificate for the agent. +agent2-cert.pem: agent2-csr.pem agent2-key.pem + openssl x509 -req \ + -days 99999 \ + -in agent2-csr.pem \ + -signkey agent2-key.pem \ + -out agent2-cert.pem + +agent2-verify: agent2-cert.pem + openssl verify -CAfile agent2-cert.pem agent2-cert.pem + +# +# agent3 is signed by ca2. +# + +agent3-key.pem: + openssl genrsa -out agent3-key.pem 1024 + +agent3-csr.pem: agent3.cnf agent3-key.pem + openssl req -new -config agent3.cnf -key agent3-key.pem -out agent3-csr.pem + +agent3-cert.pem: agent3-csr.pem ca2-cert.pem ca2-key.pem + openssl x509 -req \ + -days 99999 \ + -passin "pass:password" \ + -in agent3-csr.pem \ + -CA ca2-cert.pem \ + -CAkey ca2-key.pem \ + -CAcreateserial \ + -out agent3-cert.pem + +agent3-verify: agent3-cert.pem ca2-cert.pem + openssl verify -CAfile ca2-cert.pem agent3-cert.pem + + +# +# agent4 is signed by ca2 (client cert) +# + +agent4-key.pem: + openssl genrsa -out agent4-key.pem 1024 + +agent4-csr.pem: agent4.cnf agent4-key.pem + openssl req -new -config agent4.cnf -key agent4-key.pem -out agent4-csr.pem + +agent4-cert.pem: agent4-csr.pem ca2-cert.pem ca2-key.pem + openssl x509 -req \ + -days 99999 \ + -passin "pass:password" \ + -in agent4-csr.pem \ + -CA ca2-cert.pem \ + -CAkey ca2-key.pem \ + -CAcreateserial \ + -extfile agent4.cnf \ + -extensions ext_key_usage \ + -out agent4-cert.pem + +agent4-verify: agent4-cert.pem ca2-cert.pem + openssl verify -CAfile ca2-cert.pem agent4-cert.pem + +# +# Make CRL with agent4 being rejected +# +ca2-crl.pem: ca2-key.pem ca2-cert.pem ca2.cnf agent4-cert.pem + openssl ca -revoke agent4-cert.pem \ + -keyfile ca2-key.pem \ + -cert ca2-cert.pem \ + -config ca2.cnf \ + -passin 'pass:password' + openssl ca \ + -keyfile ca2-key.pem \ + -cert ca2-cert.pem \ + -config ca2.cnf \ + -gencrl \ + -out ca2-crl.pem \ + -passin 'pass:password' + +# +# agent5 is signed by ca2 (client cert) +# + +agent5-key.pem: + openssl genrsa -out agent5-key.pem 1024 + +agent5-csr.pem: agent5.cnf agent5-key.pem + openssl req -new -config agent5.cnf -key agent5-key.pem -out agent5-csr.pem + +agent5-cert.pem: agent5-csr.pem ca2-cert.pem ca2-key.pem + openssl x509 -req \ + -days 99999 \ + -passin "pass:password" \ + -in agent5-csr.pem \ + -CA ca2-cert.pem \ + -CAkey ca2-key.pem \ + -CAcreateserial \ + -extfile agent5.cnf \ + -extensions ext_key_usage \ + -out agent5-cert.pem + +agent5-verify: agent5-cert.pem ca2-cert.pem + openssl verify -CAfile ca2-cert.pem agent5-cert.pem + +# +# agent6 is a client RSA cert signed by ca3 +# + +agent6-key.pem: + openssl genrsa -out agent6-key.pem 1024 + +agent6-csr.pem: agent6.cnf agent6-key.pem + openssl req -new -config agent6.cnf -key agent6-key.pem -out agent6-csr.pem + +agent6-cert.pem: agent6-csr.pem ca3-cert.pem ca3-key.pem + openssl x509 -req \ + -days 99999 \ + -passin "pass:password" \ + -in agent6-csr.pem \ + -CA ca3-cert.pem \ + -CAkey ca3-key.pem \ + -CAcreateserial \ + -extfile agent6.cnf \ + -out agent6-cert.pem + cat ca3-cert.pem >> agent6-cert.pem + +agent6-verify: agent6-cert.pem ca3-cert.pem ca1-cert.pem + openssl verify -trusted ca1-cert.pem -untrusted ca3-cert.pem agent6-cert.pem + +agent6.pfx: agent6-cert.pem agent6-key.pem ca1-cert.pem + openssl pkcs12 -export \ + -descert \ + -in agent6-cert.pem \ + -inkey agent6-key.pem \ + -certfile ca1-cert.pem \ + -out agent6.pfx \ + -password pass:sample + +# +# agent7 is signed by fake-cnnic-root. +# + +agent7-key.pem: + openssl genrsa -out agent7-key.pem 2048 + +agent7-csr.pem: agent1.cnf agent7-key.pem + openssl req -new -config agent7.cnf -key agent7-key.pem -out agent7-csr.pem + +agent7-cert.pem: agent7-csr.pem fake-cnnic-root-cert.pem fake-cnnic-root-key.pem + openssl x509 -req \ + -extfile agent7.cnf \ + -days 99999 \ + -passin "pass:password" \ + -in agent7-csr.pem \ + -CA fake-cnnic-root-cert.pem \ + -CAkey fake-cnnic-root-key.pem \ + -CAcreateserial \ + -out agent7-cert.pem + +agent7-verify: agent7-cert.pem fake-cnnic-root-cert.pem + openssl verify -CAfile fake-cnnic-root-cert.pem agent7-cert.pem + +# +# agent8 is signed by fake-startcom-root with notBefore +# of Oct 20 23:59:59 2016 GMT +# + +agent8-key.pem: + openssl genrsa -out agent8-key.pem 2048 + +agent8-csr.pem: agent8.cnf agent8-key.pem + openssl req -new -config agent8.cnf -key agent8-key.pem \ + -out agent8-csr.pem + +agent8-cert.pem: agent8-csr.pem fake-startcom-root-cert.pem fake-startcom-root-key.pem + openssl ca \ + -config fake-startcom-root.cnf \ + -keyfile fake-startcom-root-key.pem \ + -cert fake-startcom-root-cert.pem \ + -batch \ + -days 99999 \ + -passin "pass:password" \ + -in agent8-csr.pem \ + -startdate 161020235959Z \ + -notext -out agent8-cert.pem + + +agent8-verify: agent8-cert.pem fake-startcom-root-cert.pem + openssl verify -CAfile fake-startcom-root-cert.pem \ + agent8-cert.pem + + +# +# agent9 is signed by fake-startcom-root with notBefore +# of Oct 21 00:00:01 2016 GMT +# +agent9-key.pem: + openssl genrsa -out agent9-key.pem 2048 + +agent9-csr.pem: agent9.cnf agent9-key.pem + openssl req -new -config agent9.cnf -key agent9-key.pem \ + -out agent9-csr.pem + + +agent9-cert.pem: agent9-csr.pem + openssl ca \ + -config fake-startcom-root.cnf \ + -keyfile fake-startcom-root-key.pem \ + -cert fake-startcom-root-cert.pem \ + -batch \ + -days 99999 \ + -passin "pass:password" \ + -in agent9-csr.pem \ + -startdate 20161021000001Z \ + -notext -out agent9-cert.pem + +# agent10 is a server RSA cert signed by ca4 for agent10.example.com +# + +agent10-key.pem: + openssl genrsa -out agent10-key.pem 1024 + +agent10-csr.pem: agent10.cnf agent10-key.pem + openssl req -new -config agent10.cnf -key agent10-key.pem -out agent10-csr.pem + +agent10-cert.pem: agent10-csr.pem ca4-cert.pem ca4-key.pem + openssl x509 -req \ + -days 99999 \ + -passin "pass:password" \ + -in agent10-csr.pem \ + -CA ca4-cert.pem \ + -CAkey ca4-key.pem \ + -CAcreateserial \ + -extfile agent10.cnf \ + -out agent10-cert.pem + cat ca4-cert.pem >> agent10-cert.pem + +agent10-verify: agent10-cert.pem ca4-cert.pem ca2-cert.pem + openssl verify -trusted ca2-cert.pem -untrusted ca4-cert.pem agent10-cert.pem + +agent10.pfx: agent10-cert.pem agent10-key.pem ca1-cert.pem + openssl pkcs12 -export \ + -descert \ + -in agent10-cert.pem \ + -inkey agent10-key.pem \ + -certfile ca1-cert.pem \ + -out agent10.pfx \ + -password pass:sample + +# +# ec10 is a server EC cert signed by ca6 for agent10.example.com +# + +ec10-key.pem: + openssl ecparam -genkey -out ec10-key.pem -name prime256v1 + +ec10-csr.pem: ec10-key.pem + openssl req -new -config agent10.cnf -key ec10-key.pem -out ec10-csr.pem + +ec10-cert.pem: ec10-csr.pem ca6-cert.pem ca6-key.pem + openssl x509 -req \ + -days 99999 \ + -passin "pass:password" \ + -in ec10-csr.pem \ + -CA ca6-cert.pem \ + -CAkey ca6-key.pem \ + -CAcreateserial \ + -extfile agent10.cnf \ + -out ec10-cert.pem + cat ca6-cert.pem >> ec10-cert.pem + +ec10-verify: ec10-cert.pem ca6-cert.pem ca5-cert.pem + openssl verify -trusted ca5-cert.pem -untrusted ca6-cert.pem ec10-cert.pem + +ec10.pfx: ec10-cert.pem ec10-key.pem ca6-cert.pem + openssl pkcs12 -export \ + -descert \ + -in ec10-cert.pem \ + -inkey ec10-key.pem \ + -certfile ca6-cert.pem \ + -out ec10.pfx \ + -password pass:sample + + +# +# ec is a self-signed EC cert for CN "agent2" +# +ec-key.pem: + openssl ecparam -genkey -out ec-key.pem -name prime256v1 + +ec-csr.pem: ec-key.pem + openssl req -new -config ec.cnf -key ec-key.pem -out ec-csr.pem + +ec-cert.pem: ec-csr.pem ec-key.pem + openssl x509 -req \ + -days 99999 \ + -in ec-csr.pem \ + -signkey ec-key.pem \ + -out ec-cert.pem + +ec.pfx: ec-cert.pem ec-key.pem + openssl pkcs12 -export \ + -descert \ + -in ec-cert.pem \ + -inkey ec-key.pem \ + -out ec.pfx \ + -password pass: + +dh512.pem: + openssl dhparam -out dh512.pem 512 + +dh1024.pem: + openssl dhparam -out dh1024.pem 1024 + +dh2048.pem: + openssl dhparam -out dh2048.pem 2048 + +dherror.pem: dh512.pem + sed 's/^[^-].*/AAAAAAAAAA/g' dh512.pem > dherror.pem + +dsa_params.pem: + openssl dsaparam -out dsa_params.pem 2048 + +dsa_private.pem: dsa_params.pem + openssl gendsa -out dsa_private.pem dsa_params.pem + +dsa_private_encrypted.pem: dsa_private.pem + openssl dsa -aes256 -in dsa_private.pem -passout 'pass:password' -out dsa_private_encrypted.pem + +dsa_private_pkcs8.pem: dsa_private.pem + openssl pkcs8 -topk8 -inform PEM -outform PEM -in dsa_private.pem -out dsa_private_pkcs8.pem -nocrypt + +dsa_public.pem: dsa_private.pem + openssl dsa -in dsa_private.pem -pubout -out dsa_public.pem + +dsa1025.pem: + openssl dsaparam -out dsa1025.pem 1025 + +dsa_private_1025.pem: + openssl gendsa -out dsa_private_1025.pem dsa1025.pem + +dsa_private_encrypted_1025.pem: + openssl pkcs8 -in dsa_private_1025.pem -topk8 -passout 'pass:secret' -out dsa_private_encrypted_1025.pem + +dsa_public_1025.pem: + openssl dsa -in dsa_private_1025.pem -pubout -out dsa_public_1025.pem + +rsa_private.pem: + openssl genrsa -out rsa_private.pem 2048 + +rsa_private_encrypted.pem: rsa_private.pem + openssl rsa -aes256 -in rsa_private.pem -passout 'pass:password' -out rsa_private_encrypted.pem + +rsa_private_pkcs8.pem: rsa_private.pem + openssl pkcs8 -topk8 -inform PEM -outform PEM -in rsa_private.pem -out rsa_private_pkcs8.pem -nocrypt + +rsa_private_pkcs8_bad.pem: rsa_private_pkcs8.pem + sed 's/PRIVATE/RSA PRIVATE/g' rsa_private_pkcs8.pem > rsa_private_pkcs8_bad.pem + +rsa_public.pem: rsa_private.pem + openssl rsa -in rsa_private.pem -pubout -out rsa_public.pem + +rsa_cert.crt: rsa_private.pem + openssl req -new -x509 -days 99999 -key rsa_private.pem -config rsa_cert.cnf -out rsa_cert.crt + +rsa_cert.pfx: rsa_cert.crt + openssl pkcs12 -export -descert -passout 'pass:sample' -inkey rsa_private.pem -in rsa_cert.crt -out rsa_cert.pfx + +rsa_ca.crt: rsa_cert.crt + cp rsa_cert.crt rsa_ca.crt + +rsa_public_sha1_signature_signedby_rsa_private.sha1: rsa_public.pem rsa_private.pem + openssl dgst -sha1 -sign rsa_private.pem -out rsa_public_sha1_signature_signedby_rsa_private.sha1 rsa_public.pem + +rsa_public_sha1_signature_signedby_rsa_private_pkcs8.sha1: rsa_public.pem rsa_private_pkcs8.pem + openssl dgst -sha1 -sign rsa_private_pkcs8.pem -out rsa_public_sha1_signature_signedby_rsa_private_pkcs8.sha1 rsa_public.pem + +rsa_private_b.pem: + openssl genrsa -out rsa_private_b.pem 2048 + +I_AM_THE_WALRUS_sha256_signature_signedby_rsa_private_b.sha256: rsa_private_b.pem + echo -n "I AM THE WALRUS" | openssl dgst -sha256 -sign rsa_private_b.pem -out I_AM_THE_WALRUS_sha256_signature_signedby_rsa_private_b.sha256 + +rsa_public_b.pem: rsa_private_b.pem + openssl rsa -in rsa_private_b.pem -pubout -out rsa_public_b.pem + +# The following 'foafssl' cert is used in test/parallel/test-https-foafssl.js. +# It requires a SAN like 'http://example.com/#me'. More info here: +# https://www.w3.org/wiki/Foaf+ssl +rsa_cert_foafssl_b.crt: rsa_private_b.pem + openssl req -new -x509 -days 99999 -config rsa_cert_foafssl_b.cnf -key rsa_private_b.pem -out rsa_cert_foafssl_b.crt + +# The 'modulus=' in the output must be stripped out +rsa_cert_foafssl_b.modulus: rsa_cert_foafssl_b.crt + openssl x509 -modulus -in rsa_cert_foafssl_b.crt -noout | cut -c 9- > rsa_cert_foafssl_b.modulus + +# Have to parse out the hex exponent +rsa_cert_foafssl_b.exponent: rsa_cert_foafssl_b.crt + openssl x509 -in rsa_cert_foafssl_b.crt -text | grep -o 'Exponent:.*' | sed 's/\(.*(\|).*\)//g' > rsa_cert_foafssl_b.exponent + +# openssl outputs `SPKAC=[SPKAC]`. That prefix needs to be removed to work with node +rsa_spkac.spkac: rsa_private.pem + openssl spkac -key rsa_private.pem -challenge this-is-a-challenge | cut -c 7- > rsa_spkac.spkac + +# cutting characters from the start to invalidate the spkac +rsa_spkac_invalid.spkac: rsa_spkac.spkac + cat rsa_spkac.spkac | cut -c 5- > rsa_spkac_invalid.spkac + +rsa_private_1024.pem: + openssl genrsa -out rsa_private_1024.pem 1024 + +rsa_private_2048.pem: + openssl genrsa -out rsa_private_2048.pem 2048 + +rsa_private_4096.pem: + openssl genrsa -out rsa_private_4096.pem 4096 + +rsa_public_1024.pem: rsa_private_1024.pem + openssl rsa -in rsa_private_1024.pem -pubout -out rsa_public_1024.pem + +rsa_public_2048.pem: rsa_private_2048.pem + openssl rsa -in rsa_private_2048.pem -pubout -out rsa_public_2048.pem + +rsa_public_4096.pem: rsa_private_4096.pem + openssl rsa -in rsa_private_4096.pem -pubout -out rsa_public_4096.pem + +rsa_pss_private_2048.pem: + openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 -out rsa_pss_private_2048.pem + +rsa_pss_private_2048_sha256_sha256_16.pem: + openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 -pkeyopt rsa_pss_keygen_md:sha256 -pkeyopt rsa_pss_keygen_mgf1_md:sha256 -pkeyopt rsa_pss_keygen_saltlen:16 -out rsa_pss_private_2048_sha256_sha256_16.pem + +rsa_pss_private_2048_sha512_sha256_20.pem: + openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 -pkeyopt rsa_pss_keygen_md:sha512 -pkeyopt rsa_pss_keygen_mgf1_md:sha256 -pkeyopt rsa_pss_keygen_saltlen:20 -out rsa_pss_private_2048_sha512_sha256_20.pem + +rsa_pss_private_2048_sha1_sha1_20.pem: + openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 -pkeyopt rsa_pss_keygen_md:sha1 -pkeyopt rsa_pss_keygen_mgf1_md:sha1 -pkeyopt rsa_pss_keygen_saltlen:20 -out rsa_pss_private_2048_sha1_sha1_20.pem + +rsa_pss_public_2048.pem: rsa_pss_private_2048.pem + openssl pkey -in rsa_pss_private_2048.pem -pubout -out rsa_pss_public_2048.pem + +rsa_pss_public_2048_sha256_sha256_16.pem: rsa_pss_private_2048_sha256_sha256_16.pem + openssl pkey -in rsa_pss_private_2048_sha256_sha256_16.pem -pubout -out rsa_pss_public_2048_sha256_sha256_16.pem + +rsa_pss_public_2048_sha512_sha256_20.pem: rsa_pss_private_2048_sha512_sha256_20.pem + openssl pkey -in rsa_pss_private_2048_sha512_sha256_20.pem -pubout -out rsa_pss_public_2048_sha512_sha256_20.pem + +rsa_pss_public_2048_sha1_sha1_20.pem: rsa_pss_private_2048_sha1_sha1_20.pem + openssl pkey -in rsa_pss_private_2048_sha1_sha1_20.pem -pubout -out rsa_pss_public_2048_sha1_sha1_20.pem + +ed25519_private.pem: + openssl genpkey -algorithm ED25519 -out ed25519_private.pem + +ed25519_public.pem: ed25519_private.pem + openssl pkey -in ed25519_private.pem -pubout -out ed25519_public.pem + +x25519_private.pem: + openssl genpkey -algorithm x25519 -out x25519_private.pem + +x25519_public.pem: x25519_private.pem + openssl pkey -in x25519_private.pem -pubout -out x25519_public.pem + +ed448_private.pem: + openssl genpkey -algorithm ed448 -out ed448_private.pem + +ed448_public.pem: ed448_private.pem + openssl pkey -in ed448_private.pem -pubout -out ed448_public.pem + +x448_private.pem: + openssl genpkey -algorithm x448 -out x448_private.pem + +x448_public.pem: x448_private.pem + openssl pkey -in x448_private.pem -pubout -out x448_public.pem + +ec_p256_private.pem: + openssl ecparam -name prime256v1 -genkey -noout -out sec1_ec_p256_private.pem + openssl pkcs8 -topk8 -nocrypt -in sec1_ec_p256_private.pem -out ec_p256_private.pem + rm sec1_ec_p256_private.pem + +ec_p256_public.pem: ec_p256_private.pem + openssl ec -in ec_p256_private.pem -pubout -out ec_p256_public.pem + +ec_p384_private.pem: + openssl ecparam -name secp384r1 -genkey -noout -out sec1_ec_p384_private.pem + openssl pkcs8 -topk8 -nocrypt -in sec1_ec_p384_private.pem -out ec_p384_private.pem + rm sec1_ec_p384_private.pem + +ec_p384_public.pem: ec_p384_private.pem + openssl ec -in ec_p384_private.pem -pubout -out ec_p384_public.pem + +ec_p521_private.pem: + openssl ecparam -name secp521r1 -genkey -noout -out sec1_ec_p521_private.pem + openssl pkcs8 -topk8 -nocrypt -in sec1_ec_p521_private.pem -out ec_p521_private.pem + rm sec1_ec_p521_private.pem + +ec_p521_public.pem: ec_p521_private.pem + openssl ec -in ec_p521_private.pem -pubout -out ec_p521_public.pem + +ec_secp256k1_private.pem: + openssl ecparam -name secp256k1 -genkey -noout -out sec1_ec_secp256k1_private.pem + openssl pkcs8 -topk8 -nocrypt -in sec1_ec_secp256k1_private.pem -out ec_secp256k1_private.pem + rm sec1_ec_secp256k1_private.pem + +ec_secp256k1_public.pem: ec_secp256k1_private.pem + openssl ec -in ec_secp256k1_private.pem -pubout -out ec_secp256k1_public.pem + +incorrect_san_correct_subject-cert.pem: incorrect_san_correct_subject-key.pem + openssl req -x509 \ + -key incorrect_san_correct_subject-key.pem \ + -out incorrect_san_correct_subject-cert.pem \ + -sha256 \ + -days 3650 \ + -subj "/CN=good.example.com" \ + -addext "subjectAltName = DNS:evil.example.com" + +incorrect_san_correct_subject-key.pem: + openssl ecparam -name prime256v1 -genkey -noout -out incorrect_san_correct_subject-key.pem + +irrelevant_san_correct_subject-cert.pem: irrelevant_san_correct_subject-key.pem + openssl req -x509 \ + -key irrelevant_san_correct_subject-key.pem \ + -out irrelevant_san_correct_subject-cert.pem \ + -sha256 \ + -days 3650 \ + -subj "/CN=good.example.com" \ + -addext "subjectAltName = IP:1.2.3.4" + +irrelevant_san_correct_subject-key.pem: + openssl ecparam -name prime256v1 -genkey -noout -out irrelevant_san_correct_subject-key.pem + +clean: + rm -f *.pfx *.pem *.srl ca2-database.txt ca2-serial fake-startcom-root-serial *.print *.old fake-startcom-root-issued-certs/*.pem + @> fake-startcom-root-database.txt + +test: agent1-verify agent2-verify agent3-verify agent4-verify agent5-verify agent6-verify agent7-verify agent8-verify agent10-verify ec10-verify + +%-cert.pem.print: %-cert.pem + openssl x509 -in $< -text -noout > $@ + +.PHONY: all clean test agent1-verify agent2-verify agent3-verify agent4-verify agent5-verify agent6-verify agent7-verify agent8-verify agent10-verify ec10-verify diff --git a/test/fixtures/keys/agent1-cert.pem b/test/fixtures/keys/agent1-cert.pem new file mode 100644 index 0000000..664d00c --- /dev/null +++ b/test/fixtures/keys/agent1-cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC2DCCAkGgAwIBAgIJAOzJuFYnDamoMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu +dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2ExMSAwHgYJKoZIhvcNAQkB +FhFyeUB0aW55Y2xvdWRzLm9yZzAgFw0xODExMTYxODQyMjFaGA8yMjkyMDgzMDE4 +NDIyMVowfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEP +MA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQ8wDQYDVQQDDAZhZ2Vu +dDExIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDvVEBwFjfiirsDjlZB+CjYNMNCqdJe27hqK/b72AnL +jgN6mLcXCOABJC5N61TGFkiF9Zndh6IyFXRZVb4gQX4zxNDRuAydo95BmiYHGV0v +t1ZXsLv7XrfQu6USLRtpZMe1cNULjsAB7raN+1hEN1CPMSmSjWc7MKPgv09QYJ5j +cQIDAQABo2EwXzBdBggrBgEFBQcBAQRRME8wIwYIKwYBBQUHMAGGF2h0dHA6Ly9v +Y3NwLm5vZGVqcy5vcmcvMCgGCCsGAQUFBzAChhxodHRwOi8vY2Eubm9kZWpzLm9y +Zy9jYS5jZXJ0MA0GCSqGSIb3DQEBCwUAA4GBAHrKvx2Z4fsF7b3VRgiIbdbFCfxY +ICvoJ0+BObYPjqIZZm9+/5c36SpzKzGO9CN9qUEj3KxPmijnb+Zjsm1CSCrG1m04 +C73+AjAIPnQ+eWZnF1K4L2kuEDTpv8nQzYKYiGxsmW58PSMeAq1TmaFwtSW3TxHX +7ROnqBX0uXQlOo1m +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/agent1-csr.pem b/test/fixtures/keys/agent1-csr.pem new file mode 100644 index 0000000..6ed2fb3 --- /dev/null +++ b/test/fixtures/keys/agent1-csr.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIB4jCCAUsCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJTRjEPMA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQ8wDQYDVQQD +DAZhZ2VudDExIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvVEBwFjfiirsDjlZB+CjYNMNCqdJe27hq +K/b72AnLjgN6mLcXCOABJC5N61TGFkiF9Zndh6IyFXRZVb4gQX4zxNDRuAydo95B +miYHGV0vt1ZXsLv7XrfQu6USLRtpZMe1cNULjsAB7raN+1hEN1CPMSmSjWc7MKPg +v09QYJ5jcQIDAQABoCUwIwYJKoZIhvcNAQkHMRYMFEEgY2hhbGxlbmdlIHBhc3N3 +b3JkMA0GCSqGSIb3DQEBCwUAA4GBAN3UIAdShj7eA91fH8m8UQBJndgigNwt88qk +S2kS3XfZqkEawMu2HF/y5yWX7EyGs7OkRXZxJSR67GlgdrTi82qCBC3H2xF7fKXr +s5b6ges5NZFjEA9JTvX5PFSAfo5APbXuuhRWBdxvagi00szTnYiaKgGU4C/dZWAz +E0/tTFT4 +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/agent1-key.pem b/test/fixtures/keys/agent1-key.pem new file mode 100644 index 0000000..fe750de --- /dev/null +++ b/test/fixtures/keys/agent1-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDvVEBwFjfiirsDjlZB+CjYNMNCqdJe27hqK/b72AnLjgN6mLcX +COABJC5N61TGFkiF9Zndh6IyFXRZVb4gQX4zxNDRuAydo95BmiYHGV0vt1ZXsLv7 +XrfQu6USLRtpZMe1cNULjsAB7raN+1hEN1CPMSmSjWc7MKPgv09QYJ5jcQIDAQAB +AoGAbqk3TlyHpKFfDarf6Yr0X9wtuQJK+n+ACt+fSR3AkbVtmF9KsUTyRrTTEEZT +IXCmQgKpDYysi5nt/WyvB70gu6xGYbT6PzZaf1RmcpWd1pLcdyBOppY6y7nTMZA3 +BVFfmIPSmAvtCuzZwQFFnNoKH3d6cqna+ZQJ0zvCLCSLcw0CQQD6tswNlhCIfguh +tvhw7hJB5vZPWWEzyTQl8nVdY6SbxAT8FTx0UjxsKgOiJFzAGAVoCi40oRKIHhrw +pKwHsEqTAkEA9GABbi2xqAmhPn66e0AiU8t2uv69PISBSt2tXbUAburJFj+4rYZW +71QIbSKEYceveb7wm0NP+adgZqJlxn7oawJBAOjfK4+fCIJPWWx+8Cqs5yZxae1w +HrokNBzfJSZ2bCoGm36uFvYQgHETYUaUsdX3OeZWNm7KAdWO6QUGX4fQtqMCQGXv +OgmEY+utAKZ55D2PFgKQB1me8r6wouHgr/U7kA+0Peba86TmOZMhIVaspD3JNqf4 +/pI1NMH1kF+fdAalXzsCQQCelwr9I3FWhx336CWrfAY20xbiMOWMyAhrjVrexgUD +53Y6AhSaRC725pZTgO2PQ4AjkGLIP61sZKgTrXS85KmJ +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/agent1.cnf b/test/fixtures/keys/agent1.cnf new file mode 100644 index 0000000..c4739da --- /dev/null +++ b/test/fixtures/keys/agent1.cnf @@ -0,0 +1,26 @@ +[ req ] +default_bits = 1024 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = Joyent +OU = Node.js +CN = agent1 +emailAddress = ry@tinyclouds.org + +[ req_attributes ] +challengePassword = A challenge password + +[ v3_ca ] +authorityInfoAccess = @issuer_info + +[ issuer_info ] +OCSP;URI.0 = http://ocsp.nodejs.org/ +caIssuers;URI.0 = http://ca.nodejs.org/ca.cert diff --git a/test/fixtures/keys/agent1.pfx b/test/fixtures/keys/agent1.pfx new file mode 100644 index 0000000..f830ecc Binary files /dev/null and b/test/fixtures/keys/agent1.pfx differ diff --git a/test/fixtures/keys/agent10-cert.pem b/test/fixtures/keys/agent10-cert.pem new file mode 100644 index 0000000..8aaabd3 --- /dev/null +++ b/test/fixtures/keys/agent10-cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIICfzCCAeigAwIBAgIJAOyvM6GMZDW6MA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD +VQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMR8wHQYDVQQKDBZUaGUg +Tm9kZS5qcyBGb3VuZGF0aW9uMRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANj +YTQxHjAcBgkqhkiG9w0BCQEWD2NhNEBleGFtcGxlLm9yZzAgFw0xODExMTYxODQy +MjFaGA8yMjkyMDgzMDE4NDIyMVoweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNB +MQswCQYDVQQHDAJTRjEfMB0GA1UECgwWVGhlIE5vZGUuanMgRm91bmRhdGlvbjEQ +MA4GA1UECwwHTm9kZS5qczEcMBoGA1UEAwwTYWdlbnQxMC5leGFtcGxlLmNvbTCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArV2diVumrKDS5k81MrcdECnYYVZ5 +feQ/FZDqwEHM/zlXvs6vphU3rGmZeASMQEdHg7vUjzzvE8PDqJuJXKrC5lEO1OUY +eUDhaZ/QvYS9tDp7qTJzORxT9im65dQH0Xq5JQwTy30hidQHxOgAkILNive07/Jk +N1vle6TnZX6K/dkCAwEAATANBgkqhkiG9w0BAQsFAAOBgQAAg+FpvhA6coalWxGR +acWiUbc7CJ4RWjlSeA+fhd1G00x0Hl5hjt6IAqEHe4T9fV41U05X1eo5KaN3jXWU +IS56SVX8BxOhU53lr0iID0MpxMqttA9LgjE3fc6uAjThnx1zX50VGR4P8LQqG+HL +WJUW0+3oJrOgRbJ6wAEs0iCcTg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICkzCCAfygAwIBAgIJAJHwBmNgafKbMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu +dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2EyMSAwHgYJKoZIhvcNAQkB +FhFyeUB0aW55Y2xvdWRzLm9yZzAgFw0xODExMTYxODQyMjFaGA8yMjkyMDgzMDE4 +NDIyMVowgYgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0Yx +HzAdBgNVBAoMFlRoZSBOb2RlLmpzIEZvdW5kYXRpb24xEDAOBgNVBAsMB05vZGUu +anMxDDAKBgNVBAMMA2NhNDEeMBwGCSqGSIb3DQEJARYPY2E0QGV4YW1wbGUub3Jn +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDC1M2aGVYsmrBiut1n0nfTU+9v +TNVdAmKQBjnNsv3IIch/PPaEOIEm7dFhgdk86Z+wVCN3sAKu54Bz4JDKdPsFGvDy +18JGuGH1vIVW5285IW7fMrzvAdZtETeBAiPM10Q69ddB4M6FbLiF273ZqCJ+vSsw +kl5Dkas8YTZ0uwqKjQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4GBAGDMGSbPg/B4OripSxT2scXFIwoej47PW1byJgWaGoMJ8zgKUoKE7Z7A +aWQbD22In05F0kBllqpSJWEZpTuVFsyyLeb3R7cuGQWs/puaaPul7sx+PRGhwxYe +nrNIGtsaBf8TO/kb5lMiXWbhM5gZbBtbMMv3xWA4FxqU0AgfO3jM +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/agent10-csr.pem b/test/fixtures/keys/agent10-csr.pem new file mode 100644 index 0000000..b96e682 --- /dev/null +++ b/test/fixtures/keys/agent10-csr.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIB3TCCAUYCAQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJTRjEfMB0GA1UECgwWVGhlIE5vZGUuanMgRm91bmRhdGlvbjEQMA4GA1UECwwH +Tm9kZS5qczEcMBoGA1UEAwwTYWdlbnQxMC5leGFtcGxlLmNvbTCBnzANBgkqhkiG +9w0BAQEFAAOBjQAwgYkCgYEArV2diVumrKDS5k81MrcdECnYYVZ5feQ/FZDqwEHM +/zlXvs6vphU3rGmZeASMQEdHg7vUjzzvE8PDqJuJXKrC5lEO1OUYeUDhaZ/QvYS9 +tDp7qTJzORxT9im65dQH0Xq5JQwTy30hidQHxOgAkILNive07/JkN1vle6TnZX6K +/dkCAwEAAaAlMCMGCSqGSIb3DQEJBzEWDBRBIGNoYWxsZW5nZSBwYXNzd29yZDAN +BgkqhkiG9w0BAQsFAAOBgQBeyxGhHnFF0ifHhWUbqqMM9zJ5OhLjGsQ0gvmK/LHL +vmGJ43XgeYiN/U6xREQ7DZMss+C14mfQvp5oM0zQRWwQhLgV7YlIIIe09CYTKTfC +xxc18OJewNQUje5cG5aSMZb2HfHmLDaavAJqK0Yaoj69e+iEnAkVFVZALqlhezS+ +xQ== +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/agent10-key.pem b/test/fixtures/keys/agent10-key.pem new file mode 100644 index 0000000..dd589ca --- /dev/null +++ b/test/fixtures/keys/agent10-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCtXZ2JW6asoNLmTzUytx0QKdhhVnl95D8VkOrAQcz/OVe+zq+m +FTesaZl4BIxAR0eDu9SPPO8Tw8Oom4lcqsLmUQ7U5Rh5QOFpn9C9hL20OnupMnM5 +HFP2Kbrl1AfRerklDBPLfSGJ1AfE6ACQgs2K97Tv8mQ3W+V7pOdlfor92QIDAQAB +AoGAIAjiaVVEMTXugpw0SlDH0ArLbwEZpgedGJEUr7348VhZPGrYzimxhexlbWX5 +vI7vSgpVNrqduts7tlY3RaZQKQzFkSqkUUb432bXkJLHNIspd0XHOO2Hy/ZbTg0n +OIQes7C91Z/OLUi9esXoh4AMsAoxiHoVee0dkEJt8RoywNkCQQDk39QND1rQ2eJq +Fcfj/v6fXgsHmQT16w2Ii9P5uPAeIGrGcrsCoVWrsh+wjSlYc7emGV8JINiltNhZ +fSg6ux8/AkEAwemf5LryUDCoZ68MlAYMcH+G7gtm06d+FUFpBclT3hJeXnUAdlyU +6kCvazcVTQQKDTWIS1oIBuleVmc/VWc05wJBAMVRZyq/QydtwTJ+hq+8pl5VIKMz +PECbnjZLfrv7wh/nCMcAINRarVZyIbn/aVbVpM3xb6qaA82QxTkZmvZPXtcCQFqx +pjMYrNSMrXcxDDT/Tzoeq0ES3BkKMZJHcZNfQnaPKMwM9RZm3s9hSapfrPrEdN8Q +tppnlXGGHLVUvO54wukCQBKbjspQONRZFBh9Fdeyf/rX+inBLhuMSnsY3FeBp0c6 +TAPbyuiezn7axr9kILojdjZgXK1b+MTHSEjqCpHwIvg= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/agent10.cnf b/test/fixtures/keys/agent10.cnf new file mode 100644 index 0000000..7dc37fc --- /dev/null +++ b/test/fixtures/keys/agent10.cnf @@ -0,0 +1,17 @@ +[ req ] +default_bits = 1024 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = The Node.js Foundation +OU = Node.js +CN = agent10.example.com + +[ req_attributes ] +challengePassword = A challenge password diff --git a/test/fixtures/keys/agent10.pfx b/test/fixtures/keys/agent10.pfx new file mode 100644 index 0000000..c6a5dd0 Binary files /dev/null and b/test/fixtures/keys/agent10.pfx differ diff --git a/test/fixtures/keys/agent2-cert.pem b/test/fixtures/keys/agent2-cert.pem new file mode 100644 index 0000000..1275054 --- /dev/null +++ b/test/fixtures/keys/agent2-cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICczCCAdwCCQCtrfdcbYDS0jANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAO +BgNVBAsMB05vZGUuanMxDzANBgNVBAMMBmFnZW50MjEgMB4GCSqGSIb3DQEJARYR +cnlAdGlueWNsb3Vkcy5vcmcwIBcNMTgxMTE2MTg0MjIxWhgPMjI5MjA4MzAxODQy +MjFaMH0xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzAN +BgNVBAoMBkpveWVudDEQMA4GA1UECwwHTm9kZS5qczEPMA0GA1UEAwwGYWdlbnQy +MSAwHgYJKoZIhvcNAQkBFhFyeUB0aW55Y2xvdWRzLm9yZzCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAq2BtovcwlIUNVAi3ukbyQveZNg3VkkxB6Ih+CL70VEX3 +0ji5EpQqGjwk5Ub6tRYfuGHwBWSO8+W2PwtdplDbuIOoK5MAp+wkDRCC8H1l4i8c +dOA97Edw3nRU6MiPrUiirV1u2liHYp6YDLWix80UiNnH6EOLFd5Wy5s7CSt4rEkC +AwEAATANBgkqhkiG9w0BAQsFAAOBgQCg75WmaDjpwBmMAl/+5oK8D7p2V98ChhyV +bIwhpLPo9jf7iPW9VayxQKvV5HXuiT1vpIjuvhbRRH3wGj3by0TtI+sqECcPKu1v +5bg2e+bX8s1OXFJh+x93KCnrcFNEwOVk9VjUX+ilJdTkdcGA75N4ZO7qEV5wKl9g +658PRZl3KA== +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/agent2-csr.pem b/test/fixtures/keys/agent2-csr.pem new file mode 100644 index 0000000..02217de --- /dev/null +++ b/test/fixtures/keys/agent2-csr.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIB4jCCAUsCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJTRjEPMA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQ8wDQYDVQQD +DAZhZ2VudDIxIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrYG2i9zCUhQ1UCLe6RvJC95k2DdWSTEHo +iH4IvvRURffSOLkSlCoaPCTlRvq1Fh+4YfAFZI7z5bY/C12mUNu4g6grkwCn7CQN +EILwfWXiLxx04D3sR3DedFToyI+tSKKtXW7aWIdinpgMtaLHzRSI2cfoQ4sV3lbL +mzsJK3isSQIDAQABoCUwIwYJKoZIhvcNAQkHMRYMFEEgY2hhbGxlbmdlIHBhc3N3 +b3JkMA0GCSqGSIb3DQEBCwUAA4GBAJqJXQdhuPxsJA6O/yWt9t9lQIgoYCQyCG2w +Xl0n84f14WDi/N9rF0IfGMSVWoLDCc5gcoqKal0X/vQI4lpPiZ0hctU5cXru1Pvi +yfDbIPB0td7POf3Q3Ge3a3RHf4I4cfRuzA6jfzMlorpgQmAKL+sstC94LZZnDiNp +ihciaeK7 +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/agent2-key.pem b/test/fixtures/keys/agent2-key.pem new file mode 100644 index 0000000..6aca2ed --- /dev/null +++ b/test/fixtures/keys/agent2-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCrYG2i9zCUhQ1UCLe6RvJC95k2DdWSTEHoiH4IvvRURffSOLkS +lCoaPCTlRvq1Fh+4YfAFZI7z5bY/C12mUNu4g6grkwCn7CQNEILwfWXiLxx04D3s +R3DedFToyI+tSKKtXW7aWIdinpgMtaLHzRSI2cfoQ4sV3lbLmzsJK3isSQIDAQAB +AoGAbaqNPiXUjpX+C3Jwr+FKkhQDlRWRP8dQvc7qaApaK7uCdKIbvInXz2YBbj7X +nB4GOmVhxPGukODEmN9bFFzV3NbQVcAO8iPI6ldkYCmpmwfeEA1UJnBfBC2IYVFr +0TSuq7OdniiO+FLhZvSAJXN+5yDv66nZEnojMuu1oqpG/gECQQDdxzSeNbOY2ak8 +Db2ZYKSBWjxVbW+UC8mYA7jHVpcUxlNnamcJrhg6hEfJqax6c/vAefECFsngG7yy +XVTYX6rpAkEAxdI83zQz4NqYkZmFlCElNDRF7S1Sa2eX1HzzJYlOEMx3P1iE/z4U +HOQh9py2jlY1th3GdbChF1VWMIOqGCtaYQJAJwxS/GQyKgBw5qz4rA+zBz9vDg+F +rMhih0xodViOo07EEppOaArqIyt1RFGGl8ziD6Kox5hhlP7tO25paOt3OQJBALSL +6y60EF06ZWENwxKtJa19wAx1/vEz/SjcWXZ62JsQYg2Yltn2KJktxam04hEKsb7j +cgxcBsqrAh0JLicc+kECQDmH1wTvulw3E59jqOaKEbYNQFi18zzFkIIoNRVqMhtt +zTJx8NYT9NUS3YE4OcUX0dQtVO3W+NIVrniaY8i29UM= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/agent2.cnf b/test/fixtures/keys/agent2.cnf new file mode 100644 index 0000000..90a1cf9 --- /dev/null +++ b/test/fixtures/keys/agent2.cnf @@ -0,0 +1,19 @@ +[ req ] +default_bits = 1024 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = Joyent +OU = Node.js +CN = agent2 +emailAddress = ry@tinyclouds.org + +[ req_attributes ] +challengePassword = A challenge password + diff --git a/test/fixtures/keys/agent3-cert.pem b/test/fixtures/keys/agent3-cert.pem new file mode 100644 index 0000000..9460b44 --- /dev/null +++ b/test/fixtures/keys/agent3-cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICcDCCAdkCCQCR8AZjYGnynDANBgkqhkiG9w0BAQsFADB6MQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAO +BgNVBAsMB05vZGUuanMxDDAKBgNVBAMMA2NhMjEgMB4GCSqGSIb3DQEJARYRcnlA +dGlueWNsb3Vkcy5vcmcwIBcNMTgxMTE2MTg0MjIxWhgPMjI5MjA4MzAxODQyMjFa +MH0xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNV +BAoMBkpveWVudDEQMA4GA1UECwwHTm9kZS5qczEPMA0GA1UEAwwGYWdlbnQzMSAw +HgYJKoZIhvcNAQkBFhFyeUB0aW55Y2xvdWRzLm9yZzCBnzANBgkqhkiG9w0BAQEF +AAOBjQAwgYkCgYEAv+hwfSVuJfHDQOXmF2D/HsI2JZkspfrPQE/ZL1tII2cXRnus +IqWZvLg9v7IVY0gSvx5gWMHxmqqaK75McVJvO1XEzLYpa9Ddnj06xqNWl6hwLHnP +bclRi2n63Cs6zSM80r1iQ16ovZ0hyWPjXaBlWmb71QeeBp6ynxhB+yA0eZsCAwEA +ATANBgkqhkiG9w0BAQsFAAOBgQA/C2xJIYA3Vo8pr1cfmzN+os9uvbMQEAegg6W+ +6/t82tLGnrCEglMTFHFp7MJCNiKCY16Ixi5WCZoUrGCfbh+Obtd4bP/wAlR8AS67 +lYZDvjADGU4e7Aqu0o0AHeb4MiRUQbkD0EUioyD8091Qhzlrx43UtdojPvakwAXM +N/LFEw== +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/agent3-csr.pem b/test/fixtures/keys/agent3-csr.pem new file mode 100644 index 0000000..21bb06e --- /dev/null +++ b/test/fixtures/keys/agent3-csr.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIB4jCCAUsCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJTRjEPMA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQ8wDQYDVQQD +DAZhZ2VudDMxIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/6HB9JW4l8cNA5eYXYP8ewjYlmSyl+s9A +T9kvW0gjZxdGe6wipZm8uD2/shVjSBK/HmBYwfGaqporvkxxUm87VcTMtilr0N2e +PTrGo1aXqHAsec9tyVGLafrcKzrNIzzSvWJDXqi9nSHJY+NdoGVaZvvVB54GnrKf +GEH7IDR5mwIDAQABoCUwIwYJKoZIhvcNAQkHMRYMFEEgY2hhbGxlbmdlIHBhc3N3 +b3JkMA0GCSqGSIb3DQEBCwUAA4GBAFHJUONDqOhZpGN8ZCFkWkGyD4iDPGdJyR1f +lh1N2vSf9vx663ni6lG9XQrQZXyPH8n7vvyyX1bJE5X6dAKuiD4GYlcGUUCnsvcA +r+JzSBrbtwD57bPnn21YSUl2QEoG2b+/6uPKWxKr8e1sreMxHOLwsPgSavnQ84Bc +GvSLlIcR +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/agent3-key.pem b/test/fixtures/keys/agent3-key.pem new file mode 100644 index 0000000..3470fdf --- /dev/null +++ b/test/fixtures/keys/agent3-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC/6HB9JW4l8cNA5eYXYP8ewjYlmSyl+s9AT9kvW0gjZxdGe6wi +pZm8uD2/shVjSBK/HmBYwfGaqporvkxxUm87VcTMtilr0N2ePTrGo1aXqHAsec9t +yVGLafrcKzrNIzzSvWJDXqi9nSHJY+NdoGVaZvvVB54GnrKfGEH7IDR5mwIDAQAB +AoGAa94V5HH2kMNskXznsPpnS/2z+7w2OXFZrvdyx0iSqruWfJqlLbBRUp9orehG +V1C6oMxNMXaJ+/qqv62uQAAq3oCPHRrN5a1fLzYKk/ixUbk0F7saNvOsmWqbSIzv +OEtsHBt3zJxEkgLFzuaFnfoBoFL7lvJYol+4QPVvxYj2exkCQQDqVRGFbhRmKBZ+ +ienF9JpUOruKEsW4lmSKP2fUAL4rH40cJEFD0OI80/WdN/34USPOEqSsFTZITvFH ++y+45PD/AkEA0acinvAAb0FLlMlhcG0LGzIwcYUWcEpPxwkioGgwGaEPLR276gHv +NvgtL7xgLi4QMKB0n48zz4W4Usww6QmbZQJBAOGIZoipXfDEfIHlcp4XwcF3lbBa +SPpTpQh55hBhdqZCg6mmKzp9/IDW7/oVPdaVIYTg5KTK9ae6cvb4hwHJNzkCQE3p +YclVAaRWzKK/b/Ga5Gy36x7UybDzPNCHyZF5Bp8PppcqnKHrFB4GfqxlwgyHW8bm +alC9pBBz7jr+3RJNWq0CQFHxhrjdhQFhYJoW8b+pqE1ryNSSSf/1yBEug6Xsqsjn +MSuyCTLXRpoS/LAdL95ENCX3ULsu/5nlpKZmy99yY2M= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/agent3.cnf b/test/fixtures/keys/agent3.cnf new file mode 100644 index 0000000..3633717 --- /dev/null +++ b/test/fixtures/keys/agent3.cnf @@ -0,0 +1,19 @@ +[ req ] +default_bits = 1024 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = Joyent +OU = Node.js +CN = agent3 +emailAddress = ry@tinyclouds.org + +[ req_attributes ] +challengePassword = A challenge password + diff --git a/test/fixtures/keys/agent4-cert.pem b/test/fixtures/keys/agent4-cert.pem new file mode 100644 index 0000000..d39e9d2 --- /dev/null +++ b/test/fixtures/keys/agent4-cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICjjCCAfegAwIBAgIJAJHwBmNgafKaMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu +dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2EyMSAwHgYJKoZIhvcNAQkB +FhFyeUB0aW55Y2xvdWRzLm9yZzAgFw0xODExMTYxODQyMjBaGA8yMjkyMDgzMDE4 +NDIyMFowfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEP +MA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQ8wDQYDVQQDDAZhZ2Vu +dDQxIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQCvcVH69FzdPGCUXgwowuBz4lLAV+COzishbuyNGF5l +J6mw6eY8gizLmpxh0r6d/REnlzKRy3Uy9FdZEQZKKfeK63MxLU6BYaHX0fnqz2y1 +oCaA2eW4yeGOLaSBcjEKHIs964Ik9VKEDnioYtoObbFihRbcS6QLNajQR9ij+7hl +pQIDAQABoxcwFTATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOB +gQAykzKtLG++xAfLoq2AZ3OKlEYGC4RuOCy9J75RYWNRuy18UnYl0HGSepeqQ/4c +0r+dy/SLUVKxC7e87hs7gP8UX+f9UaVM7dvqvZbZMVH+A6w2nIAcO3zwtSJlfQ8H +NJAdQl1lZ6qc97APtBlfeTMTdi/hTghqZLah21/hIE5lFw== +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/agent4-csr.pem b/test/fixtures/keys/agent4-csr.pem new file mode 100644 index 0000000..3e803a6 --- /dev/null +++ b/test/fixtures/keys/agent4-csr.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIB4jCCAUsCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJTRjEPMA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQ8wDQYDVQQD +DAZhZ2VudDQxIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvcVH69FzdPGCUXgwowuBz4lLAV+COzish +buyNGF5lJ6mw6eY8gizLmpxh0r6d/REnlzKRy3Uy9FdZEQZKKfeK63MxLU6BYaHX +0fnqz2y1oCaA2eW4yeGOLaSBcjEKHIs964Ik9VKEDnioYtoObbFihRbcS6QLNajQ +R9ij+7hlpQIDAQABoCUwIwYJKoZIhvcNAQkHMRYMFEEgY2hhbGxlbmdlIHBhc3N3 +b3JkMA0GCSqGSIb3DQEBCwUAA4GBAJ4tZ0hFXYqGQ0BDpcI6QIjufzoFHXMHBmE0 +wHU1f8jVM2v9Df5eInArMvAVya4gXtuZnMpRZKNrcbnwPUK9spwIzHxPyw7qjeCP +SG+TusJoFFIGgpZBo6zVdtpRCRbTxNfKteK+y34g+sYZolt88AmlzY8H2QYeQabI +1SBuLdBH +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/agent4-key.pem b/test/fixtures/keys/agent4-key.pem new file mode 100644 index 0000000..1e9a8b8 --- /dev/null +++ b/test/fixtures/keys/agent4-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQCvcVH69FzdPGCUXgwowuBz4lLAV+COzishbuyNGF5lJ6mw6eY8 +gizLmpxh0r6d/REnlzKRy3Uy9FdZEQZKKfeK63MxLU6BYaHX0fnqz2y1oCaA2eW4 +yeGOLaSBcjEKHIs964Ik9VKEDnioYtoObbFihRbcS6QLNajQR9ij+7hlpQIDAQAB +AoGASgLVIbf6gP4ahByUppFnXJuqayfnPHcu7MC9m9we3i94P4C8tuP3f8Dunbno +3f9HQFthYu3guCkpvBIZhCnmGgrge/Rm5IYN9Jktc8gTSQ0NlJbr3hjgHEkJXhca +6zE/sFEgZVWF/yCIunyU3umbWBJE9R+suk+mpe1wZ9T+AXUCQQDU208b7AvW9Yoo +8SomHE5B1FJOwnyEWHBJ9W0myGXOrJbf6zNJ4eOLQsn1UWSsI9tzgeh49f6smc7B +bhWhHqoLAkEA0wCs6zyKs0pbzGfGYQFafDbjSUBbT+nn4tXR+3O7Z8K6x3gt7DBx +VtlbJtfBBWCCrIgYsrU3TUwtweDV6umtDwJBAJN7tU+SeQ2jKex+VQb8+9gu5iy+ +IwqMQJluHQgPOENAYHWcAPiDNGdMiqSYldmUKrzY2RvezmwHUjPCM+hkV8sCQQCF +MQL2RrQi8sg5ojQmXa1ZhWg5gAdjzXnTxTcUa/ybRd+TNDiAxB93PCL+xOiR1VcH +Q62beSqcf37OyHcgHztfAkAEHZwjVSRXFELBQDPzespHXVC3rwpQlbd1tqOybmPd +tpqmlWjvFxdvEDQZnemPUCtXNhMOaXcSOeeSYcUkIVX6 +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/agent4.cnf b/test/fixtures/keys/agent4.cnf new file mode 100644 index 0000000..8839ba6 --- /dev/null +++ b/test/fixtures/keys/agent4.cnf @@ -0,0 +1,21 @@ +[ req ] +default_bits = 1024 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = Joyent +OU = Node.js +CN = agent4 +emailAddress = ry@tinyclouds.org + +[ req_attributes ] +challengePassword = A challenge password + +[ ext_key_usage ] +extendedKeyUsage = clientAuth diff --git a/test/fixtures/keys/agent5-cert.pem b/test/fixtures/keys/agent5-cert.pem new file mode 100644 index 0000000..31e9526 --- /dev/null +++ b/test/fixtures/keys/agent5-cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIChTCCAe6gAwIBAgIJAJHwBmNgafKdMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu +dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2EyMSAwHgYJKoZIhvcNAQkB +FhFyeUB0aW55Y2xvdWRzLm9yZzAgFw0xODExMTYxODQyMjFaGA8yMjkyMDgzMDE4 +NDIyMVowdDELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MREwDwYDVQQK +DAhUcmVzb3JpdDEWMBQGA1UEAwwNw4Fkw6FtIExpcHBhaTEnMCUGCSqGSIb3DQEJ +ARYYYWRhbS5saXBwYWlAdHJlc29yaXQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQCksphKSkbE4aCa68r2o7j2xWbxbWP+bjAGwWWYQwnacQ6p5tlhaN10 +ebDAmPVakLo8xxPEXMqWFxCU2AWg0Wtd6TgqIQtIMNXQz6cif5Ufxo3lhus+dLhs +flz+yTpFD5vREvn0kQ9ce+jVjVzh8bK5qfpaNlaIqQc64WpJKQe+8QIDAQABoxcw +FTATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOBgQAYcJ8LyVUB +5GNqnVdJW4dndeUvllYW3txxuUXhcxgvT7b0glDXSp/cRq0yxZb1jRCLqESsHer0 +o064S5GCWCktZWwbDo75YFE2Vo1R8TChhmD1txFcAi2J161yn9QVoHVbOhyyIHXz +Yw9zhrnJURZA+1lUpIarcRmkUsbSR25gyg== +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/agent5-csr.pem b/test/fixtures/keys/agent5-csr.pem new file mode 100644 index 0000000..6d670ff --- /dev/null +++ b/test/fixtures/keys/agent5-csr.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIB2TCCAUICAQAwdDELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MREw +DwYDVQQKDAhUcmVzb3JpdDEWMBQGA1UEAwwNw4Fkw6FtIExpcHBhaTEnMCUGCSqG +SIb3DQEJARYYYWRhbS5saXBwYWlAdHJlc29yaXQuY29tMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQCksphKSkbE4aCa68r2o7j2xWbxbWP+bjAGwWWYQwnacQ6p +5tlhaN10ebDAmPVakLo8xxPEXMqWFxCU2AWg0Wtd6TgqIQtIMNXQz6cif5Ufxo3l +hus+dLhsflz+yTpFD5vREvn0kQ9ce+jVjVzh8bK5qfpaNlaIqQc64WpJKQe+8QID +AQABoCUwIwYJKoZIhvcNAQkHMRYMFEEgY2hhbGxlbmdlIHBhc3N3b3JkMA0GCSqG +SIb3DQEBCwUAA4GBABmDywVdbouxznVhI5cnTB2cZTGKMDYCnYx+0pFOZw6ux1eR +oUF59E/QCIfibOI6b1+Dd7O3hK81aCQxd6oBiWWg8gyCjFcoCVqOkR/Ug176asZv +72+l6pBLYoZlmPrQXkxtfL+FtLM3/xLdt6hDSZEWyznWcraanDqKx9M4NEgG +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/agent5-key.pem b/test/fixtures/keys/agent5-key.pem new file mode 100644 index 0000000..5de95c2 --- /dev/null +++ b/test/fixtures/keys/agent5-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCksphKSkbE4aCa68r2o7j2xWbxbWP+bjAGwWWYQwnacQ6p5tlh +aN10ebDAmPVakLo8xxPEXMqWFxCU2AWg0Wtd6TgqIQtIMNXQz6cif5Ufxo3lhus+ +dLhsflz+yTpFD5vREvn0kQ9ce+jVjVzh8bK5qfpaNlaIqQc64WpJKQe+8QIDAQAB +AoGASLj7ecI2YXTnP8DiC+nbIEul2vDceFobJsB6pbLkROoq+WaPke2F64pYO5LO +s8C4G2IkHk6CvadNkQuZ4JrX9xgNdxWRHGAbDedEFnN8gi0inHQjHITj62Il9civ +JB8cR5fJxzAKZS23elrocrzU6lU90V4gm4VUUJ6dQhZYO2ECQQDYoC6r+Qf9YCNU +89/RnGdUzL26l1S/GmUw5VfIDorMNbwH1xfg6Z8MukF42Q9kZQdtoh+HVLG7Ljok +2cLmmBA9AkEAwqIlSsiGSjfJlzQoDi13X2ZgVp6/nicKan1eKCPq+EnWhA5CP1u1 +5WaYBbLjlDl7A7VA7gMd19tSNGHzRQRAxQJBAJe4kRe3wsXOqNBeQnuf7Kty/suK +JDv4s7jsWG/w53uhgwGGv92yIsiaRzLp7CLns60wqJ5zTkwIU4bt0dkJ1g0CQD9D +YQe7whqho37oTxS8po51wl6lXvdTDUmr0k0Nz7RAm990mwfpEWitPkCr8tkdDeUY +pzA2Bx9AhKnOJLqMNVkCQAwJYI4fS7Mec2f2Kv5SInxjMeasGivzDxe99EyLS8jz +dWfNwtCGA+gmpqcWqpT/JNnJgG4ljseW7Xk6YogngVU= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/agent5.cnf b/test/fixtures/keys/agent5.cnf new file mode 100644 index 0000000..710a677 --- /dev/null +++ b/test/fixtures/keys/agent5.cnf @@ -0,0 +1,21 @@ +[ req ] +string_mask = utf8only +utf8 = yes +default_bits = 1024 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no + +[ req_distinguished_name ] +C = HU +L = Budapest +O = Tresorit +CN = Ádám Lippai +emailAddress = adam.lippai@tresorit.com + +[ req_attributes ] +challengePassword = A challenge password + +[ ext_key_usage ] +extendedKeyUsage = clientAuth diff --git a/test/fixtures/keys/agent6-cert.pem b/test/fixtures/keys/agent6-cert.pem new file mode 100644 index 0000000..18ded35 --- /dev/null +++ b/test/fixtures/keys/agent6-cert.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIICbDCCAdWgAwIBAgIJANAIL0WLbvvoMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu +dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2EzMSAwHgYJKoZIhvcNAQkB +FhFyeUB0aW55Y2xvdWRzLm9yZzAgFw0xODExMTYxODQyMjFaGA8yMjkyMDgzMDE4 +NDIyMVowdDELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MREwDwYDVQQK +DAhUcmVzb3JpdDEWMBQGA1UEAwwNw4Fkw6FtIExpcHBhaTEnMCUGCSqGSIb3DQEJ +ARYYYWRhbS5saXBwYWlAdHJlc29yaXQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDBIF2kWViZb+GpjUKfQ2Jevk68mLWXib66z6vCi+mjpcvZeq6A5Z0M +qNJYftEgSykluxL9EkpRqWr6qCDsSrpazMHG2HB+yip8/lfLWCv/xGAHh9+4XY3s +UPGIGg+LmvhRCZvgxARxY2uG7AB+WZVMby4TCyAFAT7D/ri4L8iZZwIDAQABMA0G +CSqGSIb3DQEBCwUAA4GBAFU3MAVxVCmsaoNxr0y+KK/n0iEAzsOH9r0L3PL2gJtl +p62iOaOPw1/9x8c77RA6z/nXPX9IyAwASv0n8FEdxuIF2+KqFG6bXw5nyfgPIszr +U7YSV2Pi/Heinr76RrI6aWGtvEuD56Qt3Ce5TYiMnzAWtqEcPLGjgsx0MAv+m48B +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIChDCCAe2gAwIBAgIJAOzJuFYnDamnMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu +dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2ExMSAwHgYJKoZIhvcNAQkB +FhFyeUB0aW55Y2xvdWRzLm9yZzAgFw0xODExMTYxODQyMjFaGA8yMjkyMDgzMDE4 +NDIyMVowejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEP +MA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTMx +IDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQCZ9fF/1UcYaurFIX0QIyAJdojn9+bfsTcjEIoGbsAnQLz2 +bsZ4pqRNhZbYJApxqc+oDzZOqJOaxe8mlB5jUZ/sUA9Sp+wfWly95tkEHBMSse4x +UNJVM4vFPfOG4fv9fYGH3pcmAU1QnST4Fh+qZRzrh9wa99ltmB/U2mJEF6NriwID +AQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBAM3CFiDdGEcx +07J6pm4zGX399VxPr50PID110jmX7BRAfqva+wBRhwweSxZ/QRcKe1v/FK3GE87y +RbaXhFfnPRUHoUHQMtGwmZuZcdK65Pim9RPGb7qrEJ2wlPt/C1Q6VjL/fBGqjtJM +Bq/2GR2GoBsE85jGM287hcvXV0eG5OwM +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/agent6-csr.pem b/test/fixtures/keys/agent6-csr.pem new file mode 100644 index 0000000..af864a6 --- /dev/null +++ b/test/fixtures/keys/agent6-csr.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIB2TCCAUICAQAwdDELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MREw +DwYDVQQKDAhUcmVzb3JpdDEWMBQGA1UEAwwNw4Fkw6FtIExpcHBhaTEnMCUGCSqG +SIb3DQEJARYYYWRhbS5saXBwYWlAdHJlc29yaXQuY29tMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDBIF2kWViZb+GpjUKfQ2Jevk68mLWXib66z6vCi+mjpcvZ +eq6A5Z0MqNJYftEgSykluxL9EkpRqWr6qCDsSrpazMHG2HB+yip8/lfLWCv/xGAH +h9+4XY3sUPGIGg+LmvhRCZvgxARxY2uG7AB+WZVMby4TCyAFAT7D/ri4L8iZZwID +AQABoCUwIwYJKoZIhvcNAQkHMRYMFEEgY2hhbGxlbmdlIHBhc3N3b3JkMA0GCSqG +SIb3DQEBCwUAA4GBAAX9mbfgDULEA541c1teuG+eW0KLghFaaotFb0+R6WD1ZQLO +Url8y1iz6T/qqfuoAWu5OA8/7sKDdta/0mzV6UoGnDOcnWnH5FURmnQPUS/hBJ6A +mJBslJx6y0z4Rl/fxJUy5K31YbeRHHLEneM211usTv8QguAD0y2BNAQ0Mno0 +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/agent6-key.pem b/test/fixtures/keys/agent6-key.pem new file mode 100644 index 0000000..0a2f2fa --- /dev/null +++ b/test/fixtures/keys/agent6-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDBIF2kWViZb+GpjUKfQ2Jevk68mLWXib66z6vCi+mjpcvZeq6A +5Z0MqNJYftEgSykluxL9EkpRqWr6qCDsSrpazMHG2HB+yip8/lfLWCv/xGAHh9+4 +XY3sUPGIGg+LmvhRCZvgxARxY2uG7AB+WZVMby4TCyAFAT7D/ri4L8iZZwIDAQAB +AoGAIfEMRBwnxB+zq1bWRKNVII2VzPORxqZAzRg+eZyZXVeAMiKrlJ/GMDljboYr +Pt+2xZjRR4T1ZtC9qnvt/VlM0uWTEIgyzo29ZO0Bd9yMnIF2EUlzVtW07UnN6+VW +z1/RxOgBiAvkrSTgN4SOQJJIgOZYAt2Xhrkz/0CLwEOis1ECQQD+1hQkEYwvNH6Y +qUVoWvlNg0Q4kozNQCrwUrkHCtIOxCmr/KxcwcPBaVmEypcCnwf78KbgUQ/5oIgZ +OReuNcWjAkEAwgIk38VZRxSyBP1RThbKrK5h+GIwkEO7RW7lZ9yoVKhV6YDMWof0 +xCl23YwdpflaTUHuBVOYa3EPegkOGCfxbQJAdCTDpzCsMHN/Yzp6nLYhu3chJ5t7 +OqyNJVy+YXxIAlzbFTyind/dxQ+rsf7XVmV+sQ+cLs4jNsU4Yi6IIWj2ewJBAKRH +OH4bF9vulEdRUTV0ay4Jg3/VdRXTpJHIs4xc9lSpLgZJP8Ew+nvYOISlDr3qBSMC +PtBX1uqzk81cOYkO2YkCQBUVMew70XetUXgh/2KOWyG/87uYy/s/NZ/LGImvo+tq +FUapBPapob9I7WA6gRYVseiE+mSGPAciGIFg/d6iyxI= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/agent6.cnf b/test/fixtures/keys/agent6.cnf new file mode 100644 index 0000000..8fa963f --- /dev/null +++ b/test/fixtures/keys/agent6.cnf @@ -0,0 +1,18 @@ +[ req ] +string_mask = utf8only +utf8 = yes +default_bits = 1024 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no + +[ req_distinguished_name ] +C = HU +L = Budapest +O = Tresorit +CN = Ádám Lippai +emailAddress = adam.lippai@tresorit.com + +[ req_attributes ] +challengePassword = A challenge password diff --git a/test/fixtures/keys/agent6.pfx b/test/fixtures/keys/agent6.pfx new file mode 100644 index 0000000..1f1d827 Binary files /dev/null and b/test/fixtures/keys/agent6.pfx differ diff --git a/test/fixtures/keys/agent7-cert.pem b/test/fixtures/keys/agent7-cert.pem new file mode 100644 index 0000000..98d3f62 --- /dev/null +++ b/test/fixtures/keys/agent7-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDEjCCAfqgAwIBAgIJAJ4TtCDh9ccYMA0GCSqGSIb3DQEBCwUAMDIxCzAJBgNV +BAYTAkNOMQ4wDAYDVQQKDAVDTk5JQzETMBEGA1UEAwwKQ05OSUMgUk9PVDAgFw0x +ODExMTYxODQyMjFaGA8yMjkyMDgzMDE4NDIyMVowXTELMAkGA1UEBhMCVVMxCzAJ +BgNVBAgMAkNBMQswCQYDVQQHDAJTRjENMAsGA1UECgwESU9KUzERMA8GA1UECwwI +aW9qcy5vcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAM2+DkRxPeRGI4JK0YpuaqCJkNswMYMyZM4VQYyn99SL7xS8 +lB9P0vIm+K1P4198WXwUHSykWRcyy54nMNpq+9Dfy8BalHRaUa8BO/7UQgipRGi0 +HidDk/bAuNNHNIzJr2sGYGHsZuHkO9inEqDcqrSlTc0G0zyLry5LekRZRTgAlXpl +C9PsAZl0J+gGA83rUhdD/RDjaT4ldqjLKycHvcMLCIS6Wq0TszYCUdbvMsageDcQ +zQerIJkHzfJFGYCetQR5/fiIKyF1bICKD22AnpLlfhdthAhLVlYQn34IlYJwdptq +2miktcqvBw4rnBhE1ONj8DqF61S9BKr9aCf5OoMCAwEAATANBgkqhkiG9w0BAQsF +AAOCAQEAbek4GyLi+vVeUg3Od17lr8qT108iP3esUJ5cfPrXaexxcypRAmYPB7r4 +NA/vVeOTNkxbb07Ct8dmz+rTn+FflI9h5eKRC7hbH/rFTDEfnoS66eSlxr/jJgtv +LWuKTMJhzXjgliHleaBDGzo3mR5hcJbQvj9qyK4pXjxlt2QvkPdx2H9H76+nBh1g +TY5bW4+3NFaHfaR2p2T20bY3no25/vfV7K5endff6pgzcZR3/SptGTywC4EZzIcz +9Q0JnALQtxAAxq1yrljQcvpjM/aAYY7BxwFHJuLmb/FpMulkzZ2vJALluF/3G5ne +RT9QhxJdwUz+Juv5QKH2i+nnb2Ur6g== +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/agent7-csr.pem b/test/fixtures/keys/agent7-csr.pem new file mode 100644 index 0000000..a3634a6 --- /dev/null +++ b/test/fixtures/keys/agent7-csr.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICxzCCAa8CAQAwXTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJTRjENMAsGA1UECgwESU9KUzERMA8GA1UECwwIaW9qcy5vcmcxEjAQBgNVBAMM +CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM2+DkRx +PeRGI4JK0YpuaqCJkNswMYMyZM4VQYyn99SL7xS8lB9P0vIm+K1P4198WXwUHSyk +WRcyy54nMNpq+9Dfy8BalHRaUa8BO/7UQgipRGi0HidDk/bAuNNHNIzJr2sGYGHs +ZuHkO9inEqDcqrSlTc0G0zyLry5LekRZRTgAlXplC9PsAZl0J+gGA83rUhdD/RDj +aT4ldqjLKycHvcMLCIS6Wq0TszYCUdbvMsageDcQzQerIJkHzfJFGYCetQR5/fiI +KyF1bICKD22AnpLlfhdthAhLVlYQn34IlYJwdptq2miktcqvBw4rnBhE1ONj8DqF +61S9BKr9aCf5OoMCAwEAAaAlMCMGCSqGSIb3DQEJBzEWDBRBIGNoYWxsZW5nZSBw +YXNzd29yZDANBgkqhkiG9w0BAQsFAAOCAQEAKcwglrFyAj+pc26WqHv5R9NToUKF +1Yd5zkExZHWH5glrAprCdRhUY575KcY1Sz4KCCRADdYo7KGUFHi4B/N+iyIS9m3t +TWpJQVq4o98hF0+FalhdYyIND2FdiTmdxzmi788JFcTKZT1ryKyoB7vAj0kvXdED +3VU2mDoxPc17ZInR5x0A8hJHDHY9SlDL96n6QTEAByXfqNq/c8S7bkBPEJJUln7G +L/8YWxQJ25971PEX/QLbWADMkSPGkHCHF0znZhtJ6wxTFRkdQJSa9FASKpVgDMMu +wQVEnOa10z2aQ3PayZUHh43zq441FakE7LAseeOoJChPb00lFrN3ph+TnQ== +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/agent7-key.pem b/test/fixtures/keys/agent7-key.pem new file mode 100644 index 0000000..bc4cbe8 --- /dev/null +++ b/test/fixtures/keys/agent7-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAzb4ORHE95EYjgkrRim5qoImQ2zAxgzJkzhVBjKf31IvvFLyU +H0/S8ib4rU/jX3xZfBQdLKRZFzLLnicw2mr70N/LwFqUdFpRrwE7/tRCCKlEaLQe +J0OT9sC400c0jMmvawZgYexm4eQ72KcSoNyqtKVNzQbTPIuvLkt6RFlFOACVemUL +0+wBmXQn6AYDzetSF0P9EONpPiV2qMsrJwe9wwsIhLparROzNgJR1u8yxqB4NxDN +B6sgmQfN8kUZgJ61BHn9+IgrIXVsgIoPbYCekuV+F22ECEtWVhCffgiVgnB2m2ra +aKS1yq8HDiucGETU42PwOoXrVL0Eqv1oJ/k6gwIDAQABAoIBAEvZcmcXHIsotHSX +YrLXTCYNMUMtfENy86jqOzVAw2Qvhp+teiolAo7VgT5bwmZ0cIUG4U6Q9GtSBbEz +n5YWdOmnZ/VtL2fJ2G1dViH3XLTWumqjZK5zAnyoxjrV+HCi9jHNswDG55MF0m5o +Ab0ePSzF+G3Kw1uB376Agv3pr1QacVmMxsYxuUm8Ks0H3hB1E1rYByOcFgljq0EA +E7GuYG1JyjFcGsoyNPykpJ8Ri6mko5sbE7ndaTPqiYkovl02nsqGdiXSyrlC1Q7O ++XjfOO0gig6LFsWiKCWxQzJUOTD7RsqRaZTHnEHEf9iIcFmHh52i9Bt5r/lqNA/Q +D7V7vsECgYEA9pYzzZR4+xSYKfQHEpIdUGQTh1kFIdxHp/W4HD+3NJc+DIeUWGVl +7oUGcZvcEDfQojM3LUNAof/NuPuLinjKKzycRBU05ApuLIP9R1mq8ndxe98jxOIM +sCd/UfKHKAtOlyWnLvLuJZLozVYack+9/ZnEDUuz9u1S3l+IPUNiSmMCgYEA1Ziy +jFvchUecrXz1PFpW95psYMkCcgDYg26UwF8jrEcf26DblXs3O8mPGUqCwHOJHk+6 +fEXMGbF7ocr+b2HMKuq5EaCFUJqu5orZLDqFuxCDee2OS/pxfTXaXwrnCKVrYzJU +9HNmuac4pryWnarnGbA56BlXp9mJsAbRcxiZOWECgYEAm1CKOn+9H/Cd0zb4SXMs +8ZjHUCX6/JPhsmIr3+cl/wMQOxYekvrzFCRHpcFVAAYX7EI0C9djW2Zi7pPKFaL1 +O/yGNL/iu4vyTymnm4xYBzbCjRJEVltHQKDwKe6HwOo2Sy+VORYceCArcEI+kCe3 +9IconHNFXE+pNZWYm3XY8B8CgYBmEHUhBLQ3K6T+cXttv21XG382MFbuyuCqzShf +VBbjt4jNlevXXe1isEmkuCoKdCrNRSPDRkbk8B43jZxO9NhumYKdnaqWfZOdrjNg +IwbMAHQSyyT3wVCBmD4ktDz5sLHD0MUvmgU4KWO0qOD/ri6H4+GHurRcDGLyrg9f +hB2TgQKBgH1WLbZEHvY07coCUdAywMCjcR2zmKrxo2rsmVrfjNNF0X0mh6Tsw8Af +BpL/j2bb7bHIIVKEystD2lx+zmOyLZOmT7nvZ7nFKiRKe4HwHiZW5N3JJjpGfBWU +vzPAbJHWnyctRihxgXbq6eGJEv5Dwgf6ERP83Cnn3JiDUNPuEIa4 +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/agent7.cnf b/test/fixtures/keys/agent7.cnf new file mode 100644 index 0000000..ed3bb91 --- /dev/null +++ b/test/fixtures/keys/agent7.cnf @@ -0,0 +1,17 @@ +[ req ] +default_bits = 2048 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = IOJS +OU = iojs.org +CN = localhost + +[ req_attributes ] +challengePassword = A challenge password diff --git a/test/fixtures/keys/agent8-cert.pem b/test/fixtures/keys/agent8-cert.pem new file mode 100644 index 0000000..7b9304d --- /dev/null +++ b/test/fixtures/keys/agent8-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUDCCAjgCAQEwDQYJKoZIhvcNAQELBQAwfTELMAkGA1UEBhMCSUwxFjAUBgNV +BAoMDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsMIlNlY3VyZSBEaWdpdGFsIENlcnRp +ZmljYXRlIFNpZ25pbmcxKTAnBgNVBAMMIFN0YXJ0Q29tIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5MCAXDTE2MTAyMDIzNTk1OVoYDzIyOTIwODMwMTg0MjIxWjBdMQsw +CQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ8wDQYDVQQKDAZO +T0RFSlMxDzANBgNVBAsMBmFnZW50ODESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvCbXGqz553XQ+W9zsQEaBc1/mhd4 +TFjivwbK1hSdTuB8vWyOw6oZuqAJjctcIPmNXf01zV1+cAurpoU8k9SmtetwqaDV +0K5ooKUuzgAefRoLJqU0XonW4VaK0ICQATkxSWdJzYET68NTukv5f9Fh0Jfi2Q6Y +PKlgUIuoTPQJSErAMsdph4KWMP7zsaEZNJhmZ1Lprfm4DdVnwUfYvDhq5VmAHFLj +Vor/z3DJS+pW9oORDta3CMvAY5oGcIYWWMxsoG9B9NtTTs58jjeFpJrw/RYJA/CM +uRawLWKt/z1zPhzmvknTKfAIc6SjbBqu8Nx/Xvcd61c2V39U/nZDTs+H9QIDAQAB +MA0GCSqGSIb3DQEBCwUAA4IBAQBfy91+ceZDfZ0DnHHAlm8e+26V5sdrdOXZJtkc +AacDcCX6AD1iMK+0axBgG6ZJs6m87cmFdaq23pLpBLQ+KHSdG5YgRCEuWW+RaJGj +/vVn9AS4eB3EmX0RhhJgYyVbN7ye8qjfAv0NtHzUsdMS8ay3HbdUCtrcsHonGDR3 +t/0BGsYny9Kt2f2PNN32UEkx/jhcssXwnNGxyxR/04heJUe6LI5ErdQoxxvaZtrd +u9ZgjSxix4dFH4nTYEYe3oXM1U7PakbzOzJvRMmDh8vYyK7/ih0w8/DcsK0d1Oej +mgtTF/IyJqy8T9goFf9U2uSshia+sKJBfrrzRaUHZMx+ZobA +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/agent8-csr.pem b/test/fixtures/keys/agent8-csr.pem new file mode 100644 index 0000000..9b76b0f --- /dev/null +++ b/test/fixtures/keys/agent8-csr.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICxzCCAa8CAQAwXTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJTRjEPMA0GA1UECgwGTk9ERUpTMQ8wDQYDVQQLDAZhZ2VudDgxEjAQBgNVBAMM +CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALwm1xqs ++ed10Plvc7EBGgXNf5oXeExY4r8GytYUnU7gfL1sjsOqGbqgCY3LXCD5jV39Nc1d +fnALq6aFPJPUprXrcKmg1dCuaKClLs4AHn0aCyalNF6J1uFWitCAkAE5MUlnSc2B +E+vDU7pL+X/RYdCX4tkOmDypYFCLqEz0CUhKwDLHaYeCljD+87GhGTSYZmdS6a35 +uA3VZ8FH2Lw4auVZgBxS41aK/89wyUvqVvaDkQ7WtwjLwGOaBnCGFljMbKBvQfTb +U07OfI43haSa8P0WCQPwjLkWsC1irf89cz4c5r5J0ynwCHOko2warvDcf173HetX +Nld/VP52Q07Ph/UCAwEAAaAlMCMGCSqGSIb3DQEJBzEWDBRBIGNoYWxsZW5nZSBw +YXNzd29yZDANBgkqhkiG9w0BAQsFAAOCAQEAsLb6+50b2ck7DOop0M+EitLaY3H2 +PWZBS6A86RU+5y30pJsFKCtefN8Hb21DwSwqhkcFukHzZRcKuGA8IsMhlE2u7YAU +Bx8pX5jOBhFWbUG+mAoe563XPCQNZ3GbKg3pGOCJ8b6gflmGvIxXlzQlR8lg1RG2 +dT5q/sWTOXOsDyu49bObDw0jEFM/HgHzpFyHdrnh3P2vEULx7qdRVUXQ9JIsuPjB +bys9FhjDmV9yEabWfHRXqrFY318CPit25Q6Cl9G4EFMCYkUX2nVzjLojExkwJHdf +y4wDaEzxtqJgEEaQwMu+j68v3wgYAGk0yKMFNDQ0gaSZkAQ6u8I5unTGYQ== +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/agent8-key.pem b/test/fixtures/keys/agent8-key.pem new file mode 100644 index 0000000..dac8716 --- /dev/null +++ b/test/fixtures/keys/agent8-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAvCbXGqz553XQ+W9zsQEaBc1/mhd4TFjivwbK1hSdTuB8vWyO +w6oZuqAJjctcIPmNXf01zV1+cAurpoU8k9SmtetwqaDV0K5ooKUuzgAefRoLJqU0 +XonW4VaK0ICQATkxSWdJzYET68NTukv5f9Fh0Jfi2Q6YPKlgUIuoTPQJSErAMsdp +h4KWMP7zsaEZNJhmZ1Lprfm4DdVnwUfYvDhq5VmAHFLjVor/z3DJS+pW9oORDta3 +CMvAY5oGcIYWWMxsoG9B9NtTTs58jjeFpJrw/RYJA/CMuRawLWKt/z1zPhzmvknT +KfAIc6SjbBqu8Nx/Xvcd61c2V39U/nZDTs+H9QIDAQABAoIBAQC0gx8EfMgWBLbF +WORKAaCRyKKPl8zWksCYPVAFLCnwLvf+VFRz7JJatofz/hMZn9K9Rd2EdhqELO42 +CMYhnneDOasRUzlPyMSgu1m4UezuYTopjX485Um/T2RGvdFrGw/qOKpZ+2i9XNzL +c3Cf7KZHljERxirQqD+7hwGlMsxlCYpIRYbe6orpT0aAiGr1iVioohyk8tT+7iXD +mlPeF9qbWAChgfLzTmHcpxGpiFXS0w6KV1XG8sickm1tnoXbCV5ZJw6HscL5VBp9 +SBclRo8AXWBaqhcfj0mvLJWs5E5K3P6dM9X/RcxJwP4Q+kfABYoYjZrZ1/sOJkZr +mHzoYznRAoGBAPeDWWG5RLSlYgs6Llw9ERF3917AiY+eWUCai7faWGP3SEigmPOm +m7rHQcI650DN22aXm8DBSRM1QV0C/UWd1DoDpQAc76GexJhLYRA8/ZGAe24/q1nX +V0aDHzTLC6m8fYGj5ATOotvzYYz6aK9dCuLxPYfWmyZsIXU6K+ypsZOrAoGBAMKa +a+7es33C8aUf6kvBOtPPs9tJW8rhox9gMQxHXuibz+0ZUM7h0DUbsMkDpj4Ydwqr +c7O5sIHUjJLSmw7Oaw6xByK51tZNZeA3bVB2ZUAPILNkF2UqldRYRT7DfLTHEbNV +DMo5P4tR8HxWdrKuhcp+RdPfVmay7iIkFtcKWbLfAoGAR34nKTUMhWln4npRvc7d +yT/vsezHTzab7S82wEpPUcCxnljVFTvAq7i2Y9YDyhIsF3wfPxQVeXjegnFEmwE1 +tfQritbQ2Mw1WRAc30XesFJ+VKALbI3o5bMmJmen3MVXM0UVrdXJ8OJiAQiriEvF +wzuPXFc+xWBiYawF1/xEELUCgYAYBm6K2A262gVxSGZpodp8aekfiof9nSvBZOPJ +S0ppV0stT3HNiM1msRt7RasRgX244H/xUVx8Otx8B+pCwrMu5iYmYGEopfeM3eru +Ax/u768u1o2Y3NAQnjE2VXYg7269ACQLF1REBAK3pwkSeD9mR36hcLI/DZoetuvm +8o0uawKBgQCGwazVcS1cOOODw8mP39KIlWNtbpeRI6T4Qz8Z4FO5B324uccRmxL6 +gCRTdO0990mYQ/BJQ0EQWyrcnpqSfw1YYQOYpYvPexhKpRV+sU5KqJJkqEBe3WLm +oiaFpuz+NtKkQrCc9AlA3SHlJWPo7jTjIPCpGuVM+FipGTbLGHr7HA== +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/agent8.cnf b/test/fixtures/keys/agent8.cnf new file mode 100644 index 0000000..c89841b --- /dev/null +++ b/test/fixtures/keys/agent8.cnf @@ -0,0 +1,17 @@ +[ req ] +default_bits = 2048 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = NODEJS +OU = agent8 +CN = localhost + +[ req_attributes ] +challengePassword = A challenge password diff --git a/test/fixtures/keys/agent9-cert.pem b/test/fixtures/keys/agent9-cert.pem new file mode 100644 index 0000000..7fdada4 --- /dev/null +++ b/test/fixtures/keys/agent9-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUjCCAjoCAQIwDQYJKoZIhvcNAQELBQAwfTELMAkGA1UEBhMCSUwxFjAUBgNV +BAoMDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsMIlNlY3VyZSBEaWdpdGFsIENlcnRp +ZmljYXRlIFNpZ25pbmcxKTAnBgNVBAMMIFN0YXJ0Q29tIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5MCIYDzIwMTYxMDIxMDAwMDAxWhgPMjI5MjA4MzAxODQyMjFaMF0x +CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoM +Bk5PREVKUzEPMA0GA1UECwwGYWdlbnQ5MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2oEMk2EKwIZrx4IPNcGjHw5DO +u8A8yJrcWG4pThUadrvwMI7bQ4QwNgHm4PVpbjAPbSUsRPX98PWL6GcpoH0lmJ9+ +j9CCEIEkW+j5wM7hYBXUSGuAZZfkdrpbZHsvwpYj2U39sfmUyGT1gBbGBmaAzODh +ZaqYSm9VdaKS56SRMey3Pbsx+ikylgiEyPFoRKA141Zuxz1MKiwszLHuyz6pCZKY +K7x1dlEGi3h3dvkRAdMyeSXJkYCZGbS5Fbl2OuW4pSWP4no/M960vBwEYvuJPDtx +qxGezE51oXp4W4l9k+TYPOfGJDVW0PAg+JpfbepLetgFaO9/eNWes34AhF6FAgMB +AAEwDQYJKoZIhvcNAQELBQADggEBAD8ojlI4FdXLCyHVYwwTP6BncF4tfeGP82/i +Zcr8U9k28T2vlsLwdCGu8UVqGWfYrSY5oZqZmHPcDfZmv6Uc39eQ72aweoyLedk3 +UF1Ucwq+MxEM98doLlqL4lnPO1+TcpdhtoHAgT28WkddbR3alfsu+GRU3br3s4lS +DHcm6UzdA/lkgZtC8wFUSW04WhzSHB78gm8VOl+1JGY0pp/T+ae5swkfj45Q3jOd +H6jdZiUrU+LJQwLlXYniF4qzmH0SN8Gd3djVNzWJtNF+LFKXzCOYSK8AFaQ6Ta+s +Pd6Rqa8Hl6cMmlsDu1NLumstvGna5wsc7ks1VZwtWt6WfIyIN2k= +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/agent9-csr.pem b/test/fixtures/keys/agent9-csr.pem new file mode 100644 index 0000000..b93b34e --- /dev/null +++ b/test/fixtures/keys/agent9-csr.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICxzCCAa8CAQAwXTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJTRjEPMA0GA1UECgwGTk9ERUpTMQ8wDQYDVQQLDAZhZ2VudDkxEjAQBgNVBAMM +CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALagQyTY +QrAhmvHgg81waMfDkM67wDzImtxYbilOFRp2u/AwjttDhDA2Aebg9WluMA9tJSxE +9f3w9YvoZymgfSWYn36P0IIQgSRb6PnAzuFgFdRIa4Bll+R2ultkey/CliPZTf2x ++ZTIZPWAFsYGZoDM4OFlqphKb1V1opLnpJEx7Lc9uzH6KTKWCITI8WhEoDXjVm7H +PUwqLCzMse7LPqkJkpgrvHV2UQaLeHd2+REB0zJ5JcmRgJkZtLkVuXY65bilJY/i +ej8z3rS8HARi+4k8O3GrEZ7MTnWhenhbiX2T5Ng858YkNVbQ8CD4ml9t6kt62AVo +73941Z6zfgCEXoUCAwEAAaAlMCMGCSqGSIb3DQEJBzEWDBRBIGNoYWxsZW5nZSBw +YXNzd29yZDANBgkqhkiG9w0BAQsFAAOCAQEAms/rMyW2wVfNtBHZ7HAUxUlWl8ow +0dlZgVmAXF0WBnfOGn31CQcGPyj9btJ48tmaTXmhIw96KqQDIi8KmYXDrDm0JmEp +d+6Q704A0Qjwq4OmMqSobNHRVZUM24niF+U/oGuI8J5nSbCp/6m6chwM+R015cfl +1yNqqQXYYIogcHQZVdofeKvGmrQhBfsEt5cdk2riGqfWVBwY6rfXW+MSHIw6cHIn +vVFYG32Gk8ZU+MoWPQ/DLAy8B7Azo7ePMnidfaOxPAox6IGzCcZOfnCu2tZ09S5t +gqcpdnecBLuQdIybhKhCbM7GOmIricDeIJXkVhmwmjpcu1WdQWUIUsD18A== +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/agent9-key.pem b/test/fixtures/keys/agent9-key.pem new file mode 100644 index 0000000..1dc2df9 --- /dev/null +++ b/test/fixtures/keys/agent9-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAtqBDJNhCsCGa8eCDzXBox8OQzrvAPMia3FhuKU4VGna78DCO +20OEMDYB5uD1aW4wD20lLET1/fD1i+hnKaB9JZiffo/QghCBJFvo+cDO4WAV1Ehr +gGWX5Ha6W2R7L8KWI9lN/bH5lMhk9YAWxgZmgMzg4WWqmEpvVXWikuekkTHstz27 +MfopMpYIhMjxaESgNeNWbsc9TCosLMyx7ss+qQmSmCu8dXZRBot4d3b5EQHTMnkl +yZGAmRm0uRW5djrluKUlj+J6PzPetLwcBGL7iTw7casRnsxOdaF6eFuJfZPk2Dzn +xiQ1VtDwIPiaX23qS3rYBWjvf3jVnrN+AIRehQIDAQABAoIBAQCxMp0TkfZa+bBi +woqAenJgaeQGg2vKTobcB72TvFyDmfNO4X6rRz5qnOyJfXsBelWNkkSASMU6SWOn +Ba+bV0o2gXk4DwisOqFjiv5p3uedDGMB3+bW5TxVA9JcPQm91JtjW0TuRJK7BxnW +jxsJt0ob7S7B5Kh7LbYLAKHm0nX+HgFcfiaRsVCiCtuYkTOzpkUQsl7psX4VJJQ+ +SYr4URsPZI7SGdIeq6ofKH/yGJeoYyhxfNDzNZXqdIC1LEU4BrsrBR6mVRTj35+K +0pHiOQ5Eezi5BnEFUgRR6ompTXQGfnDgEmNyuhzVhuP6nFJ/QPO2KUcIkU4cqViC +7ZTeaLjdAoGBAPNfZbi515DhtwdwDx/Ol0Y6kzyK2NP2FpMlGXwUKXSYbmwbSxSo +zXGyCjxmKtQfbwzVSd/aNdH1lPCHerNFmumhL52wsWi/9EIT+liaxN8Cw/IbUuOy +QlA8BT3Pf1H4Fv/ePIx1DuiPk5jp/eyAK/iPVCyXu7d85ULdfhvwVS7XAoGBAMAZ +/x7dP7qRAUgjvCw8JQVpJPxeVvraKHpzQnceIhd88oTAIYHONNomSBAlqoT00JzW +uFkDbtMZydhorOCUR/d6oHWyg4qEt7F89mssvCFXkROs+ePCuvspYO4F9B7/A7HT +dwErWsvEaRmEO/AWRvHMhjTV4F5ZMNvPj3E1YX4DAoGBAMM/y7oRzrG7hD2BV4Dr +G04KfElcE2ypx56xauqyujeCe0Rb+TZP3tLSRYgDZ2Ta+xrOmv/ubrNNVPpLltLw +isHYwPy/3vTs2yeQI46mTD+mVlGMPknSn4UDQik+qSS35qvMPcNpvlYxqfZJ85+j +jKNTSfKkoMMqfjvQuvXrMEvtAoGAIjZ/EWgmKXwZ1ldG9Dnh/gyz4Z6LrzGbc/OD +KuPa/oPqTWpKjWvETfXzb6zFqdhQLx6uxmuuGTrGkBxUbcr65kCYw11/v/PTI3E2 +EfBtsSJ/XBm6h63uzzyXXs0ApWSVq94Vm8e07AWXEkxSwHe3OulKHa7ZvvPzl7Jn +wanYKzECgYEAun2EZ1ETQ10fkQ9RYxeJYd3HjazTfWXUJJeVhPlsLQQyVfZoRwy0 +9nzdOclgSvE2/+m3SQFh4UHTddlrU1C/V8pkgImnOyCmBFEv8SAfuvEnhQWA13kA +bfreLGMiDnbK1+7MEq0CJeIUZ1LWeuHlFNJT8bYvyjLiZcJyipgQ3NA= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/agent9.cnf b/test/fixtures/keys/agent9.cnf new file mode 100644 index 0000000..bf5da95 --- /dev/null +++ b/test/fixtures/keys/agent9.cnf @@ -0,0 +1,17 @@ +[ req ] +default_bits = 2048 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = NODEJS +OU = agent9 +CN = localhost + +[ req_attributes ] +challengePassword = A challenge password diff --git a/test/fixtures/keys/ca1-cert.pem b/test/fixtures/keys/ca1-cert.pem new file mode 100644 index 0000000..c126543 --- /dev/null +++ b/test/fixtures/keys/ca1-cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIChDCCAe2gAwIBAgIJAMsVOuISYJ/GMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu +dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2ExMSAwHgYJKoZIhvcNAQkB +FhFyeUB0aW55Y2xvdWRzLm9yZzAgFw0xODExMTYxODQyMjBaGA8yMjkyMDgzMDE4 +NDIyMFowejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEP +MA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTEx +IDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDrNdKjVKhbxKbrDRLdy45u9vsU3IH8C3qFcLF5wqf+g7OC +vMOOrFDM6mL5iYwuYaLRvAtsC0mtGPzBGyFflxGhiBYaOhi7nCKEsUkFuNYlCzX+ +FflT04JYT3qWPLL7rT32GXpABND/8DEnj5D5liYYNR05PjV1fUnGg1gPqXVxbwID +AQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBAHhsWFy6m6VO +AjK14n0XCSM66ltk9qMKpOryXneLhmmkOQbJd7oavueUWzMdszWLMKhrBoXjmvuW +QceutP9IUq1Kzw7a/B+lLPD90xfLMr7tNLAxZoJmq/NAUI63M3nJGpX0HkjnYwoU +ekzNkKt5TggwcqqzK+cCSG1wDvJ+wjiD +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/ca1-cert.srl b/test/fixtures/keys/ca1-cert.srl new file mode 100644 index 0000000..d0ad2b1 --- /dev/null +++ b/test/fixtures/keys/ca1-cert.srl @@ -0,0 +1 @@ +ECC9B856270DA9A8 diff --git a/test/fixtures/keys/ca1-key.pem b/test/fixtures/keys/ca1-key.pem new file mode 100644 index 0000000..be4ad72 --- /dev/null +++ b/test/fixtures/keys/ca1-key.pem @@ -0,0 +1,18 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIC1DBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIO9cMY0yHJGgCAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECAKYHSZXK7beBIICgHkIYlgRIfZt +QZxMoahaz4Bu6t5F24ud9rdrpT8qKCnffj71z5mtwucE7sXMslcejgo4QEP5V3lS +zFWQDY5B0p9al+M9DIsseSz8L9StqgQo3FfYLGVs8njO4PFyaWSdbPWWpaXRUmdm +32CbOKwdvOMFhcR8G+jlezkm4uyQ9yCC5bERdrpsaaPJTlt8aMdrvxMytHw60OZo +VVF88fRm9x4kUT+sKyiwG0JWTRDueeFoJR4oa5xlw1AVmEYBBQee9Tc73wqtOr2r +4dnL/zwc6c9ZoTTDOGZNgBvxswVzg04K/qp9LIt3uOeHXqfnmzfmJ4eT5fG71pir +pSfZPIYXJwgC1NjWvsWudI/kE1Wj0JnGYuPY/Vea9RSSrRqq9nL9kqTSa1jgo8l1 +zTR/uZHZH2wCMlH09tiz7uOiPihsO8qeYWIe+vkgC2xCm6O/B1vMuqQY36LOxeVt +igi9hDLjWxNth9lEcC/0wsrft8Y1/CF5h3/j86h1IcgQKFrePu40kols/yBxp7/n +MIzm4oUoClGe3q+a3aajEYL5Cos+7tu2jSM3MKVMiNZ5aTICQ9Sr/OOz+tYZOdJd +hCDF/oncBALJ7nhe77mf1j+Qy/vZzCvDIl/ki3zK/S83U9InZW3oKFv4u3brh+77 +2zruIZ6l/zId8fFARdh6PvXz5avz2eC//EM1zNJiV6IC9DvB3xibvA05rmoK4g9V +DIIIWJsTJUPPvEXszMAtb1zR+MhDs71RF6KvM67KCasI0k07hGY0rgDjlzeoWe/3 +SLgXPda5/WCkRcpgznzRu53HJwq27tUsh74hXPbqaQREDrX0bBBokAMCGBxCFrJi +Btmo/ouOp4w= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test/fixtures/keys/ca1.cnf b/test/fixtures/keys/ca1.cnf new file mode 100644 index 0000000..0b6426d --- /dev/null +++ b/test/fixtures/keys/ca1.cnf @@ -0,0 +1,23 @@ +[ req ] +default_bits = 1024 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +output_password = password +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = Joyent +OU = Node.js +CN = ca1 +emailAddress = ry@tinyclouds.org + +[ req_attributes ] +challengePassword = A challenge password + +[ v3_ca ] +basicConstraints = CA:TRUE diff --git a/test/fixtures/keys/ca2-cert.pem b/test/fixtures/keys/ca2-cert.pem new file mode 100644 index 0000000..0c72d6c --- /dev/null +++ b/test/fixtures/keys/ca2-cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICbTCCAdYCCQDRrfgRk8tC1zANBgkqhkiG9w0BAQsFADB6MQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAO +BgNVBAsMB05vZGUuanMxDDAKBgNVBAMMA2NhMjEgMB4GCSqGSIb3DQEJARYRcnlA +dGlueWNsb3Vkcy5vcmcwIBcNMTgxMTE2MTg0MjIwWhgPMjI5MjA4MzAxODQyMjBa +MHoxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNV +BAoMBkpveWVudDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2EyMSAwHgYJ +KoZIhvcNAQkBFhFyeUB0aW55Y2xvdWRzLm9yZzCBnzANBgkqhkiG9w0BAQEFAAOB +jQAwgYkCgYEAv61gNiLff+zxCdAwdzlVoucGu5L+LFFN9TXzcT3ZD8U1H6CiLp3Q +02IlbK1JRHwpJBXgYOFvMWd9LD6JiJgJsp61kpZShl2qZSUIfhzeExWH7kkuPHWC +IEkiP/aDp5wuqbFBkNUJu8opYr0E6/t9sIzl4IK7WNDXWgQvv8cqin8CAwEAATAN +BgkqhkiG9w0BAQsFAAOBgQB80WTJ9neA5yVaDVV+hZtOasLiZlUT8m49ImQMnInA +jdoAkxgySNOJP8IrsilleAGeHF+JPy042z8NZ5C+xL9REaB1/OaQ7+nwHP0O0f+l +kXHgZATQ3YVf6db5euK3R1mdO1Vv++R4Nu4NYBu0cmfMpdl/uKdYpXMjPVn21iB7 +5w== +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/ca2-cert.srl b/test/fixtures/keys/ca2-cert.srl new file mode 100644 index 0000000..70dfed5 --- /dev/null +++ b/test/fixtures/keys/ca2-cert.srl @@ -0,0 +1 @@ +91F006636069F29D diff --git a/test/fixtures/keys/ca2-crl.pem b/test/fixtures/keys/ca2-crl.pem new file mode 100644 index 0000000..de41629 --- /dev/null +++ b/test/fixtures/keys/ca2-crl.pem @@ -0,0 +1,10 @@ +-----BEGIN X509 CRL----- +MIIBXTCBxzANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQGEwJVUzELMAkGA1UECAwC +Q0ExCzAJBgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAOBgNVBAsMB05vZGUu +anMxDDAKBgNVBAMMA2NhMjEgMB4GCSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5v +cmcXDTE4MTExNjE4NDIyMFoXDTQ2MDQwMjE4NDIyMFowHDAaAgkAkfAGY2Bp8poX +DTE4MTExNjE4NDIyMFowDQYJKoZIhvcNAQENBQADgYEAiZKSPllC/hi1S9jedRFt +eah65RINSkGZt40AuxPIFfyu5qSeKU+9p8zdXELtPqywSr2k6JjBP40yRIc5/odZ +CxFM5XR/AIoeNspplxb+Qg1v1KlbGxzfeBHRIn91QpKcvsMCEYF8nu3xKOJmYrTR +3Kj0gniNHbiLe/HTpEY7YOA= +-----END X509 CRL----- diff --git a/test/fixtures/keys/ca2-database.txt b/test/fixtures/keys/ca2-database.txt new file mode 100644 index 0000000..3981127 --- /dev/null +++ b/test/fixtures/keys/ca2-database.txt @@ -0,0 +1 @@ +R 22920830184220Z 181116184220Z 91F006636069F29A unknown /C=US/ST=CA/L=SF/O=Joyent/OU=Node.js/CN=agent4/emailAddress=ry@tinyclouds.org diff --git a/test/fixtures/keys/ca2-database.txt.attr b/test/fixtures/keys/ca2-database.txt.attr new file mode 100644 index 0000000..3a7e39e --- /dev/null +++ b/test/fixtures/keys/ca2-database.txt.attr @@ -0,0 +1 @@ +unique_subject = no diff --git a/test/fixtures/keys/ca2-database.txt.attr.old b/test/fixtures/keys/ca2-database.txt.attr.old new file mode 100644 index 0000000..3a7e39e --- /dev/null +++ b/test/fixtures/keys/ca2-database.txt.attr.old @@ -0,0 +1 @@ +unique_subject = no diff --git a/test/fixtures/keys/ca2-database.txt.old b/test/fixtures/keys/ca2-database.txt.old new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/keys/ca2-key.pem b/test/fixtures/keys/ca2-key.pem new file mode 100644 index 0000000..2efd44d --- /dev/null +++ b/test/fixtures/keys/ca2-key.pem @@ -0,0 +1,18 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIC1DBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQI0/0Q5tLDQW0CAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECHCRfosVTaMUBIICgHq2VKpOxq8W ++KZ7bVFuWx/gJvE1FAJokIZogK52LH3oEk9kMlEujwYAMovEew4lPMgAnyQh5Mbx +BXIm8Arww9hUZlRPuHmeQQuikbz/Sy5LVFbrzRsM0xGZxeWkpq3iKj3Z4W1OneRd +HAtBADAlID1a4r5f/BxiuNBGn5X54x66qbC94mJ2b02zHJJaRVd6OQM5iZURlcbi +N1E/LtQ3/I9qWqGYfiVCZf39ItxbrBkIEk65BbCackpDpVxzOfEbvC8RdBZcHxZm +8g4XZ6p1rCmzLi22l7usgEhd4QSMQyT9JTnMfM1QFzaqAVTqWr4ZFP108a2vH574 +T/HFKBkI+DEUsKQTLmYqZ05mg0wx80KGP/+1jOB1yx0tGnxCihGJVhqqGoFqgBSm +aqC5arQIZSUt2eN4OamakgU4iLzrKFb6bWGwTNUoHZNh4TsYz4CvFkPcM5tOyX+l +RoUyPAyfu348Z2IKBzUwYUfXJ5WFW2xq+RiOmlt4zF1Lym+aktEP6REQZYTGZZZx +l1YsvIUDd0pj5AJ3/PSTZN+VzkKz5lJdKEDEqpoOkEZnE/FL5VJHnRLOANyNf1zl +qZFgLGRZgZQwGkwj2hAF3auRJWJyvjuQW57v86F3U6XKKKejgBVb2ohMk7U6WW8B +wPtYyEa2zW1hSCLWhMEaek5Y2/2NX/dPryHNZ5XJ1UD0SGrPumN4lbErKDWGmAoK +jH6bpX/xVdmur2BwgGdqt6S1BW9B2F+cXz46UNiFKPYL49iBe13xM5EFKk9N9DL3 +HWPWrExlmi+p4PASL6cR5t9sDw8wUYp5cyC/M1RHDJPvjgBX987F17fI6GkNNToE +ZIbM6M/EuKg= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test/fixtures/keys/ca2-serial b/test/fixtures/keys/ca2-serial new file mode 100644 index 0000000..8a0f05e --- /dev/null +++ b/test/fixtures/keys/ca2-serial @@ -0,0 +1 @@ +01 diff --git a/test/fixtures/keys/ca2.cnf b/test/fixtures/keys/ca2.cnf new file mode 100644 index 0000000..51a72fa --- /dev/null +++ b/test/fixtures/keys/ca2.cnf @@ -0,0 +1,33 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +serial = ca2-serial +crl = ca2-crl.pem +database = ca2-database.txt +name_opt = CA_default +cert_opt = CA_default +default_crl_days = 9999 +default_md = sha512 + + +[ req ] +default_bits = 1024 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +output_password = password + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = Joyent +OU = Node.js +CN = ca2 +emailAddress = ry@tinyclouds.org + +[ req_attributes ] +challengePassword = A challenge password + diff --git a/test/fixtures/keys/ca3-cert.pem b/test/fixtures/keys/ca3-cert.pem new file mode 100644 index 0000000..7cc2830 --- /dev/null +++ b/test/fixtures/keys/ca3-cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIChDCCAe2gAwIBAgIJAOzJuFYnDamnMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu +dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2ExMSAwHgYJKoZIhvcNAQkB +FhFyeUB0aW55Y2xvdWRzLm9yZzAgFw0xODExMTYxODQyMjFaGA8yMjkyMDgzMDE4 +NDIyMVowejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEP +MA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTMx +IDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQCZ9fF/1UcYaurFIX0QIyAJdojn9+bfsTcjEIoGbsAnQLz2 +bsZ4pqRNhZbYJApxqc+oDzZOqJOaxe8mlB5jUZ/sUA9Sp+wfWly95tkEHBMSse4x +UNJVM4vFPfOG4fv9fYGH3pcmAU1QnST4Fh+qZRzrh9wa99ltmB/U2mJEF6NriwID +AQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBAM3CFiDdGEcx +07J6pm4zGX399VxPr50PID110jmX7BRAfqva+wBRhwweSxZ/QRcKe1v/FK3GE87y +RbaXhFfnPRUHoUHQMtGwmZuZcdK65Pim9RPGb7qrEJ2wlPt/C1Q6VjL/fBGqjtJM +Bq/2GR2GoBsE85jGM287hcvXV0eG5OwM +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/ca3-cert.srl b/test/fixtures/keys/ca3-cert.srl new file mode 100644 index 0000000..c7fc41d --- /dev/null +++ b/test/fixtures/keys/ca3-cert.srl @@ -0,0 +1 @@ +D0082F458B6EFBE8 diff --git a/test/fixtures/keys/ca3-csr.pem b/test/fixtures/keys/ca3-csr.pem new file mode 100644 index 0000000..2c03587 --- /dev/null +++ b/test/fixtures/keys/ca3-csr.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIB3zCCAUgCAQAwejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJTRjEPMA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQD +DANjYTMxIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0GCSqG +SIb3DQEBAQUAA4GNADCBiQKBgQCZ9fF/1UcYaurFIX0QIyAJdojn9+bfsTcjEIoG +bsAnQLz2bsZ4pqRNhZbYJApxqc+oDzZOqJOaxe8mlB5jUZ/sUA9Sp+wfWly95tkE +HBMSse4xUNJVM4vFPfOG4fv9fYGH3pcmAU1QnST4Fh+qZRzrh9wa99ltmB/U2mJE +F6NriwIDAQABoCUwIwYJKoZIhvcNAQkHMRYMFEEgY2hhbGxlbmdlIHBhc3N3b3Jk +MA0GCSqGSIb3DQEBCwUAA4GBACeDmANyVHX/zFlz0OhqXzFw2C76/AjoNsR7cY6b +Mdl8R27MexPxkhD2IOzESxDkxFTzv+aVAz4gQIxDmdea307/P5LvRQXucAtNkAWi +2j6hB0Oq1BNKyBnevRTv28X7rhUp5OGDhRPP5lt1+PPA0zTurw+zJIaInePM//sT +7Ckh +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/ca3-key.pem b/test/fixtures/keys/ca3-key.pem new file mode 100644 index 0000000..b1fe69b --- /dev/null +++ b/test/fixtures/keys/ca3-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCZ9fF/1UcYaurFIX0QIyAJdojn9+bfsTcjEIoGbsAnQLz2bsZ4 +pqRNhZbYJApxqc+oDzZOqJOaxe8mlB5jUZ/sUA9Sp+wfWly95tkEHBMSse4xUNJV +M4vFPfOG4fv9fYGH3pcmAU1QnST4Fh+qZRzrh9wa99ltmB/U2mJEF6NriwIDAQAB +AoGAOfsmdNb0TFzPh2fiOnaP9SBf1MRGfU23DwyGfn+s+9tkjoYPVpajX9KEiWeh +S0cBPjBkamEQHYSXWPcFLrApwnaS8A3Tkp1Voas1dg9Bu3WmDzIIsmBseMgMW00C +6yETeYFtZ8/2nnpK/G7+N8eeseA63LUqg6ANw+BMH3o3dcECQQDNKW6ZfhDtn94+ +PWeXmxJWU6bm30U4othIo3iZKIswDhxVnlZhZ0mrgs0mc9c4CzTmyogvLKN0/nJF +gknvEcdrAkEAwByJR63E5Wg8OR/d0HgZAkrXVZGkGz33ZddGygrdUGvk4dfYYALB +A6/aCDc99gn4cUkzcOGOUGGEn3m38BeUYQJAUfyytChK/4sZt2m2kkFoTJNVaYHk +GcQKBs09DofDR8r7y8Ng5b/vEtlMvocghMcFtw1M6v09vS1J4Tk17pH+TQJBAJYZ +dbU4cv+e6nbjjAam3ztoSEjGK0dRqiu7AMc5p+N++WzvnVKetDnyOtNyfgnvjlrN +C9ElmnD5UIrdqjZ/5eECQHhcQsKgsWGRKPKxyf7f/sFHpkSeAEZSAPdh8ouzIdUS +xSRbK9UF+ckIop5cYjnBbmFa/BjWr4m9NcEKE8rXYxk= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/ca3.cnf b/test/fixtures/keys/ca3.cnf new file mode 100644 index 0000000..83a6e7f --- /dev/null +++ b/test/fixtures/keys/ca3.cnf @@ -0,0 +1,23 @@ +[ req ] +default_bits = 1024 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +output_password = password +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = Joyent +OU = Node.js +CN = ca3 +emailAddress = ry@tinyclouds.org + +[ req_attributes ] +challengePassword = A challenge password + +[ v3_ca ] +basicConstraints = CA:TRUE diff --git a/test/fixtures/keys/ca4-cert.pem b/test/fixtures/keys/ca4-cert.pem new file mode 100644 index 0000000..4a94c99 --- /dev/null +++ b/test/fixtures/keys/ca4-cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICkzCCAfygAwIBAgIJAJHwBmNgafKbMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu +dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2EyMSAwHgYJKoZIhvcNAQkB +FhFyeUB0aW55Y2xvdWRzLm9yZzAgFw0xODExMTYxODQyMjFaGA8yMjkyMDgzMDE4 +NDIyMVowgYgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0Yx +HzAdBgNVBAoMFlRoZSBOb2RlLmpzIEZvdW5kYXRpb24xEDAOBgNVBAsMB05vZGUu +anMxDDAKBgNVBAMMA2NhNDEeMBwGCSqGSIb3DQEJARYPY2E0QGV4YW1wbGUub3Jn +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDC1M2aGVYsmrBiut1n0nfTU+9v +TNVdAmKQBjnNsv3IIch/PPaEOIEm7dFhgdk86Z+wVCN3sAKu54Bz4JDKdPsFGvDy +18JGuGH1vIVW5285IW7fMrzvAdZtETeBAiPM10Q69ddB4M6FbLiF273ZqCJ+vSsw +kl5Dkas8YTZ0uwqKjQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4GBAGDMGSbPg/B4OripSxT2scXFIwoej47PW1byJgWaGoMJ8zgKUoKE7Z7A +aWQbD22In05F0kBllqpSJWEZpTuVFsyyLeb3R7cuGQWs/puaaPul7sx+PRGhwxYe +nrNIGtsaBf8TO/kb5lMiXWbhM5gZbBtbMMv3xWA4FxqU0AgfO3jM +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/ca4-cert.srl b/test/fixtures/keys/ca4-cert.srl new file mode 100644 index 0000000..328d904 --- /dev/null +++ b/test/fixtures/keys/ca4-cert.srl @@ -0,0 +1 @@ +ECAF33A18C6435BA diff --git a/test/fixtures/keys/ca4-csr.pem b/test/fixtures/keys/ca4-csr.pem new file mode 100644 index 0000000..0e6a039 --- /dev/null +++ b/test/fixtures/keys/ca4-csr.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIB7jCCAVcCAQAwgYgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UE +BwwCU0YxHzAdBgNVBAoMFlRoZSBOb2RlLmpzIEZvdW5kYXRpb24xEDAOBgNVBAsM +B05vZGUuanMxDDAKBgNVBAMMA2NhNDEeMBwGCSqGSIb3DQEJARYPY2E0QGV4YW1w +bGUub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDC1M2aGVYsmrBiut1n +0nfTU+9vTNVdAmKQBjnNsv3IIch/PPaEOIEm7dFhgdk86Z+wVCN3sAKu54Bz4JDK +dPsFGvDy18JGuGH1vIVW5285IW7fMrzvAdZtETeBAiPM10Q69ddB4M6FbLiF273Z +qCJ+vSswkl5Dkas8YTZ0uwqKjQIDAQABoCUwIwYJKoZIhvcNAQkHMRYMFEEgY2hh +bGxlbmdlIHBhc3N3b3JkMA0GCSqGSIb3DQEBCwUAA4GBAJoKIMOK0sCoXAa/cCaJ +oTYLHee1aJBWmt8XTUqREdFIIAjjrgY0/ZGEeA9OEczbFgSTMPXemir4Ks3ib3kr +MeJkOWSUgKL2gdV4jPZIUEdeTYaMQ5utiTvL2oKN4R51mSNg5ZEFIf+vZpK6UTpR +LCERUC79Hsj13NrHK2Lf8jhy +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/ca4-key.pem b/test/fixtures/keys/ca4-key.pem new file mode 100644 index 0000000..90d595b --- /dev/null +++ b/test/fixtures/keys/ca4-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDC1M2aGVYsmrBiut1n0nfTU+9vTNVdAmKQBjnNsv3IIch/PPaE +OIEm7dFhgdk86Z+wVCN3sAKu54Bz4JDKdPsFGvDy18JGuGH1vIVW5285IW7fMrzv +AdZtETeBAiPM10Q69ddB4M6FbLiF273ZqCJ+vSswkl5Dkas8YTZ0uwqKjQIDAQAB +AoGALnr8Tf4ra9q/p94ywfkZMyZ8Ic5vvI+8GeYSVjuUhfFhVtGhcivUzAfCgwOq +YvjNaxC3oW8xRK7gG0UA5fwAgm24RkwazqYgcT2OTqoffRhd3lmyOUcR7fWB6FAN +p7mx9ctW83HBPCwc7SIFaWxMULi3O38A7jXMMJrjIzhEsVUCQQDiKJF8sE6Ep7cr +ARDcWxKP5INa7+UdsPdpR+wKxdrReQuhIF5V0hA6QbyCsNpqhqrO7e4El194qCMk +NfYnz1nPAkEA3IoHlwMOquisd6/KcurFbUHguKH6CWzpxRU6QfLqUkNf+MPPozU1 +qYOm7nukWyJq+dDt5hrmaSuZny6I9zWY4wJBALcq8kJRrRZFm9VppJVD8bG2+ygw +uZkllgyf4q4K9yHG7sNOKvlJDDmSujIDOLMkZLz5+VegngNj8ipGxhoSFwMCQGwv +VdvRhx919izcUk6fNmwLVgachsCa6e5hJGv3ktT58hlhTPk9/+4BBCGXC6AdOScF +Q76OUZsj5T8+H7hNVYsCQCahB/XNbok2MgfB8ABdqVPcAOMLtaGZH4gbE2M3p/Fh +Y6BHQ25FT3LPKEzU89XJuyRoxea3CXqioJngt3JxN1I= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/ca4.cnf b/test/fixtures/keys/ca4.cnf new file mode 100644 index 0000000..9081594 --- /dev/null +++ b/test/fixtures/keys/ca4.cnf @@ -0,0 +1,23 @@ +[ req ] +default_bits = 1024 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +output_password = password +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = The Node.js Foundation +OU = Node.js +CN = ca4 +emailAddress = ca4@example.org + +[ req_attributes ] +challengePassword = A challenge password + +[ v3_ca ] +basicConstraints = CA:TRUE diff --git a/test/fixtures/keys/ca5-cert.pem b/test/fixtures/keys/ca5-cert.pem new file mode 100644 index 0000000..ef42f5b --- /dev/null +++ b/test/fixtures/keys/ca5-cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHDCCAcGgAwIBAgIJANUB/0ZUgBZhMAoGCCqGSM49BAMCMIGIMQswCQYDVQQG +EwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMR8wHQYDVQQKDBZUaGUgTm9k +ZS5qcyBGb3VuZGF0aW9uMRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTUx +HjAcBgkqhkiG9w0BCQEWD2NhNUBleGFtcGxlLm9yZzAgFw0xODExMTYxODQyMjFa +GA8yMjkyMDgzMDE4NDIyMVowgYgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEL +MAkGA1UEBwwCU0YxHzAdBgNVBAoMFlRoZSBOb2RlLmpzIEZvdW5kYXRpb24xEDAO +BgNVBAsMB05vZGUuanMxDDAKBgNVBAMMA2NhNTEeMBwGCSqGSIb3DQEJARYPY2E1 +QGV4YW1wbGUub3JnMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6qDnQ6qm6hN+ +zbym76EK+spOKstEmqj9WzdA/tRBHhzZijXq1l90yQmRfmgclAKZw843qzMfj8Vj +RMRXdZyyYKMQMA4wDAYDVR0TBAUwAwEB/zAKBggqhkjOPQQDAgNJADBGAiEA4nCM +yQUkViSEvBeL3cLzRnak68tXTIkdRMekRFgdsOMCIQDFnkeCyB4S9u2gz1u/syEq +usBaxJpZkN5nyTLapTQGqA== +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/ca5-cert.srl b/test/fixtures/keys/ca5-cert.srl new file mode 100644 index 0000000..345399f --- /dev/null +++ b/test/fixtures/keys/ca5-cert.srl @@ -0,0 +1 @@ +C4C2054438388E3E diff --git a/test/fixtures/keys/ca5-csr.pem b/test/fixtures/keys/ca5-csr.pem new file mode 100644 index 0000000..a1f77c7 --- /dev/null +++ b/test/fixtures/keys/ca5-csr.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBaTCCARACAQAwgYgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UE +BwwCU0YxHzAdBgNVBAoMFlRoZSBOb2RlLmpzIEZvdW5kYXRpb24xEDAOBgNVBAsM +B05vZGUuanMxDDAKBgNVBAMMA2NhNTEeMBwGCSqGSIb3DQEJARYPY2E1QGV4YW1w +bGUub3JnMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6qDnQ6qm6hN+zbym76EK ++spOKstEmqj9WzdA/tRBHhzZijXq1l90yQmRfmgclAKZw843qzMfj8VjRMRXdZyy +YKAlMCMGCSqGSIb3DQEJBzEWDBRBIGNoYWxsZW5nZSBwYXNzd29yZDAKBggqhkjO +PQQDAgNHADBEAiABtQaxoQqAdrK8rjMh4wPB14/+uxMtJ7mY+QwJ411XywIgERcz +HrcyJDqk2CS8B9mHwzD+ERUZ1CgThc15bnBleN0= +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/ca5-key.pem b/test/fixtures/keys/ca5-key.pem new file mode 100644 index 0000000..c213e03 --- /dev/null +++ b/test/fixtures/keys/ca5-key.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINQRYMZO9+WwFZsKDa03DHkQfraKyJ3EcAfYkgtyYtX3oAoGCCqGSM49 +AwEHoUQDQgAE6qDnQ6qm6hN+zbym76EK+spOKstEmqj9WzdA/tRBHhzZijXq1l90 +yQmRfmgclAKZw843qzMfj8VjRMRXdZyyYA== +-----END EC PRIVATE KEY----- diff --git a/test/fixtures/keys/ca5.cnf b/test/fixtures/keys/ca5.cnf new file mode 100644 index 0000000..f1efb33 --- /dev/null +++ b/test/fixtures/keys/ca5.cnf @@ -0,0 +1,35 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +serial = ca5-serial +crl = ca5-crl.pem +database = ca5-database.txt +name_opt = CA_default +cert_opt = CA_default +default_crl_days = 999 +default_md = sha512 +x509_extensions = v3_ca + + +[ req ] +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +output_password = password + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = The Node.js Foundation +OU = Node.js +CN = ca5 +emailAddress = ca5@example.org + +[ req_attributes ] +challengePassword = A challenge password + +[ v3_ca ] +basicConstraints = CA:TRUE diff --git a/test/fixtures/keys/ca6-cert.pem b/test/fixtures/keys/ca6-cert.pem new file mode 100644 index 0000000..a6d2c1f --- /dev/null +++ b/test/fixtures/keys/ca6-cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICGzCCAcGgAwIBAgIJAMTCBUQ4OI4+MAoGCCqGSM49BAMCMIGIMQswCQYDVQQG +EwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMR8wHQYDVQQKDBZUaGUgTm9k +ZS5qcyBGb3VuZGF0aW9uMRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTUx +HjAcBgkqhkiG9w0BCQEWD2NhNUBleGFtcGxlLm9yZzAgFw0xODExMTYxODQyMjFa +GA8yMjkyMDgzMDE4NDIyMVowgYgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEL +MAkGA1UEBwwCU0YxHzAdBgNVBAoMFlRoZSBOb2RlLmpzIEZvdW5kYXRpb24xEDAO +BgNVBAsMB05vZGUuanMxDDAKBgNVBAMMA2NhNjEeMBwGCSqGSIb3DQEJARYPY2E2 +QGV4YW1wbGUub3JnMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEa7HfEgyVTPWY +ku9cWGRSym5OdB7zqFihL8+k93EfWViJph72fJH3sOZypUgDXS/sEyUaLhbxtLYz +sMbECzEDwaMQMA4wDAYDVR0TBAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEA+NIP +zuqh2e3/59QndyPqRH2CZ4V4ipU6rf6ZZmwPApUCIBMABWesJfwdrETIjN6dT8gc +STrYyR4ovD8Aofubqjd0 +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/ca6-cert.srl b/test/fixtures/keys/ca6-cert.srl new file mode 100644 index 0000000..422681a --- /dev/null +++ b/test/fixtures/keys/ca6-cert.srl @@ -0,0 +1 @@ +A97535039C5E962B diff --git a/test/fixtures/keys/ca6-csr.pem b/test/fixtures/keys/ca6-csr.pem new file mode 100644 index 0000000..c12e008 --- /dev/null +++ b/test/fixtures/keys/ca6-csr.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBaTCCARACAQAwgYgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UE +BwwCU0YxHzAdBgNVBAoMFlRoZSBOb2RlLmpzIEZvdW5kYXRpb24xEDAOBgNVBAsM +B05vZGUuanMxDDAKBgNVBAMMA2NhNjEeMBwGCSqGSIb3DQEJARYPY2E2QGV4YW1w +bGUub3JnMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEa7HfEgyVTPWYku9cWGRS +ym5OdB7zqFihL8+k93EfWViJph72fJH3sOZypUgDXS/sEyUaLhbxtLYzsMbECzED +waAlMCMGCSqGSIb3DQEJBzEWDBRBIGNoYWxsZW5nZSBwYXNzd29yZDAKBggqhkjO +PQQDAgNHADBEAiAH69eeaDguTPAqGhWJbhFPEw7zXyZl6TgxoMIeZOouRgIge+Ft +kXO05md30kbq6s559B45rYoH4iHxFOJZqHso7Yc= +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/ca6-key.pem b/test/fixtures/keys/ca6-key.pem new file mode 100644 index 0000000..bb80dd3 --- /dev/null +++ b/test/fixtures/keys/ca6-key.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIFbxtF4Zc09n/2w7z5SMQLFNTdxg4QPSm1WgyffHtJFIoAoGCCqGSM49 +AwEHoUQDQgAEa7HfEgyVTPWYku9cWGRSym5OdB7zqFihL8+k93EfWViJph72fJH3 +sOZypUgDXS/sEyUaLhbxtLYzsMbECzEDwQ== +-----END EC PRIVATE KEY----- diff --git a/test/fixtures/keys/ca6.cnf b/test/fixtures/keys/ca6.cnf new file mode 100644 index 0000000..3147615 --- /dev/null +++ b/test/fixtures/keys/ca6.cnf @@ -0,0 +1,22 @@ +[ req ] +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +output_password = password +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = The Node.js Foundation +OU = Node.js +CN = ca6 +emailAddress = ca6@example.org + +[ req_attributes ] +challengePassword = A challenge password + +[ v3_ca ] +basicConstraints = CA:TRUE diff --git a/test/fixtures/keys/dh1024.pem b/test/fixtures/keys/dh1024.pem new file mode 100644 index 0000000..67c8cbc --- /dev/null +++ b/test/fixtures/keys/dh1024.pem @@ -0,0 +1,5 @@ +-----BEGIN DH PARAMETERS----- +MIGHAoGBAOJ2igPYPdXULPSXaeR40pdGRdQmiWchzxDbzExdaJ4Q/landa7K9jfj +KNV2heMpFoXa6+GkCxXSli+99j0W/ARK1nnZ9YaB42sp3g2oTmMevWyk9hfZTTUC +Pc3AyioJGTQV/5wqYPdSNAu5KdHFZkP26EhbGgnDZ2hg5vFQT6EjAgEC +-----END DH PARAMETERS----- diff --git a/test/fixtures/keys/dh2048.pem b/test/fixtures/keys/dh2048.pem new file mode 100644 index 0000000..cece692 --- /dev/null +++ b/test/fixtures/keys/dh2048.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA+/OlXG2eqOsd9oA6Rg9KDbU22adIXuuwSaVLiUFNIc4WYnzPuyd0 +WcLajZzhXoUpN6GCLqXE+jBphHTh+xx6Y0ztlMldx0mnrLzacd236LCrTX5smojO +CQ3BFmbDn7naG2famOZXF3gLRkcAmUtHrn2hYRbEk+1+GD3yXUm/JgFcnAX2HUQa +b4sDL5NHMJ61+qE65qIIht9jOVovl4b7yOoyDu+JrheV0XBrc8WU03AGKcJnjW6i +YqKXQlpamVvL/QqWfhg3SztBxYADAURePTUmS1H1FTnxSdRoUK9ZwKoTeZFL6krT +3HK76Y0HXhwtjk3aXKTJyEDDLdYaltOCOwIBAg== +-----END DH PARAMETERS----- diff --git a/test/fixtures/keys/dh512.pem b/test/fixtures/keys/dh512.pem new file mode 100644 index 0000000..713076e --- /dev/null +++ b/test/fixtures/keys/dh512.pem @@ -0,0 +1,4 @@ +-----BEGIN DH PARAMETERS----- +MEYCQQDItkdipTrinQp7aKt0GtwY7QE6tMKpg+r1zeucMrq9uNp8uLhHkF1OtqnL +StFTzxQIkjN2s8exIvoWZWiy/9DTAgEC +-----END DH PARAMETERS----- diff --git a/test/fixtures/keys/dherror.pem b/test/fixtures/keys/dherror.pem new file mode 100644 index 0000000..4f56268 --- /dev/null +++ b/test/fixtures/keys/dherror.pem @@ -0,0 +1,4 @@ +-----BEGIN DH PARAMETERS----- +AAAAAAAAAA +AAAAAAAAAA +-----END DH PARAMETERS----- diff --git a/test/fixtures/keys/dns-cert1.cnf b/test/fixtures/keys/dns-cert1.cnf new file mode 100644 index 0000000..44ce399 Binary files /dev/null and b/test/fixtures/keys/dns-cert1.cnf differ diff --git a/test/fixtures/keys/dsa1025.pem b/test/fixtures/keys/dsa1025.pem new file mode 100644 index 0000000..bd83e73 --- /dev/null +++ b/test/fixtures/keys/dsa1025.pem @@ -0,0 +1,9 @@ +-----BEGIN DSA PARAMETERS----- +MIIBLgKBiQDp3xGIsNcKZWSpur2Ab2f5rirhu5AsYGLI/XZvzueoO4kLPp3szKHX +abBObdxU07kRFkfpV9lftdXT3a6E/Wxb+w2d7g0F7pWTdwpmmofcRtwgbynmJKvq +Wt/OwfVFsdUvvpVci9AxY/5rdM/K54Srp6iSpfteBYKGqCpPnswikND0GxKM1Mbz +AhUAqAPKCZ4hxromirUu33tn3Nhsq20CgYgf0tH2nwzWlPLoN4OJvC5dVLPvMRyJ +TQekW8tR0oY6PeshmZOaoI0L3e/M4KeZ/4qiY7IRNksP2YGJxjfS6W+h+MNjPO5r +0aYYFYCPQ8JbhBO7l0hdXmkTY3FcdyRHeh2gwe3bBX6Auev2MGgL616Cgd/KH8PA +X9EHmrNPcQOWHem4KcfJXqou +-----END DSA PARAMETERS----- diff --git a/test/fixtures/keys/dsa_params.pem b/test/fixtures/keys/dsa_params.pem new file mode 100644 index 0000000..9052393 --- /dev/null +++ b/test/fixtures/keys/dsa_params.pem @@ -0,0 +1,14 @@ +-----BEGIN DSA PARAMETERS----- +MIICLQKCAQEAoSw3Ghf02sMSmd5k2rvSqf6eJPFO7fHDRyvDDbifjO6/BKSIkXM4 +3qyCqddC04arKg7wc1QDEQ8gb13pCmnC0RBiljE6ke4yK46Q5JjiEKH9U1eCbtTr +hcGrLDgwbqvRM06EN6IfAL3OBF6YzS9wn3/EfSwW2Z8gAIkjZrTjEUTV+/gEAdfE +gd/WAZxcc9zYKOwPy0/LjjldQw5fsPlIEkS1yJFlWMokSsZVYlJLR06h1S4kQoE3 +BqELirH/FQfJ36RMRFsaKZ6nQYS66Qc8rybQw2VlOJsqiRoTSDwREPz6j9oLYh1E +e86j5Xt9jbiBrK33UbkTr/jBtO0J2PR0+wIhAIXIexS5LQJPSoi96k6OU4yrLLmA +IY8gS9mdYTdbpwcdAoIBAQCCN3gTjFiPgBQ/bj/Edp9w90SA+dQ/VnnYDTMcz+Mi +/8sgtlQ3O9CCFb0327YnOLwvxsmSadT9XrIq1/5jGD2VtjFDVlridjYASrjezR2k +dr781G+bxtVNQuIOKZl9xqruCmHUSSRL/vuCR6pKsA81ZPfpdcLh3RYYxDIoTK6t +VX4GrX5bcxGDIUCQiTaqKv9Nzpm1liBLRm6LHczBsFk2OVrRyMsT3gh0J6DSUw+d +w/Vru1J6glkrr0CxBWoJ65btcqtFyQV/76btor9Qgc/z9suYBoJZ3Ua0yAfv3J2E +rOs1CAbh4LWNULA9eJObY2R4sAV7Q8wOMT5jmjKo4unp +-----END DSA PARAMETERS----- diff --git a/test/fixtures/keys/dsa_private.pem b/test/fixtures/keys/dsa_private.pem new file mode 100644 index 0000000..3f64ae4 --- /dev/null +++ b/test/fixtures/keys/dsa_private.pem @@ -0,0 +1,20 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIDVwIBAAKCAQEAoSw3Ghf02sMSmd5k2rvSqf6eJPFO7fHDRyvDDbifjO6/BKSI +kXM43qyCqddC04arKg7wc1QDEQ8gb13pCmnC0RBiljE6ke4yK46Q5JjiEKH9U1eC +btTrhcGrLDgwbqvRM06EN6IfAL3OBF6YzS9wn3/EfSwW2Z8gAIkjZrTjEUTV+/gE +AdfEgd/WAZxcc9zYKOwPy0/LjjldQw5fsPlIEkS1yJFlWMokSsZVYlJLR06h1S4k +QoE3BqELirH/FQfJ36RMRFsaKZ6nQYS66Qc8rybQw2VlOJsqiRoTSDwREPz6j9oL +Yh1Ee86j5Xt9jbiBrK33UbkTr/jBtO0J2PR0+wIhAIXIexS5LQJPSoi96k6OU4yr +LLmAIY8gS9mdYTdbpwcdAoIBAQCCN3gTjFiPgBQ/bj/Edp9w90SA+dQ/VnnYDTMc +z+Mi/8sgtlQ3O9CCFb0327YnOLwvxsmSadT9XrIq1/5jGD2VtjFDVlridjYASrje +zR2kdr781G+bxtVNQuIOKZl9xqruCmHUSSRL/vuCR6pKsA81ZPfpdcLh3RYYxDIo +TK6tVX4GrX5bcxGDIUCQiTaqKv9Nzpm1liBLRm6LHczBsFk2OVrRyMsT3gh0J6DS +Uw+dw/Vru1J6glkrr0CxBWoJ65btcqtFyQV/76btor9Qgc/z9suYBoJZ3Ua0yAfv +3J2ErOs1CAbh4LWNULA9eJObY2R4sAV7Q8wOMT5jmjKo4unpAoIBAQCE1m+DUb9L +T58u6XV/L1p6K9T2mc6jAmzD51fPiUwsRov9sDGJmSnQjQ5pt3hVp8inVfNkhqOI +1rpdKmx5W00fPu6VCiPuximuHSNHzJpCAVUrIH8YasS+AurCOwGMdvODLF6dx7yR +MdxbiszrBry8J0TdvqElHZ1YmQDwoHH7R4pUd31jsk4gnE6pkqLgWwVAy0LXXGsg +2JfnDvdQY8fIHkuezLdhOyO9pRlXSYv4fLdMaSjHyEcwr2hnm5tm5RsBwM+u0sDc +yBqUjwoN8NTuLLasfJzzmjeHWDcRGFbzKt/xlUkQ7pf+xdelnLOstuPDGglB1U85 +REhx4rQGKg7nAiA0FX4e4Ms3OXUnmtsTALk5YMiMF3jUp4pRDhHFKBgsYQ== +-----END DSA PRIVATE KEY----- diff --git a/test/fixtures/keys/dsa_private_1025.pem b/test/fixtures/keys/dsa_private_1025.pem new file mode 100644 index 0000000..038b0c3 --- /dev/null +++ b/test/fixtures/keys/dsa_private_1025.pem @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIB0gIBAAKBiQDp3xGIsNcKZWSpur2Ab2f5rirhu5AsYGLI/XZvzueoO4kLPp3s +zKHXabBObdxU07kRFkfpV9lftdXT3a6E/Wxb+w2d7g0F7pWTdwpmmofcRtwgbynm +JKvqWt/OwfVFsdUvvpVci9AxY/5rdM/K54Srp6iSpfteBYKGqCpPnswikND0GxKM +1MbzAhUAqAPKCZ4hxromirUu33tn3Nhsq20CgYgf0tH2nwzWlPLoN4OJvC5dVLPv +MRyJTQekW8tR0oY6PeshmZOaoI0L3e/M4KeZ/4qiY7IRNksP2YGJxjfS6W+h+MNj +PO5r0aYYFYCPQ8JbhBO7l0hdXmkTY3FcdyRHeh2gwe3bBX6Auev2MGgL616Cgd/K +H8PAX9EHmrNPcQOWHem4KcfJXqouAoGIcZ/4bJurOAw5OL4+5BsE4jfSVFq4nmNl ++m6Iy6ls3hOHOZ9sJlw0Fi+ZtdeddOTYnIngW/kH09XGoF5JJOaLQv2O4GmZfDfn +la+vfHZPsOxmYqeEbSwA2pqlhOn/dCshBOymhwNLXp1FJ3JUEyh8i605X06XmWoj +4ZIu/tr1xbnZDLHuFjBmXAIURbB5/IDf2h0sW+qL5gHFO8bgr+E= +-----END DSA PRIVATE KEY----- diff --git a/test/fixtures/keys/dsa_private_encrypted.pem b/test/fixtures/keys/dsa_private_encrypted.pem new file mode 100644 index 0000000..49b3375 --- /dev/null +++ b/test/fixtures/keys/dsa_private_encrypted.pem @@ -0,0 +1,23 @@ +-----BEGIN DSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,FABA263DD471F214EF3E02699B837C20 + +Tj2+4x9MEIaQGFQ4o7hk12MriVYyvLO5aCbqq7LG5uhVk546/+bJc6hewdSwb6oT +MYPbuV+QTdtqshqFESA0McyGlj4w1tOg5TomP84NTKvwTO1EirVLMukfF3dqaguw +C117AZJkGbqgbi6lZ2bG0Hta6HRbhI5+ODFtOp3rKQ2KwVmtL7zw6vt3PCISeMHN +fLqikDc2+YoI9V1FJis9/FATyqV8yrJYYQQpP1RQN+gDY4SSs/eUr+Me7RNy6Lz7 +oH0tDaPGbiafwrZe1okksjxT2JQz1Q3hciBPikgdQIoE2NWTUlOeRYX0T0N2n37S +6Odbcr522e+2XjcLj34Ozthp+Q5mIDcLuakazxkXhq0RyhJ7vo+xA2YiP7Q3vH7g +oAnsJPFNVY6wJhprZi2VofKIUJUiajAXGDVX2yEIG/DOA9rnx0ZP+zopXMi4ptu0 +RzWyAL+P4jn0b8vgPf9CYJmn4VNfOcVmomZ1Bw6hzqTE2FnThJCXU3l2eaC/wcSR +uMRp8c6IM8AR5DUzUBKIckkvXj1m5iSZoKuR8dB7s9BhrRtBAI7K3G254G06sByv +0pnft8r+BkMqgdfG4rJQoQJw7tVYln+pL/gYPDuYsqyJ9kFuHDqtBvlozqXY5AL1 +XQaXoD6xMACEoJSIv5y+TzFzXwFQrDW+G1724YOSbiioUfGD0tRfjj2ei63PThQr +Z50SryfKQQf4UgcJeokMhmRWT2vPXEFWEP1b2FMEQxBy6fyKcqwZBAbhqF6usGEB +nwr/S1HXQAGEsWoc/Z4yynB7uhOwWu/Vpj+V6B98NmC7EUX15Why8zCsT9gzaIgC +M6sZafHhcmjfwc+lL9xFlU/wnAOz0LeKZWry3D0sXZn1r2FRlOJdtLLx01Sve/MU +ZRsgEDTkzv8E9dDltMeq8HQDCgLT1USTMWcY1kMELBj7y7ZdCWjH1QhTq2KlId+o +1X28zJOsOL/XRseUSlpjmSSLRw1QQEypNCY2+tcvViAvn3AifipBbdzUNhvygLhc +a2+5rYsd8BBEFnMJx7lDiyqXGnZkBbhbCSIudppNcjC+akFlFp6fBzkp4mKBuKpc +hwBBdfqdEyzqu6SVHM8nGV/aDoRuu9shV6MX0y/KnIgLedudn8aN2eLgjR5k1+99 +-----END DSA PRIVATE KEY----- diff --git a/test/fixtures/keys/dsa_private_encrypted_1025.pem b/test/fixtures/keys/dsa_private_encrypted_1025.pem new file mode 100644 index 0000000..b75ffaf --- /dev/null +++ b/test/fixtures/keys/dsa_private_encrypted_1025.pem @@ -0,0 +1,12 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIBvTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIqTW00yecdxMCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBKgO4UF0LfCkPyS+iCvSrtBIIB +YD3W6FyEZ97/crnoyRqjPUtr2Mm4KJMtaB5ZiGFzZEzd6AH7N/dbtAAMIibtsjmd +RYdIptpET6xTpUhM8TvpULyYaZnhZJKTpVUrTVdvFTS3DYDutu7aWRLTrle6LzcY +XpIppeP8ZmYFdRBQxhF+KoDsP4O0QA+vWl2W2VmRfr+sK9R+qV89w0YMjEWHsYY+ +VZsDbJBGKkj9gzIvxIsRyack/+RsbiSDrh6WTw+D0jrX/IMbgPjvYfBFhpxGC7zR +hDn9r3JaO2KdHh9kMtvQfshA1n636kb0X6ewY57BhEs3J4hpMg46c6YFry94to24 +jxl5KutM0CFea7mYGtNf6WJXBsm7JSW03kjlqYoZGK43KNgZhzKAsXaNkoRkA5cw +BzGfgmG6dHTpeAY9G4vM4inhCmGFA8Tx189g+xzRv16uFXRb8WFIllne1fEFaXRr +1Rz2G6SPJkA3fsrl8zUIB0Y= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test/fixtures/keys/dsa_private_pkcs8.pem b/test/fixtures/keys/dsa_private_pkcs8.pem new file mode 100644 index 0000000..8e4be9e --- /dev/null +++ b/test/fixtures/keys/dsa_private_pkcs8.pem @@ -0,0 +1,15 @@ +-----BEGIN PRIVATE KEY----- +MIICZQIBADCCAjoGByqGSM44BAEwggItAoIBAQChLDcaF/TawxKZ3mTau9Kp/p4k +8U7t8cNHK8MNuJ+M7r8EpIiRczjerIKp10LThqsqDvBzVAMRDyBvXekKacLREGKW +MTqR7jIrjpDkmOIQof1TV4Ju1OuFwassODBuq9EzToQ3oh8Avc4EXpjNL3Cff8R9 +LBbZnyAAiSNmtOMRRNX7+AQB18SB39YBnFxz3Ngo7A/LT8uOOV1DDl+w+UgSRLXI +kWVYyiRKxlViUktHTqHVLiRCgTcGoQuKsf8VB8nfpExEWxopnqdBhLrpBzyvJtDD +ZWU4myqJGhNIPBEQ/PqP2gtiHUR7zqPle32NuIGsrfdRuROv+MG07QnY9HT7AiEA +hch7FLktAk9KiL3qTo5TjKssuYAhjyBL2Z1hN1unBx0CggEBAII3eBOMWI+AFD9u +P8R2n3D3RID51D9WedgNMxzP4yL/yyC2VDc70IIVvTfbtic4vC/GyZJp1P1esirX +/mMYPZW2MUNWWuJ2NgBKuN7NHaR2vvzUb5vG1U1C4g4pmX3Gqu4KYdRJJEv++4JH +qkqwDzVk9+l1wuHdFhjEMihMrq1VfgatfltzEYMhQJCJNqoq/03OmbWWIEtGbosd +zMGwWTY5WtHIyxPeCHQnoNJTD53D9Wu7UnqCWSuvQLEFagnrlu1yq0XJBX/vpu2i +v1CBz/P2y5gGglndRrTIB+/cnYSs6zUIBuHgtY1QsD14k5tjZHiwBXtDzA4xPmOa +Mqji6ekEIgIgNBV+HuDLNzl1J5rbEwC5OWDIjBd41KeKUQ4RxSgYLGE= +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/dsa_public.pem b/test/fixtures/keys/dsa_public.pem new file mode 100644 index 0000000..7d2f2c6 --- /dev/null +++ b/test/fixtures/keys/dsa_public.pem @@ -0,0 +1,20 @@ +-----BEGIN PUBLIC KEY----- +MIIDSDCCAjoGByqGSM44BAEwggItAoIBAQChLDcaF/TawxKZ3mTau9Kp/p4k8U7t +8cNHK8MNuJ+M7r8EpIiRczjerIKp10LThqsqDvBzVAMRDyBvXekKacLREGKWMTqR +7jIrjpDkmOIQof1TV4Ju1OuFwassODBuq9EzToQ3oh8Avc4EXpjNL3Cff8R9LBbZ +nyAAiSNmtOMRRNX7+AQB18SB39YBnFxz3Ngo7A/LT8uOOV1DDl+w+UgSRLXIkWVY +yiRKxlViUktHTqHVLiRCgTcGoQuKsf8VB8nfpExEWxopnqdBhLrpBzyvJtDDZWU4 +myqJGhNIPBEQ/PqP2gtiHUR7zqPle32NuIGsrfdRuROv+MG07QnY9HT7AiEAhch7 +FLktAk9KiL3qTo5TjKssuYAhjyBL2Z1hN1unBx0CggEBAII3eBOMWI+AFD9uP8R2 +n3D3RID51D9WedgNMxzP4yL/yyC2VDc70IIVvTfbtic4vC/GyZJp1P1esirX/mMY +PZW2MUNWWuJ2NgBKuN7NHaR2vvzUb5vG1U1C4g4pmX3Gqu4KYdRJJEv++4JHqkqw +DzVk9+l1wuHdFhjEMihMrq1VfgatfltzEYMhQJCJNqoq/03OmbWWIEtGbosdzMGw +WTY5WtHIyxPeCHQnoNJTD53D9Wu7UnqCWSuvQLEFagnrlu1yq0XJBX/vpu2iv1CB +z/P2y5gGglndRrTIB+/cnYSs6zUIBuHgtY1QsD14k5tjZHiwBXtDzA4xPmOaMqji +6ekDggEGAAKCAQEAhNZvg1G/S0+fLul1fy9aeivU9pnOowJsw+dXz4lMLEaL/bAx +iZkp0I0Oabd4VafIp1XzZIajiNa6XSpseVtNHz7ulQoj7sYprh0jR8yaQgFVKyB/ +GGrEvgLqwjsBjHbzgyxence8kTHcW4rM6wa8vCdE3b6hJR2dWJkA8KBx+0eKVHd9 +Y7JOIJxOqZKi4FsFQMtC11xrINiX5w73UGPHyB5Lnsy3YTsjvaUZV0mL+Hy3TGko +x8hHMK9oZ5ubZuUbAcDPrtLA3MgalI8KDfDU7iy2rHyc85o3h1g3ERhW8yrf8ZVJ +EO6X/sXXpZyzrLbjwxoJQdVPOURIceK0BioO5w== +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/dsa_public_1025.pem b/test/fixtures/keys/dsa_public_1025.pem new file mode 100644 index 0000000..1b4f083 --- /dev/null +++ b/test/fixtures/keys/dsa_public_1025.pem @@ -0,0 +1,12 @@ +-----BEGIN PUBLIC KEY----- +MIIBzjCCATsGByqGSM44BAEwggEuAoGJAOnfEYiw1wplZKm6vYBvZ/muKuG7kCxg +Ysj9dm/O56g7iQs+nezModdpsE5t3FTTuREWR+lX2V+11dPdroT9bFv7DZ3uDQXu +lZN3Cmaah9xG3CBvKeYkq+pa387B9UWx1S++lVyL0DFj/mt0z8rnhKunqJKl+14F +goaoKk+ezCKQ0PQbEozUxvMCFQCoA8oJniHGuiaKtS7fe2fc2GyrbQKBiB/S0faf +DNaU8ug3g4m8Ll1Us+8xHIlNB6Rby1HShjo96yGZk5qgjQvd78zgp5n/iqJjshE2 +Sw/ZgYnGN9Lpb6H4w2M87mvRphgVgI9DwluEE7uXSF1eaRNjcVx3JEd6HaDB7dsF +foC56/YwaAvrXoKB38ofw8Bf0Qeas09xA5Yd6bgpx8leqi4DgYwAAoGIcZ/4bJur +OAw5OL4+5BsE4jfSVFq4nmNl+m6Iy6ls3hOHOZ9sJlw0Fi+ZtdeddOTYnIngW/kH +09XGoF5JJOaLQv2O4GmZfDfnla+vfHZPsOxmYqeEbSwA2pqlhOn/dCshBOymhwNL +Xp1FJ3JUEyh8i605X06XmWoj4ZIu/tr1xbnZDLHuFjBmXA== +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/ec-cert.pem b/test/fixtures/keys/ec-cert.pem new file mode 100644 index 0000000..1fd84b2 --- /dev/null +++ b/test/fixtures/keys/ec-cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB6zCCAZICCQDB6nsD1ZVtUjAKBggqhkjOPQQDAjB9MQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAOBgNV +BAsMB05vZGUuanMxDzANBgNVBAMMBmFnZW50MjEgMB4GCSqGSIb3DQEJARYRcnlA +dGlueWNsb3Vkcy5vcmcwIBcNMTgxMTE2MTg0MzE0WhgPMjI5MjA4MzAxODQzMTRa +MH0xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNV +BAoMBkpveWVudDEQMA4GA1UECwwHTm9kZS5qczEPMA0GA1UEAwwGYWdlbnQyMSAw +HgYJKoZIhvcNAQkBFhFyeUB0aW55Y2xvdWRzLm9yZzBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABEppwUcxZQc2qbTxko8VEYAtKhMPbzzkhMlC3iqv+DJG9gXRJ3Kt +8vz3/HuBV6gGDjuaTObou//p7sBjOdvcTXAwCgYIKoZIzj0EAwIDRwAwRAIgAmC2 +Xfpv1zjKePs3xAAWGP3Xp9+1lOdHpA3mTjlAFOoCIAJagVrpr8rWOf73fdN31xrs +8IdbV8S1DDlqYzANVPnA +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/ec-csr.pem b/test/fixtures/keys/ec-csr.pem new file mode 100644 index 0000000..ab9e208 --- /dev/null +++ b/test/fixtures/keys/ec-csr.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBODCB3wIBADB9MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcM +AlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAOBgNVBAsMB05vZGUuanMxDzANBgNVBAMM +BmFnZW50MjEgMB4GCSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAARKacFHMWUHNqm08ZKPFRGALSoTD2885ITJQt4q +r/gyRvYF0SdyrfL89/x7gVeoBg47mkzm6Lv/6e7AYznb3E1woAAwCgYIKoZIzj0E +AwIDSAAwRQIhANc8OD4E9EhR8SBrvdgA6n0rg9x6cpWst4cMkR59wkSJAiAD+kOE +X4RCKkmFxRysPaXrbwEQRCVYV/ynrCsYm5kbnA== +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/ec-key.pem b/test/fixtures/keys/ec-key.pem new file mode 100644 index 0000000..f9e40a8 --- /dev/null +++ b/test/fixtures/keys/ec-key.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIBxOoTv4SYyESx2cM2ittkAS3z3YGQsPp38otS0PpkqhoAoGCCqGSM49 +AwEHoUQDQgAESmnBRzFlBzaptPGSjxURgC0qEw9vPOSEyULeKq/4Mkb2BdEncq3y +/Pf8e4FXqAYOO5pM5ui7/+nuwGM529xNcA== +-----END EC PRIVATE KEY----- diff --git a/test/fixtures/keys/ec.cnf b/test/fixtures/keys/ec.cnf new file mode 100644 index 0000000..03bd566 --- /dev/null +++ b/test/fixtures/keys/ec.cnf @@ -0,0 +1,17 @@ +[ req ] +default_bits = 1024 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no + +[ req_distinguished_name ] +C = US +ST = CA +L = SF +O = Joyent +OU = Node.js +CN = agent2 +emailAddress = ry@tinyclouds.org + +[ req_attributes ] diff --git a/test/fixtures/keys/ec.pfx b/test/fixtures/keys/ec.pfx new file mode 100644 index 0000000..2d2f502 Binary files /dev/null and b/test/fixtures/keys/ec.pfx differ diff --git a/test/fixtures/keys/ec10-cert.pem b/test/fixtures/keys/ec10-cert.pem new file mode 100644 index 0000000..d6019eb --- /dev/null +++ b/test/fixtures/keys/ec10-cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIB9zCCAZ6gAwIBAgIJAKl1NQOcXpYrMAoGCCqGSM49BAMCMIGIMQswCQYDVQQG +EwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMR8wHQYDVQQKDBZUaGUgTm9k +ZS5qcyBGb3VuZGF0aW9uMRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTYx +HjAcBgkqhkiG9w0BCQEWD2NhNkBleGFtcGxlLm9yZzAgFw0xODExMTYxODQyMjFa +GA8yMjkyMDgzMDE4NDIyMVoweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw +CQYDVQQHDAJTRjEfMB0GA1UECgwWVGhlIE5vZGUuanMgRm91bmRhdGlvbjEQMA4G +A1UECwwHTm9kZS5qczEcMBoGA1UEAwwTYWdlbnQxMC5leGFtcGxlLmNvbTBZMBMG +ByqGSM49AgEGCCqGSM49AwEHA0IABDuWsQNVJ8wPZjqFqkkVeuYfYrfgstcPO7Ai +cGrgfQKeFvz+oLd6+U+OGFMFh0+LkI6oTADserWMFyuaiqDIFzAwCgYIKoZIzj0E +AwIDRwAwRAIgM0oqALjQXRlxmJlje71j0AX22Dq732dH0/oBEHiTDBcCIBbOfILG +vtveS9DMkz5VVlqQJ2TsDcSerhXHcLowXt5i +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICGzCCAcGgAwIBAgIJAMTCBUQ4OI4+MAoGCCqGSM49BAMCMIGIMQswCQYDVQQG +EwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMR8wHQYDVQQKDBZUaGUgTm9k +ZS5qcyBGb3VuZGF0aW9uMRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTUx +HjAcBgkqhkiG9w0BCQEWD2NhNUBleGFtcGxlLm9yZzAgFw0xODExMTYxODQyMjFa +GA8yMjkyMDgzMDE4NDIyMVowgYgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEL +MAkGA1UEBwwCU0YxHzAdBgNVBAoMFlRoZSBOb2RlLmpzIEZvdW5kYXRpb24xEDAO +BgNVBAsMB05vZGUuanMxDDAKBgNVBAMMA2NhNjEeMBwGCSqGSIb3DQEJARYPY2E2 +QGV4YW1wbGUub3JnMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEa7HfEgyVTPWY +ku9cWGRSym5OdB7zqFihL8+k93EfWViJph72fJH3sOZypUgDXS/sEyUaLhbxtLYz +sMbECzEDwaMQMA4wDAYDVR0TBAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEA+NIP +zuqh2e3/59QndyPqRH2CZ4V4ipU6rf6ZZmwPApUCIBMABWesJfwdrETIjN6dT8gc +STrYyR4ovD8Aofubqjd0 +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/ec10-csr.pem b/test/fixtures/keys/ec10-csr.pem new file mode 100644 index 0000000..da92a5e --- /dev/null +++ b/test/fixtures/keys/ec10-csr.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBWDCB/wIBADB4MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcM +AlNGMR8wHQYDVQQKDBZUaGUgTm9kZS5qcyBGb3VuZGF0aW9uMRAwDgYDVQQLDAdO +b2RlLmpzMRwwGgYDVQQDDBNhZ2VudDEwLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0C +AQYIKoZIzj0DAQcDQgAEO5axA1UnzA9mOoWqSRV65h9it+Cy1w87sCJwauB9Ap4W +/P6gt3r5T44YUwWHT4uQjqhMAOx6tYwXK5qKoMgXMKAlMCMGCSqGSIb3DQEJBzEW +DBRBIGNoYWxsZW5nZSBwYXNzd29yZDAKBggqhkjOPQQDAgNIADBFAiEAgyYiDWmr +Ks1RAQAsxR91PFzxJRa7dgclWPmuDTGTxhcCIFLtalcAOlD+4Wa06SvgGWN/R4V2 +u/JZjWD+lWFZjOC5 +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/keys/ec10-key.pem b/test/fixtures/keys/ec10-key.pem new file mode 100644 index 0000000..eca56fc --- /dev/null +++ b/test/fixtures/keys/ec10-key.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIAAaqQ4/ivoch4lwve3fjDLeycYlB3q15IoxsA3fEBA7oAoGCCqGSM49 +AwEHoUQDQgAEO5axA1UnzA9mOoWqSRV65h9it+Cy1w87sCJwauB9Ap4W/P6gt3r5 +T44YUwWHT4uQjqhMAOx6tYwXK5qKoMgXMA== +-----END EC PRIVATE KEY----- diff --git a/test/fixtures/keys/ec10.pfx b/test/fixtures/keys/ec10.pfx new file mode 100644 index 0000000..59448e5 Binary files /dev/null and b/test/fixtures/keys/ec10.pfx differ diff --git a/test/fixtures/keys/ec_p256_private.pem b/test/fixtures/keys/ec_p256_private.pem new file mode 100644 index 0000000..6bb0bb9 --- /dev/null +++ b/test/fixtures/keys/ec_p256_private.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDxBsPQPIgMuMyQbx +zbb9toew6Ev6e9O6ZhpxLNgmAEqhRANCAARfSYxhH+6V5lIg+M3O0iQBLf+53kuE +2luIgWnp81/Ya1Gybj8tl4tJVu1GEwcTyt8hoA7vRACmCHnI5B1+bNpS +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ec_p256_public.pem b/test/fixtures/keys/ec_p256_public.pem new file mode 100644 index 0000000..08f7bd2 --- /dev/null +++ b/test/fixtures/keys/ec_p256_public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX0mMYR/uleZSIPjNztIkAS3/ud5L +hNpbiIFp6fNf2GtRsm4/LZeLSVbtRhMHE8rfIaAO70QApgh5yOQdfmzaUg== +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/ec_p384_private.pem b/test/fixtures/keys/ec_p384_private.pem new file mode 100644 index 0000000..06393e2 --- /dev/null +++ b/test/fixtures/keys/ec_p384_private.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB3B+4e4C1OUxGftkEI +Gb/SCulzUP/iE940CB6+B6WWO4LT76T8sMWiwOAGUsuZmyKhZANiAASE43efMYmC +/7Tx90elDGBEkVnOUr4ZkMZrl/cqe8zfVy++MmayPhR46Ah3LesMCNV+J0eG15w0 +IYJ8uqasuMN6drU1LNbNYfW7+hR0woajldJpvHMPv7wlnGOlzyxH1yU= +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ec_p384_public.pem b/test/fixtures/keys/ec_p384_public.pem new file mode 100644 index 0000000..2b50f3b --- /dev/null +++ b/test/fixtures/keys/ec_p384_public.pem @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhON3nzGJgv+08fdHpQxgRJFZzlK+GZDG +a5f3KnvM31cvvjJmsj4UeOgIdy3rDAjVfidHhtecNCGCfLqmrLjDena1NSzWzWH1 +u/oUdMKGo5XSabxzD7+8JZxjpc8sR9cl +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/ec_p521_private.pem b/test/fixtures/keys/ec_p521_private.pem new file mode 100644 index 0000000..e4a8a65 --- /dev/null +++ b/test/fixtures/keys/ec_p521_private.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAEghuafcab9jXW4gO +QLeDaKOlHEiskQFjiL8klijk6i6DNOXcFfaJ9GW48kxpodw16ttAf9Z1WQstfzpK +GUetHImhgYkDgYYABAGixYI8Gbc5zNze6rH2/OmsFV3unOnY1GDqG9RTfpJZXpL9 +ChF1dG8HA4zxkM+X+jMSwm4THh0Wr1Euj9dK7E7QZwHd35XsQXgH13Hjc0QR9dvJ +BWzlg+luNTY8CkaqiBdur5oFv/AjpXRimYxZDkhAEsTwXLwNohSUVMkN8IQtNI9D +aQ== +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ec_p521_public.pem b/test/fixtures/keys/ec_p521_public.pem new file mode 100644 index 0000000..c0ed00f --- /dev/null +++ b/test/fixtures/keys/ec_p521_public.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBosWCPBm3Oczc3uqx9vzprBVd7pzp +2NRg6hvUU36SWV6S/QoRdXRvBwOM8ZDPl/ozEsJuEx4dFq9RLo/XSuxO0GcB3d+V +7EF4B9dx43NEEfXbyQVs5YPpbjU2PApGqogXbq+aBb/wI6V0YpmMWQ5IQBLE8Fy8 +DaIUlFTJDfCELTSPQ2k= +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/ec_secp256k1_private.pem b/test/fixtures/keys/ec_secp256k1_private.pem new file mode 100644 index 0000000..f753c75 --- /dev/null +++ b/test/fixtures/keys/ec_secp256k1_private.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgc34ocwTwpFa9NZZh3l88 +qXyrkoYSxvC0FEsU5v1v4IOhRANCAARw7OEVKlbGFqUJtY10/Yf/JSR0LzUL1PZ1 +4Ol/ErujAPgNwwGU5PSD6aTfn9NycnYB2hby9XwB2qF3+El+DV8q +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ec_secp256k1_public.pem b/test/fixtures/keys/ec_secp256k1_public.pem new file mode 100644 index 0000000..e95322e --- /dev/null +++ b/test/fixtures/keys/ec_secp256k1_public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEcOzhFSpWxhalCbWNdP2H/yUkdC81C9T2 +deDpfxK7owD4DcMBlOT0g+mk35/TcnJ2AdoW8vV8Adqhd/hJfg1fKg== +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/ed25519_private.pem b/test/fixtures/keys/ed25519_private.pem new file mode 100644 index 0000000..f837457 --- /dev/null +++ b/test/fixtures/keys/ed25519_private.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIMFSujN0jIUIdzSvuxka0lfgVVkMdRTuaVvIYUHrvzXQ +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ed25519_public.pem b/test/fixtures/keys/ed25519_public.pem new file mode 100644 index 0000000..4127a47 --- /dev/null +++ b/test/fixtures/keys/ed25519_public.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAK1wIouqnuiA04b3WrMa+xKIKIpfHetNZRv3h9fBf768= +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/ed448_private.pem b/test/fixtures/keys/ed448_private.pem new file mode 100644 index 0000000..9643665 --- /dev/null +++ b/test/fixtures/keys/ed448_private.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MEcCAQAwBQYDK2VxBDsEOdOtCnu9bDdBqSHNNZ5xoDA5KdLBTUNPcKFaOADNX32s +dfpo52pCtPqfku/l3/OfUHsF43EfZsaaWA== +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/ed448_public.pem b/test/fixtures/keys/ed448_public.pem new file mode 100644 index 0000000..b767109 --- /dev/null +++ b/test/fixtures/keys/ed448_public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MEMwBQYDK2VxAzoAoX/ee5+jlcU53+BbGRsGIzly0V+SZtJ/oGXY0udf84q2hTW2 +RdstLktvwpkVJOoNb7oDgc2V5ZUA +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/fake-cnnic-root-cert.pem b/test/fixtures/keys/fake-cnnic-root-cert.pem new file mode 100644 index 0000000..da1b9d6 --- /dev/null +++ b/test/fixtures/keys/fake-cnnic-root-cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC+TCCAeGgAwIBAgIJAJfD0pHVqYGkMA0GCSqGSIb3DQEBCwUAMDIxCzAJBgNV +BAYTAkNOMQ4wDAYDVQQKDAVDTk5JQzETMBEGA1UEAwwKQ05OSUMgUk9PVDAgFw0x +ODExMTYxODQyMjFaGA8yMjkyMDgzMDE4NDIyMVowMjELMAkGA1UEBhMCQ04xDjAM +BgNVBAoMBUNOTklDMRMwEQYDVQQDDApDTk5JQyBST09UMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA0nOFmJ4C0bUucql6YlXHPVyuxh5IxZ0heSjVCJXp +Vk9JJRPxpU0Py1tSTW7GJSMRIsvFrrbVfb52YHOzaGwMiJ5OcR1cCVXWR5fci0lS +0mTh8Rf+igHjKe/qrpOoqWzw7a0AHkFbcA5pGOZcB9qW2aMq3+mv4z+K8Jpw/b7G +4QGeEfNx52xM/ygtW51GnmxFJp0eTrIQmJbPFKVrjjdAner+8v9fgmrYiFydFknf +EqzFlsO1hcflw3caVD/usBpHq4VVvzy2fPqx6VJsloMYyNlCJIvrC0woHb/yj7Da +JEnHu0MdEhrxzA+biw0/sgOH3au2FdtPoZlmloyReydiGQIDAQABoxAwDjAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAMz67MddcgXRPkTEiRhVFrTIqr +rny80Smrz9byJx7KhK0ciaDm+KvJavm7e6rrzuedOmbOdhZ3oN5wVo55v1fZqhL1 +dpt1/BlzyCBADE4FM2pDyvLKxbGGcpCzFCbi2K5WsKFJspm6Zr43R1y9UtyInNXB +GLYwcbGqUrhbtLgnGMBccS/rEgasN3RGbADnZulfzkoGSCTAHJP6B+pFIW6T3kyr +Sa+Wuvs0UeoaicOzMz8vTySpJPF+8kW/7MSuW26XZyc966Ht1dUuosv9Umg2VNcF +SxRoUuaQKyHkyh/p4JmrDu2I9J25VE3FNvH7YoJCyaVrF213LvVA92FtDqzp +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/fake-cnnic-root-cert.srl b/test/fixtures/keys/fake-cnnic-root-cert.srl new file mode 100644 index 0000000..6a7a03f --- /dev/null +++ b/test/fixtures/keys/fake-cnnic-root-cert.srl @@ -0,0 +1 @@ +9E13B420E1F5C718 diff --git a/test/fixtures/keys/fake-cnnic-root-key.pem b/test/fixtures/keys/fake-cnnic-root-key.pem new file mode 100644 index 0000000..9189469 --- /dev/null +++ b/test/fixtures/keys/fake-cnnic-root-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA0nOFmJ4C0bUucql6YlXHPVyuxh5IxZ0heSjVCJXpVk9JJRPx +pU0Py1tSTW7GJSMRIsvFrrbVfb52YHOzaGwMiJ5OcR1cCVXWR5fci0lS0mTh8Rf+ +igHjKe/qrpOoqWzw7a0AHkFbcA5pGOZcB9qW2aMq3+mv4z+K8Jpw/b7G4QGeEfNx +52xM/ygtW51GnmxFJp0eTrIQmJbPFKVrjjdAner+8v9fgmrYiFydFknfEqzFlsO1 +hcflw3caVD/usBpHq4VVvzy2fPqx6VJsloMYyNlCJIvrC0woHb/yj7DaJEnHu0Md +EhrxzA+biw0/sgOH3au2FdtPoZlmloyReydiGQIDAQABAoIBAEDF04mcoIuA81HR +PdzEP/Vv8E8EBSvlZ+cNnTvuQAoTjxS9ZbOV21Wgvt0cShomB+EozKgwl9cC5xZa +pg5uqxDlgIkqGyi4ZaJVaEjqgXZGHJCC7RH28L74m8etpMy4vhK5G380aHs9xDUo +uYylR6ampMyT9VHBPfc94acHr9iSguuPlZcO37aBc7BPRTa6MBFtLC8c2K7ybI+b +yeGJypsd3N5aIzM3+JOVJQzD/3jNHODdAZKdCw5ZAfrUBIS4OaI/bC+3Ke/OvTrI +tliXqHemlJNdVbGdOneaUu6Ysq/en4NK3d86b03dGrLnCsWeMa194TXYfLRQEVtn +qbuXroECgYEA75KNQ7Fr7SX9r/TCp7Qro3cVoFxsgntypay3uqoiq2dJTvyBcgYh +eawfY4vE2YVTGRmlwqits0SMr6VBa/BqFwm7D2IDqJDJ4blggmJ6m9qMQLADg2Mc +CX5rRu24NsH8LEcikvXJy/qPUeFm8JDjEcRui66KLaM/IeVh5NZZn2kCgYEA4OHG +84ExUNEXk7qoCd3CSlkFYj/7NgMcEiEeRI1gViaLp7XuI4i1my0JZbvmznyAUsxd +NdDsYpqvETKuXA5G9doJwI5Y4FkMt9nfPniKsHESwvaIardeM42DuUOtnYApi74d +GRd6RipJHFdfv6GNaftx5w2nyIVn1vfYaAsEBzECgYBOIh/MWgr29xL71fm+NDaf +Q3FcMYh6LcTAX8o0KNTRzgfMqPGWvIUiZ459KtJyltb5MrIrAFRWSR8REfZ6O5h+ +FwBZDgBfc4lEAu+E1pViSy6+0ijzKtm0BvT51wHjafTShAi0oVDFI9ymObsW7koA +O25KRAxwwfMPHP6GYZotMQKBgCJ5mmV0Ndo8489q+x3gGEwLj667PkjOezwwRZKe +1dj/OcOxOVvLNoQeiGVHRB/9qDKJT/TTHZoUOqh5S4+jRK+mCH6zk9546GE7DmVm +V2SrQQQQhWNOzys6E6qQPIp7vmLE93METWN6UhD9OBmJq8NGn/Sa/FDaWsvy3QM+ +RRTRAoGBAKQJMSfuiEB42TtVm0VnIbs+r9iDXgKi0ifDQSQyHjptYL2KF5GCNljY +d9ZZJJsfMmj2pDeggLE9RB8yANQGQ87KfvZ45pZ6qL48Efw5ZknpeSjswHby87Qc +24HmFjQKg6DhN+mRmxRjKe1rQpchRkaoO5kFmE2IUknuIybUkKMV +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/fake-cnnic-root.cnf b/test/fixtures/keys/fake-cnnic-root.cnf new file mode 100644 index 0000000..4b88c23 --- /dev/null +++ b/test/fixtures/keys/fake-cnnic-root.cnf @@ -0,0 +1,19 @@ +[ req ] +default_bits = 2048 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +output_password = password +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = CN +O = CNNIC +CN = CNNIC ROOT + +[ req_attributes ] +challengePassword = A challenge password + +[ v3_ca ] +basicConstraints = CA:TRUE diff --git a/test/fixtures/keys/fake-startcom-root-cert.pem b/test/fixtures/keys/fake-startcom-root-cert.pem new file mode 100644 index 0000000..48e5713 --- /dev/null +++ b/test/fixtures/keys/fake-startcom-root-cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDjzCCAnegAwIBAgIJAIIPb0xPNcgKMA0GCSqGSIb3DQEBCwUAMH0xCzAJBgNV +BAYTAklMMRYwFAYDVQQKDA1TdGFydENvbSBMdGQuMSswKQYDVQQLDCJTZWN1cmUg +RGlnaXRhbCBDZXJ0aWZpY2F0ZSBTaWduaW5nMSkwJwYDVQQDDCBTdGFydENvbSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAgFw0xODExMTYxODQyMjFaGA8yMjkyMDgz +MDE4NDIyMVowfTELMAkGA1UEBhMCSUwxFjAUBgNVBAoMDVN0YXJ0Q29tIEx0ZC4x +KzApBgNVBAsMIlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxKTAn +BgNVBAMMIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1mZ/bufFVPGxKagC8W7hpBephIFIZw9K +bX6ska2PXZkyqRToU5UFgTYhdBwkCNJMwaYfTqLpc9y/goRpVlLSAFk/t4W6Z0w1 +b80T149XvmelAUQTBJR49kkYspN+Jw627pf8tmmSkG5qcHykB9gr/nvoTpXtlk2t +um/SL3BQSqXmqffBM/6VpFvGAB2FNWGQUIxj55e/7p9Opjo8yS4s2lnbovV6OSJ/ +CnqEYt6Ur4kdLwVOLKlMKRG3H4q65UXfoVpE+XhFgKADAiMZySSGjBsbjF6ADPnP +/zNklvYwcM0phtQivmkKEcSOvJNsZodszYhoiwie5OknOo7Mqz9jqQIDAQABoxAw +DjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBrsLtF6MEMCWQF6YXP +DLw4friQhYzoB7w1W+fgksOOIyLyRmUEEA9X0FSfNW2a6KLmMtSoNYn3y5cLkmGr ++JE4U3ovvXDU8C3r09dynuHywcib4oFRaG8NKNqldUryO3abk+kbdxMvxQlA/NHb +33ABKPX7UTnTr6CexZ5Qr0ss62w0ELwxC3eVugJrVtDOmFt/yZF75lc0OgifK4Nj +dii7g+sQvzymIgdWLAIbbrc3r/NfymFgmTEMPY/M17QEIdr9YS1qAHmqA6vGvmBz +v2fCr+xrOQRzq+HO1atOmz8gOdtYJwDfUl2CWgJ2r8iMRsOTE7QgEl/+zpOM3fe+ +JU1b +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/fake-startcom-root-database.txt b/test/fixtures/keys/fake-startcom-root-database.txt new file mode 100644 index 0000000..3daded9 --- /dev/null +++ b/test/fixtures/keys/fake-startcom-root-database.txt @@ -0,0 +1,2 @@ +V 22920830184221Z 01 unknown /C=US/ST=CA/L=SF/O=NODEJS/OU=agent8/CN=localhost +V 22920830184221Z 02 unknown /C=US/ST=CA/L=SF/O=NODEJS/OU=agent9/CN=localhost diff --git a/test/fixtures/keys/fake-startcom-root-database.txt.attr b/test/fixtures/keys/fake-startcom-root-database.txt.attr new file mode 100644 index 0000000..3a7e39e --- /dev/null +++ b/test/fixtures/keys/fake-startcom-root-database.txt.attr @@ -0,0 +1 @@ +unique_subject = no diff --git a/test/fixtures/keys/fake-startcom-root-database.txt.attr.old b/test/fixtures/keys/fake-startcom-root-database.txt.attr.old new file mode 100644 index 0000000..3a7e39e --- /dev/null +++ b/test/fixtures/keys/fake-startcom-root-database.txt.attr.old @@ -0,0 +1 @@ +unique_subject = no diff --git a/test/fixtures/keys/fake-startcom-root-database.txt.old b/test/fixtures/keys/fake-startcom-root-database.txt.old new file mode 100644 index 0000000..4cd5277 --- /dev/null +++ b/test/fixtures/keys/fake-startcom-root-database.txt.old @@ -0,0 +1 @@ +V 22920830184221Z 01 unknown /C=US/ST=CA/L=SF/O=NODEJS/OU=agent8/CN=localhost diff --git a/test/fixtures/keys/fake-startcom-root-issued-certs/01.pem b/test/fixtures/keys/fake-startcom-root-issued-certs/01.pem new file mode 100644 index 0000000..7b9304d --- /dev/null +++ b/test/fixtures/keys/fake-startcom-root-issued-certs/01.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUDCCAjgCAQEwDQYJKoZIhvcNAQELBQAwfTELMAkGA1UEBhMCSUwxFjAUBgNV +BAoMDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsMIlNlY3VyZSBEaWdpdGFsIENlcnRp +ZmljYXRlIFNpZ25pbmcxKTAnBgNVBAMMIFN0YXJ0Q29tIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5MCAXDTE2MTAyMDIzNTk1OVoYDzIyOTIwODMwMTg0MjIxWjBdMQsw +CQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ8wDQYDVQQKDAZO +T0RFSlMxDzANBgNVBAsMBmFnZW50ODESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvCbXGqz553XQ+W9zsQEaBc1/mhd4 +TFjivwbK1hSdTuB8vWyOw6oZuqAJjctcIPmNXf01zV1+cAurpoU8k9SmtetwqaDV +0K5ooKUuzgAefRoLJqU0XonW4VaK0ICQATkxSWdJzYET68NTukv5f9Fh0Jfi2Q6Y +PKlgUIuoTPQJSErAMsdph4KWMP7zsaEZNJhmZ1Lprfm4DdVnwUfYvDhq5VmAHFLj +Vor/z3DJS+pW9oORDta3CMvAY5oGcIYWWMxsoG9B9NtTTs58jjeFpJrw/RYJA/CM +uRawLWKt/z1zPhzmvknTKfAIc6SjbBqu8Nx/Xvcd61c2V39U/nZDTs+H9QIDAQAB +MA0GCSqGSIb3DQEBCwUAA4IBAQBfy91+ceZDfZ0DnHHAlm8e+26V5sdrdOXZJtkc +AacDcCX6AD1iMK+0axBgG6ZJs6m87cmFdaq23pLpBLQ+KHSdG5YgRCEuWW+RaJGj +/vVn9AS4eB3EmX0RhhJgYyVbN7ye8qjfAv0NtHzUsdMS8ay3HbdUCtrcsHonGDR3 +t/0BGsYny9Kt2f2PNN32UEkx/jhcssXwnNGxyxR/04heJUe6LI5ErdQoxxvaZtrd +u9ZgjSxix4dFH4nTYEYe3oXM1U7PakbzOzJvRMmDh8vYyK7/ih0w8/DcsK0d1Oej +mgtTF/IyJqy8T9goFf9U2uSshia+sKJBfrrzRaUHZMx+ZobA +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/fake-startcom-root-issued-certs/02.pem b/test/fixtures/keys/fake-startcom-root-issued-certs/02.pem new file mode 100644 index 0000000..7fdada4 --- /dev/null +++ b/test/fixtures/keys/fake-startcom-root-issued-certs/02.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUjCCAjoCAQIwDQYJKoZIhvcNAQELBQAwfTELMAkGA1UEBhMCSUwxFjAUBgNV +BAoMDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsMIlNlY3VyZSBEaWdpdGFsIENlcnRp +ZmljYXRlIFNpZ25pbmcxKTAnBgNVBAMMIFN0YXJ0Q29tIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5MCIYDzIwMTYxMDIxMDAwMDAxWhgPMjI5MjA4MzAxODQyMjFaMF0x +CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoM +Bk5PREVKUzEPMA0GA1UECwwGYWdlbnQ5MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2oEMk2EKwIZrx4IPNcGjHw5DO +u8A8yJrcWG4pThUadrvwMI7bQ4QwNgHm4PVpbjAPbSUsRPX98PWL6GcpoH0lmJ9+ +j9CCEIEkW+j5wM7hYBXUSGuAZZfkdrpbZHsvwpYj2U39sfmUyGT1gBbGBmaAzODh +ZaqYSm9VdaKS56SRMey3Pbsx+ikylgiEyPFoRKA141Zuxz1MKiwszLHuyz6pCZKY +K7x1dlEGi3h3dvkRAdMyeSXJkYCZGbS5Fbl2OuW4pSWP4no/M960vBwEYvuJPDtx +qxGezE51oXp4W4l9k+TYPOfGJDVW0PAg+JpfbepLetgFaO9/eNWes34AhF6FAgMB +AAEwDQYJKoZIhvcNAQELBQADggEBAD8ojlI4FdXLCyHVYwwTP6BncF4tfeGP82/i +Zcr8U9k28T2vlsLwdCGu8UVqGWfYrSY5oZqZmHPcDfZmv6Uc39eQ72aweoyLedk3 +UF1Ucwq+MxEM98doLlqL4lnPO1+TcpdhtoHAgT28WkddbR3alfsu+GRU3br3s4lS +DHcm6UzdA/lkgZtC8wFUSW04WhzSHB78gm8VOl+1JGY0pp/T+ae5swkfj45Q3jOd +H6jdZiUrU+LJQwLlXYniF4qzmH0SN8Gd3djVNzWJtNF+LFKXzCOYSK8AFaQ6Ta+s +Pd6Rqa8Hl6cMmlsDu1NLumstvGna5wsc7ks1VZwtWt6WfIyIN2k= +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/fake-startcom-root-key.pem b/test/fixtures/keys/fake-startcom-root-key.pem new file mode 100644 index 0000000..748c3c1 --- /dev/null +++ b/test/fixtures/keys/fake-startcom-root-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA1mZ/bufFVPGxKagC8W7hpBephIFIZw9KbX6ska2PXZkyqRTo +U5UFgTYhdBwkCNJMwaYfTqLpc9y/goRpVlLSAFk/t4W6Z0w1b80T149XvmelAUQT +BJR49kkYspN+Jw627pf8tmmSkG5qcHykB9gr/nvoTpXtlk2tum/SL3BQSqXmqffB +M/6VpFvGAB2FNWGQUIxj55e/7p9Opjo8yS4s2lnbovV6OSJ/CnqEYt6Ur4kdLwVO +LKlMKRG3H4q65UXfoVpE+XhFgKADAiMZySSGjBsbjF6ADPnP/zNklvYwcM0phtQi +vmkKEcSOvJNsZodszYhoiwie5OknOo7Mqz9jqQIDAQABAoIBAAcdibcljAAAsW9/ +evGGS4jFnEOggsWg1UiC/rkq+GoTzoGcBwXXGUKriDqxQGTmjdOTbtCOSY8l0VlE +ibZqszt9usadco1BEzjtpm3t/Ox9xhUfrD3nq4gI7v/mMzaan2mVs7ZeFJYkg/XN +vSfhfbxJYnFROnxVgaGBWolmgdOoVE1gB/aRlHO9XTF08Rq1+gQFCUxPDKSEtwFQ +L/7Bw4NrbnE7W3WAsSmPjd/tH3UfjCcVaRvMyqFNaeLbmt/6uCbaZbIJVgXhPfKS +Z+ANYWRPi8WSf9bEbj2Xu7hdI/jvFSe44zHvBCnSfjw0RSgoNc5GTT+d37C/Him1 +AyAa3z0CgYEA7C+KsqSJM+0xiwyJlRDzlfW3kf/SdobYCQOkXx17w/RjmZsQNwWr +EgHO/xqm+5YSrxeQEFbzHjCWLYw7k1bEBjC/tFAV16+1P275RDnOHzRm3xTWN74P +Q6ECjy6ww9QwyPIdXwKuTp3MFeG4jfTH4glGKGTKC3n5SZ9JZLVu+DMCgYEA6GMU ++gKBnsPH1wtWbpgezBlEApfPiE8E4sNInh5dwDYBVozr4+Es9eP7CQ0Dh2pSnc3U +FgbsJ15J9ojkyL0MA9zS/z/rqVjyybeb86My7GGZJ7zTkBMXoH35H8hzDYAe5blQ +X9N67UJ1dsWb+Vj83mUkw4NzCM958JO3trBAyLMCgYA12yFlWt9uV8fUTSeSNitV +JpKVWCBFprncVFhG2BJAvJl5jUJFSaWYlZD92rX46F+aTWUsVKdbWvjjqfZrwn0w +bC1KkHhqlkZeEJAGXqgBtZE/jSDL1Srl4PEUdTEZdmkpaQwJfjMA+jpvQukydX6e +rD6zN0hbFZUilI/HxxdmwQKBgFyKaGYO7XM936zhFPBBn7IDNbQapEhRv05WGert +iMPsPagrwhwjJXZd7S/zgL5CNtgkiRqkcxJSV/3XEdRmhAxduaBv4fa0Nyrg9TeW +e8bqLsVGSrGLCNOelsBzYG214Zf1re4bF064MnKzyqMHLtuZR4ScKgkOJi8JhBU6 +JvJFAoGBAIEmjibErM1/gjh+8MfEZHSuziP0+8bcRSEsKWz1oqqI2gRIzw5ffVOF +xU5FuiYPrY9OZ4Gtva88M9/JGfwQSc8S6gmIWx/eFFSTW/ys/XYH3j0hCUv08Te9 +lCTRX9ivVNvmUenLWe30V283TgdoQXlHVJnu2xQLxMrJeUuOKGMi +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/fake-startcom-root-serial b/test/fixtures/keys/fake-startcom-root-serial new file mode 100644 index 0000000..75016ea --- /dev/null +++ b/test/fixtures/keys/fake-startcom-root-serial @@ -0,0 +1 @@ +03 diff --git a/test/fixtures/keys/fake-startcom-root-serial.old b/test/fixtures/keys/fake-startcom-root-serial.old new file mode 100644 index 0000000..9e22bcb --- /dev/null +++ b/test/fixtures/keys/fake-startcom-root-serial.old @@ -0,0 +1 @@ +02 diff --git a/test/fixtures/keys/fake-startcom-root.cnf b/test/fixtures/keys/fake-startcom-root.cnf new file mode 100644 index 0000000..ba92892 --- /dev/null +++ b/test/fixtures/keys/fake-startcom-root.cnf @@ -0,0 +1,46 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = . +name_opt = CA_default +cert_opt = CA_default +default_crl_days = 9999 +default_md = sha256 +database = fake-startcom-root-database.txt +serial = fake-startcom-root-serial +private_key = fake-startcom-root-key.pem +certificate = fake-startcom-root-cert.pem +new_certs_dir = fake-startcom-root-issued-certs +email_in_dn = no +policy = policy_anything + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +default_bits = 2048 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +output_password = password +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = IL +O = StartCom Ltd. +OU = Secure Digital Certificate Signing +CN = StartCom Certification Authority + +[ req_attributes ] +challengePassword = A challenge password + +[ v3_ca ] +basicConstraints = CA:TRUE diff --git a/test/fixtures/keys/incorrect_san_correct_subject-cert.pem b/test/fixtures/keys/incorrect_san_correct_subject-cert.pem new file mode 100644 index 0000000..787d9f1 --- /dev/null +++ b/test/fixtures/keys/incorrect_san_correct_subject-cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBqDCCAU6gAwIBAgIUE3Kx4WUjkwuKy/fBOM+UJkb9aSAwCgYIKoZIzj0EAwIw +GzEZMBcGA1UEAwwQZ29vZC5leGFtcGxlLmNvbTAeFw0yMTEyMTExNjUxNDVaFw0z +MTEyMDkxNjUxNDVaMBsxGTAXBgNVBAMMEGdvb2QuZXhhbXBsZS5jb20wWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAASQ/CKa5uMZuLYssnNOm7DPdw3I5Doa0Qpyf3cS +7aGatfK3tuY8qG7nJ5OGtl1WOL/gN0vRRN0/KA/iRJyjafzzo3AwbjAdBgNVHQ4E +FgQUFkpgPzE1ePjK5UsPcR0gk5uLsTUwHwYDVR0jBBgwFoAUFkpgPzE1ePjK5UsP +cR0gk5uLsTUwDwYDVR0TAQH/BAUwAwEB/zAbBgNVHREEFDASghBldmlsLmV4YW1w +bGUuY29tMAoGCCqGSM49BAMCA0gAMEUCIQCMZAinQXkOEhfp+moxVnLbcUPAAqsl +1KCq3NRG91TGCgIgC4grmOhCRqJMF1RPNWobGogX/yNrYNjiGzNVyJzMR0s= +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/incorrect_san_correct_subject-key.pem b/test/fixtures/keys/incorrect_san_correct_subject-key.pem new file mode 100644 index 0000000..f7f5125 --- /dev/null +++ b/test/fixtures/keys/incorrect_san_correct_subject-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIOOVRgLS3H2T2fUhj4ASCFq60ySwO6yvSK6rvZHldAHuoAoGCCqGSM49 +AwEHoUQDQgAEkPwimubjGbi2LLJzTpuwz3cNyOQ6GtEKcn93Eu2hmrXyt7bmPKhu +5yeThrZdVji/4DdL0UTdPygP4kSco2n88w== +-----END EC PRIVATE KEY----- diff --git a/test/fixtures/keys/irrelevant_san_correct_subject-cert.pem b/test/fixtures/keys/irrelevant_san_correct_subject-cert.pem new file mode 100644 index 0000000..cdb74b7 --- /dev/null +++ b/test/fixtures/keys/irrelevant_san_correct_subject-cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBnTCCAUKgAwIBAgIUa28EJmmQ7yZOq3WWNP3SLiJnzcAwCgYIKoZIzj0EAwIw +GzEZMBcGA1UEAwwQZ29vZC5leGFtcGxlLmNvbTAeFw0yMTEyMTExNzE0NDVaFw0z +MTEyMDkxNzE0NDVaMBsxGTAXBgNVBAMMEGdvb2QuZXhhbXBsZS5jb20wWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAATEKoJfDvKQ6dD+yvc4DaeH0ZlG8VuGJUVi6iIb +ugY3dKHdmXUIuwwUScgztLc6W8FfvbTxfTF2q90ZBJlr/Klvo2QwYjAdBgNVHQ4E +FgQUu55oRZI5tdQDmViwAvPEbzZuY2owHwYDVR0jBBgwFoAUu55oRZI5tdQDmViw +AvPEbzZuY2owDwYDVR0TAQH/BAUwAwEB/zAPBgNVHREECDAGhwQBAgMEMAoGCCqG +SM49BAMCA0kAMEYCIQDw8z8d7ToB14yxMJxEDF1dhUqMReJFFwPVnvzkr174igIh +AKJ9XL+02sGOE7xZd5C0KqUXeHoIE9shnejnhm3WBrB/ +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/irrelevant_san_correct_subject-key.pem b/test/fixtures/keys/irrelevant_san_correct_subject-key.pem new file mode 100644 index 0000000..b0a9665 --- /dev/null +++ b/test/fixtures/keys/irrelevant_san_correct_subject-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIDsijdVlHMNTvJ4eqeUbpjMMnl72+HLtEIEcbauckCP6oAoGCCqGSM49 +AwEHoUQDQgAExCqCXw7ykOnQ/sr3OA2nh9GZRvFbhiVFYuoiG7oGN3Sh3Zl1CLsM +FEnIM7S3OlvBX7208X0xdqvdGQSZa/ypbw== +-----END EC PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_ca.crt b/test/fixtures/keys/rsa_ca.crt new file mode 100644 index 0000000..21878a3 --- /dev/null +++ b/test/fixtures/keys/rsa_ca.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIUf4Z4DKj5kJW60NE+9PaucYFlft8wDQYJKoZIhvcNAQEL +BQAwgbAxCzAJBgNVBAYTAlVLMRQwEgYDVQQIDAtBY2tuYWNrIEx0ZDETMBEGA1UE +BwwKUmh5cyBKb25lczEQMA4GA1UECgwHbm9kZS5qczEdMBsGA1UECwwUVGVzdCBU +TFMgQ2VydGlmaWNhdGUxFDASBgNVBAsMC0VuZ2luZWVyaW5nMRIwEAYDVQQDDAls +b2NhbGhvc3QxGzAZBgkqhkiG9w0BCQEWDGFsZXhAYXViLmRldjAgFw0xOTA2Mjgy +MTM2NDhaGA8yMjkzMDQxMTIxMzY0OFowgbAxCzAJBgNVBAYTAlVLMRQwEgYDVQQI +DAtBY2tuYWNrIEx0ZDETMBEGA1UEBwwKUmh5cyBKb25lczEQMA4GA1UECgwHbm9k +ZS5qczEdMBsGA1UECwwUVGVzdCBUTFMgQ2VydGlmaWNhdGUxFDASBgNVBAsMC0Vu +Z2luZWVyaW5nMRIwEAYDVQQDDAlsb2NhbGhvc3QxGzAZBgkqhkiG9w0BCQEWDGFs +ZXhAYXViLmRldjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALfcWIiK +J7HAt78/wNnHkeyoWWZQ7v+W8nud5wlU3Cp1ndrHmOlAHSnq7F+p46/Nw3eTlXtQ +Vv8Cb6eRI1kwBmGxbMJTZng+OHnRBjPd/Qei6vv/IgZK04vJhjeGAsrYrxNuGCNA ++F2TD35C0qWm9svx8uA40CgatU4WdFAZyvvSIV9+ybv2aBmEpUyBiIc8momAYe8K +2QaD3MyBLunrf5DdlZ520VLZGExfe4IHL+YfoQ6VEI0FtGYDjHE3PJ/kZBqSLdP4 +jP204yN08/4LBXYuoR3KWYG6KTy+t9NveogcsooEAjyVc3brBa0DeQ3gvs0/5xsq +UJGWpy2+GbKUsfUCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsF +AAOCAQEAD2FBwUO9iV4VBrpTdUEd0Y39ajJsdbbnNkJ/N1ZbJgdymCLfVpzCqB+s +qRjpq9JqGtcxwzPG7GGu+OIXCGMfeMzFkk2cjLeZic5uKcynFgru4bzrhGdb26Wq +s8rQeXFOO6aRdpTVvIO+Vq5goRTXJhbtEzA9efmYWIOXcL5WDYhwApOc8rwfz9fm +q8VZCW+KK23EU3gfyOcO14E0Al/K6lewX15K1Hh4P8cSnFtjtCqRRWmQi9JfdasS +A0YJd8rN47dplRylTtXu5VFKL+XXa/jmlzsgtxBHX14onpVKqfGCvCLqj+AeZA3Y +iX4iQjxnLr5DRtykOz+bKEYgX8AV8g== +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/rsa_cert.cnf b/test/fixtures/keys/rsa_cert.cnf new file mode 100644 index 0000000..83137f2 --- /dev/null +++ b/test/fixtures/keys/rsa_cert.cnf @@ -0,0 +1,22 @@ +[ req ] +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = UK +ST = Acknack Ltd +L = Rhys Jones +O = node.js +0.OU = Test TLS Certificate +1.OU = Engineering +CN = localhost +emailAddress = alex@aub.dev + +[ req_attributes ] + +[ v3_ca ] +basicConstraints = CA:TRUE + +[ x509_extensions ] diff --git a/test/fixtures/keys/rsa_cert.crt b/test/fixtures/keys/rsa_cert.crt new file mode 100644 index 0000000..21878a3 --- /dev/null +++ b/test/fixtures/keys/rsa_cert.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIUf4Z4DKj5kJW60NE+9PaucYFlft8wDQYJKoZIhvcNAQEL +BQAwgbAxCzAJBgNVBAYTAlVLMRQwEgYDVQQIDAtBY2tuYWNrIEx0ZDETMBEGA1UE +BwwKUmh5cyBKb25lczEQMA4GA1UECgwHbm9kZS5qczEdMBsGA1UECwwUVGVzdCBU +TFMgQ2VydGlmaWNhdGUxFDASBgNVBAsMC0VuZ2luZWVyaW5nMRIwEAYDVQQDDAls +b2NhbGhvc3QxGzAZBgkqhkiG9w0BCQEWDGFsZXhAYXViLmRldjAgFw0xOTA2Mjgy +MTM2NDhaGA8yMjkzMDQxMTIxMzY0OFowgbAxCzAJBgNVBAYTAlVLMRQwEgYDVQQI +DAtBY2tuYWNrIEx0ZDETMBEGA1UEBwwKUmh5cyBKb25lczEQMA4GA1UECgwHbm9k +ZS5qczEdMBsGA1UECwwUVGVzdCBUTFMgQ2VydGlmaWNhdGUxFDASBgNVBAsMC0Vu +Z2luZWVyaW5nMRIwEAYDVQQDDAlsb2NhbGhvc3QxGzAZBgkqhkiG9w0BCQEWDGFs +ZXhAYXViLmRldjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALfcWIiK +J7HAt78/wNnHkeyoWWZQ7v+W8nud5wlU3Cp1ndrHmOlAHSnq7F+p46/Nw3eTlXtQ +Vv8Cb6eRI1kwBmGxbMJTZng+OHnRBjPd/Qei6vv/IgZK04vJhjeGAsrYrxNuGCNA ++F2TD35C0qWm9svx8uA40CgatU4WdFAZyvvSIV9+ybv2aBmEpUyBiIc8momAYe8K +2QaD3MyBLunrf5DdlZ520VLZGExfe4IHL+YfoQ6VEI0FtGYDjHE3PJ/kZBqSLdP4 +jP204yN08/4LBXYuoR3KWYG6KTy+t9NveogcsooEAjyVc3brBa0DeQ3gvs0/5xsq +UJGWpy2+GbKUsfUCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsF +AAOCAQEAD2FBwUO9iV4VBrpTdUEd0Y39ajJsdbbnNkJ/N1ZbJgdymCLfVpzCqB+s +qRjpq9JqGtcxwzPG7GGu+OIXCGMfeMzFkk2cjLeZic5uKcynFgru4bzrhGdb26Wq +s8rQeXFOO6aRdpTVvIO+Vq5goRTXJhbtEzA9efmYWIOXcL5WDYhwApOc8rwfz9fm +q8VZCW+KK23EU3gfyOcO14E0Al/K6lewX15K1Hh4P8cSnFtjtCqRRWmQi9JfdasS +A0YJd8rN47dplRylTtXu5VFKL+XXa/jmlzsgtxBHX14onpVKqfGCvCLqj+AeZA3Y +iX4iQjxnLr5DRtykOz+bKEYgX8AV8g== +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/rsa_cert.pfx b/test/fixtures/keys/rsa_cert.pfx new file mode 100644 index 0000000..aef85e2 Binary files /dev/null and b/test/fixtures/keys/rsa_cert.pfx differ diff --git a/test/fixtures/keys/rsa_cert_foafssl_b.cnf b/test/fixtures/keys/rsa_cert_foafssl_b.cnf new file mode 100644 index 0000000..5e69db8 --- /dev/null +++ b/test/fixtures/keys/rsa_cert_foafssl_b.cnf @@ -0,0 +1,28 @@ +# The following 'foafssl' cert is used in test/parallel/test-https-foafssl.js. +# It requires a SAN like 'http://example.com/#me'. More info here: +# https://www.w3.org/wiki/Foaf+ssl + +[ req ] +days = 99999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = UK +ST = "FOAF+SSL Auth Certificate" +L = Rhys Jones +O = node.js +OU = Test TLS Certificate +CN = localhost +emailAddress = alex@aub.dev + +[ req_attributes ] + +[ v3_ca ] +basicConstraints = CA:FALSE +subjectAltName = @alt_names + +[ alt_names ] +URI = http://example.com/\#me diff --git a/test/fixtures/keys/rsa_cert_foafssl_b.crt b/test/fixtures/keys/rsa_cert_foafssl_b.crt new file mode 100644 index 0000000..475e35d --- /dev/null +++ b/test/fixtures/keys/rsa_cert_foafssl_b.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEEjCCAvqgAwIBAgIUCi0fIToueeHu6wSjX9cgjUKzgc8wDQYJKoZIhvcNAQEL +BQAwgagxCzAJBgNVBAYTAlVLMSIwIAYDVQQIDBlGT0FGK1NTTCBBdXRoIENlcnRp +ZmljYXRlMRMwEQYDVQQHDApSaHlzIEpvbmVzMRAwDgYDVQQKDAdub2RlLmpzMR0w +GwYDVQQLDBRUZXN0IFRMUyBDZXJ0aWZpY2F0ZTESMBAGA1UEAwwJbG9jYWxob3N0 +MRswGQYJKoZIhvcNAQkBFgxhbGV4QGF1Yi5kZXYwIBcNMTkwNjI4MjEyNzE0WhgP +MjI5MzA0MTEyMTI3MTRaMIGoMQswCQYDVQQGEwJVSzEiMCAGA1UECAwZRk9BRitT +U0wgQXV0aCBDZXJ0aWZpY2F0ZTETMBEGA1UEBwwKUmh5cyBKb25lczEQMA4GA1UE +CgwHbm9kZS5qczEdMBsGA1UECwwUVGVzdCBUTFMgQ2VydGlmaWNhdGUxEjAQBgNV +BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMYWxleEBhdWIuZGV2MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyb1grrN+29fxeeEbTaSEja6TKDTp +T/WXnqrFCS+h7IYcnDoAVwcsPU5FZeUPvLKMzi9NHSJ34LQCurqHgH8X+cw0YT3g +dYS/7qoQiXs+zKv615NcttD3xlQLceY+NwznoPXyyZwOeZqyU5Hiqbrqu6hdr6gQ +YogMNLn2NxBW2pGegd6+ZGMCX3+/BtMP/6tXmttYjY+yhN2SrGz5cKhWpcHiC6X+ +B7uCKoKZy+t2jUxYVKUwWr1ZuM8kpSnuVCcv1OoMGEimEHA7v/eaF/y+z/VdQ4Y8 +8GhTnVN4KbtgZ+o9PohjxLFU62VeTALixU5mPQKSgSICKfjev0FUUurF6wIDAQAB +ozAwLjAJBgNVHRMEAjAAMCEGA1UdEQQaMBiGFmh0dHA6Ly9leGFtcGxlLmNvbS8j +bWUwDQYJKoZIhvcNAQELBQADggEBAFBCx6BoYUdrjqgFDFAGTMqj6sG0nfZ5a8b4 +Im82kKKUeSMnK5nnI8mG14tcAZqph4OewC3mc1S3ufBnJKOLZnae+xyIQiM046ZI +b2g/6vk0C3lrvp7S+UZS3ueJNt6UewlubmTembs7t2T1MdDoBwmMuYRph4jcFHp+ +BFNGdoNSipkBSSrYRb55oTIBMOYNHNeSFHnOLVyZNeR4qaGBr4ae6r+Z4kkfqiTV +Z0j4ta/7mXrIEX3RarQEdTEwAqYz9LioKSXfP9eye8NTt2zinu4pM9JZ3UU08P7f +DT7VyxCDdegaGb0Uaqjs0cRU7JRYkv/YaK7V5KNdIpjHuG3SlF8= +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/rsa_cert_foafssl_b.exponent b/test/fixtures/keys/rsa_cert_foafssl_b.exponent new file mode 100644 index 0000000..ac0ecd6 --- /dev/null +++ b/test/fixtures/keys/rsa_cert_foafssl_b.exponent @@ -0,0 +1 @@ +0x10001 diff --git a/test/fixtures/keys/rsa_cert_foafssl_b.modulus b/test/fixtures/keys/rsa_cert_foafssl_b.modulus new file mode 100644 index 0000000..7f7f1f0 --- /dev/null +++ b/test/fixtures/keys/rsa_cert_foafssl_b.modulus @@ -0,0 +1 @@ +C9BD60AEB37EDBD7F179E11B4DA4848DAE932834E94FF5979EAAC5092FA1EC861C9C3A0057072C3D4E4565E50FBCB28CCE2F4D1D2277E0B402BABA87807F17F9CC34613DE07584BFEEAA10897B3ECCABFAD7935CB6D0F7C6540B71E63E370CE7A0F5F2C99C0E799AB25391E2A9BAEABBA85DAFA81062880C34B9F6371056DA919E81DEBE6463025F7FBF06D30FFFAB579ADB588D8FB284DD92AC6CF970A856A5C1E20BA5FE07BB822A8299CBEB768D4C5854A5305ABD59B8CF24A529EE54272FD4EA0C1848A610703BBFF79A17FCBECFF55D43863CF068539D537829BB6067EA3D3E8863C4B154EB655E4C02E2C54E663D029281220229F8DEBF415452EAC5EB diff --git a/test/fixtures/keys/rsa_private.pem b/test/fixtures/keys/rsa_private.pem new file mode 100644 index 0000000..215e5cc --- /dev/null +++ b/test/fixtures/keys/rsa_private.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAt9xYiIonscC3vz/A2ceR7KhZZlDu/5bye53nCVTcKnWd2seY +6UAdKersX6njr83Dd5OVe1BW/wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq+/8i +BkrTi8mGN4YCytivE24YI0D4XZMPfkLSpab2y/Hy4DjQKBq1ThZ0UBnK+9IhX37J +u/ZoGYSlTIGIhzyaiYBh7wrZBoPczIEu6et/kN2VnnbRUtkYTF97ggcv5h+hDpUQ +jQW0ZgOMcTc8n+RkGpIt0/iM/bTjI3Tz/gsFdi6hHcpZgbopPL630296iByyigQC +PJVzdusFrQN5DeC+zT/nGypQkZanLb4ZspSx9QIDAQABAoIBAQCS2erYu8gyoGPi +3E/zYgQ6ishFAZWzDWSFubwD5wSm4SSAzvViL/RbO6kqS25xR569DmLRiHzD17VI +mJMsNECUnPrqR2TL256OJZaXrNHh3I1lUwVhEzjeKMsL4/ys+d70XPXoiocVblVs +moDXEIGEqa48ywPvVE3Fngeuxrsq3/GCVBNiwtt0YjAOZxmKEh31UZdHO+YI+wNF +/Z8KQCPscN5HGlR0SIQOlqMANz49aKStrevdvjS1UcpabzDEkuK84g3saJhcpAhb +pGFmAf5GTjkkhE0rE1qDF15dSqrKGfCFtOjUeK17SIEN7E322ChmTReZ1hYGfoSV +cdFntUINAoGBAPFKL5QeJ6wZu8R/ru11wTG6sQA0Jub2hGccPXpbnPrT+3CACOLI +JTCLy/xTKW3dqRHj/wZEe+jUw88w7jwGb1BkWr4BI8tDvY9jQLP1jyuLWRfrxXbp +4Z0oeBBwBeCI/ZG7FIvdDTqWxn1aj3Tmh6s4ByqEdtwrrrJPcBUNl01fAoGBAMMR +3RGE/ca6X6xz6kgUD6TtHVhiiRJK1jm/u+q0n7i/MBkeDgTZkHYS7lPc0yIdtqaI +Plz5yzwHnAvuMrv8LSdkjwioig2yQa3tAij8kXxqs7wN5418DMV2s1OJBrPthYPs +bv4im2iI8V63JQS4ZMYQbckq8ABYccTpOnxXDy0rAoGBAKkvzHa+QjERhjB9GyoT +1FhLQIsVBmYSWrp1+cGO9V6HPxoeHJzvm+wTSf/uS/FmaINL6+j4Ii4a6gWgmJts +I6cqBtqNsAx5vjQJczf8KdxthBYa0sXTrsfktXNJKUXMqIgDtp9vazQ2vozs8AQX +FPAAhD3SzgkJdCBBRSTt97ZfAoGAWAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCD +dCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP/Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNm +bDXFPdG0G3hzQovx/9fajiRV4DWghLHeT9wzJfZabRRiI0VQR472300AVEeX4vgb +rDBn600CgYEAk7czBCT9rHn/PNwCa17hlTy88C4vXkwbz83Oa+aX5L4e5gw5lhcR +2ZuZHLb2r6oMt9rlD7EIDItSs+u21LOXWPTAlazdnpYUyw/CzogM/PN+qNwMRXn5 +uXFFhmlP2mVg2EdELTahXch8kWqHaCSX53yvqCtRKu/j76V31TfQZGM= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_private_1024.pem b/test/fixtures/keys/rsa_private_1024.pem new file mode 100644 index 0000000..a8a4830 --- /dev/null +++ b/test/fixtures/keys/rsa_private_1024.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDRMGk0Tp+sGoU4FbTF76l7g3uOdSpqLBnP4UDKSdhv3p+lfr6F +piJh8KnXIPKMq5b4EX+X4Ghz7PKDMEs24ihiqwUMl89exvFtW2bXwqq0XpMqdF6R +CxbbBYy3DFPTCTvoHaoAfmwHzwBfelHt2PaYl4ZNxhpD4UcP3/lrUB5KhwIDAQAB +AoGAYK4gIUWpNDB5m4ckqkpuqSAGbbum47UIJPR1Lkjc2C8q16DxSvGSeHNy+3NF +xk/TkUj9EGNtww4isxER4ga6JLH5WNkF3k92ET2wSn6G9/CtPZRe6wRP593ZO5vd +KXLCXRNprlRVHQbE6VysIQToZKVH2sqoFhIH62RDdhXvQNECQQDq8PQsLd1szeKf +iaJSlPxfuPG9yEZO7YY3QygpTmzRjLfN5560SejEvctMiO4hlwnWNbQzGbgQfhfA +JbgK6Z0fAkEA4/CKKcR8JJe/8vukfTLDlgnp3DPMa1HOVFnKWF6QeCPk5aOehDcf +CFm4E+A6NAbtG0oor6e4Bsr4ASOpLxE9mQJBAK4oAYiCU0JleFm1CAvZfx9iFGkP +ffbiIfzzHmFITmgjvNi4mq+gnhjBbGOGmadytAsDclny9bvcDLUWANCuDhcCQCNE +aFwmBn8y64QQ41ZrsE9aoVBsw0gnlCEA84nQt9Ge3B+bvT7/uFF2cEDDBL5gA/eg +9cKX1KVYah7jAZ5CsKECQQCJFqYwEewb3G7aKjtAzVCsNfQSC64xT2EnMg47va9t +GK5DXshigVNpMHJ52N4QxWIRtUs5LOCeGaZP/vTkTQ+z +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_private_2048.pem b/test/fixtures/keys/rsa_private_2048.pem new file mode 100644 index 0000000..0e5bde2 --- /dev/null +++ b/test/fixtures/keys/rsa_private_2048.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEArk4OqxBqU5/k0FoUDU7CpZpjz6YJEXUpyqeJmFRVZPMUv/Rc +7U4seLY+Qp6k26T/wlQ2WJWuyY+VJcbQNWLvjJWks5HWknwDuVs6sjuTM8CfHWn1 +960JkK5Ec2TjRhCQ1KJy+uc3GJLtWb4rWVgTbbaaC5fiR1/GeuJ8JH1Q50lB3mDs +NGIk1U5jhNaYY82hYvlbErf6Ft5njHK0BOM5OTvQ6BBv7c363WNG7tYlNw1J40du +p9OQPo5JmXN/h+sRbdgG8iUxrkRibuGv7loh52QQgq2snznuRMdKidRfUZjCDGgw +bgK23Q7n8VZ9Y10j8PIvPTLJ83PX4lOEA37JlwIDAQABAoIBACoL2Ev5lLyBaI+9 ++vJO2nNaL9OKSMu2SJODIJTnWwYUASBg0P3Jir6/r3sgi8IUJkH5UHbD/LrQcPkA +4X7PU9vEyUsr1efWFIvk7t7JsjOctoVA5z2Mty74arivUIe5PUadvUC6/7Zk0u6A +CjLuJRmlH7nGNKZk+xrvgWTH+fkgc5ddbFxoGH129RcVC+ePbsi1EF60C9KbJvp1 +xjUJ5cDtNYnZ/g+ULo6ZJjRG5kUCVSI8H/Nc/DmStKsjN0isKpNGofU5ArEwywGC +Cqxz/tr4hT2haAkVEto04ooYpqDUSqGEfPpLWL+CjFNPfCsWJ1tX5LQRvpu6eukd +FO72oVECgYEA4+Ot7RQtGOtPeaxl3P7sbEzBZk0W/ZhCk+mzE2iYh0QXU44VtjtO +/9CENajTklckiwlxjqBZ5NO1SiMQKBcmqkoA03x/DEujo2rMVuNPoc6ZYp1Gc4qA +4ImkMQNsM7Swum42rKE960WoiWW7dsdEAq6vqgeApZlMU8lcKRAlOZkCgYEAw85H +3bjF7gMatVibsWzj0zp2L4616m2v5Z3YkgohGRAvm1912DI5ku5Nmy9am57Z1EP2 +UtDOxahd/Vf6mK9lR4YEbNW1TenykViQJ6lmljOFUeZEZYYO3O+fthkyN/42l5yn +MyUANTTb2rvt8amdRr0ARdRqWJmt5NfJzYBV+q8CgYB1ZjuZoQVCiybcRcYMPX/K +oxgW/avUZPYXgRNx8jZxqNBjiRUCVjdybhdOFXU5NI9s2SaZFV56Fd6VHM8b+CFB +JPKcAMzqpqTccQ5nzJ6fevFl7iP3LekKw53EakD5uiI5SMH92OsvIymZ7sDOhgUx +ZJC2hTrvFLRPjbJerSSgMQKBgAv5iZuduT0dI30DtkHbjvNUF/ZAnA+CNcetJ5mG +1Q9bVg4CgIqAR9UcjdJ3yurJhDjfDylxa7Pa4CSmRMUhtOfy4kJlr3jcXeFVsTs7 +uPJmpDimBHjRAgew/+t7Dv8tpNkQ04jlMmYOnYN7CspEvUGePW4H15kjjOb563WN +67QxAoGAdhJPaHVtDBUrobn50ORVkVNJVZPBhEDbR6tNtHsgEevH7iYjxAdxEeUa +c9S2iV9lir3hkrQceiYWAADcphWfO1HyP5Uld4sJzYiEGbSM7a0zRQjYlQgXFbZo +SAc6Gok78kwECPwpmeH4lpGVeKNmzEteSBVYxGb9b6C/SSsu7l0= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_private_4096.pem b/test/fixtures/keys/rsa_private_4096.pem new file mode 100644 index 0000000..4177b9e --- /dev/null +++ b/test/fixtures/keys/rsa_private_4096.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAxeStwofbjtZuol4lwKn1w08AzcSNLHfCqNFHa+W7er8is7LQ +sPljtPT4yn4lsao83ngHFvSC3tbMiRNDpUHYqH2wBuUkuOmCtYkZLi0307H0CwcV +V6W5P3tNEt80IJ+PqlRxtTknezUtbOasbIi/aornVWG+psgqDGrFZ4oTsWtiE0Sv +i7sDqN5E2dijmH/YYnlnwqszgzHdtAnARp1bG34E64sqWCmLoGCfPdHtym/CSdxO +LOsDV15jrwODZQ/TJZ5thkwKZRxu7g9fwlhA1PiI5WDP4reXNaqa2bSgrzpAljQE +xYs4N0L7okSVOJQX9BEaoWtq8NLU8MpMdGoHNDU0Xr60Lfr58Z5qn8RGEvlTxoCb +PJzPV2zgzD/lmEqft6NnfTclveA3sd8xSrOBUn4o3S8hS0b9Su7PBukHjM96/e0R +eoIshSwXlQTLr2Ft8KwupyPm1ltNcTDtjqHcIWU6Bg+kPy9mxSVtGGZYAPtqGzNB +A/m+oOja/OSPxAblPdln691DaDuZs5nuZCGwGcLaJWgiyoqvXAcyXDZFyH4OZZh8 +rsBLKbnFXHZ/ziG0cAozEygZEPJappw8Lx/ady7WL/SJjxooiKapc7Bnfy8eSLV3 ++XAKxhLW/MQ6ChJ+e/8ExAY02ca4MpCvqwIk9TfV6FM8pWGqHzQFj0v3NL0CAwEA +AQKCAgBTb8eTbZS09NRQwUFJql9kqbq9B1I+nYAFjbd/Vq1lY5FOEubKt1vCwEbl +mapq7kwbwJ+8nftP2WEDqouq8chXwialwZdqH4ps4BEt1wLizvUGcUYeXlFs4p/s +hQ+FccExH8mRjzeGSzWL5PZuDHoogchnx36K83pHIf15Wk5TT+NaHGunjoJMgOqm +ryDK+5xQaL/G5Egj2LKRZksbet0fClMovNRtt5aXWCXL+uc3o0dXvPt5FN2jyLhe +4ixUQAfWpKWpKgZ3+zUKSpElb/Bl2yRdEiSUgrPOfNAtWmsldnok2mnooHpjUmqm +UCRaZpZy4YNI6/F6+Gmv3Ju/ubSvHzoxQLlvgUqWAnVshivF1TJImHSIiLIvBKPp +29SD6maWIT1DC9sKC4E1gq7VO4762l1//zEOAY7XK0Z7LrbZO4WXHnsgFOpGthQ3 +g9Qi/SeM6mb5xEJTBUBTmkhGs1x8jolzca30mqv8T63W4PXkXHmZdK7vyH5useiI +s0eGUeaYK892WgfxCBo24JCNQiAcH/wTwV4l4yROqeH2V4ShbIYmCzla++7vsPYW +hAwQR9eH0+4ogTkaMQrm16plZk0ezVX9BKK8KTnd4G9/T18VstQbiowF2/cKnGKC +OqrmoR2vHOksQdUJVmnwCRqU1symBxhY0GSIps98v+lUYExKQQKCAQEA/uVYE2/H +eNcV/uWAI9LspANXHJE33TFMZ8SuyOYtp3MYJizmQ1uT7Om2LEanDnNiz+fAQhrE +vo1sDIF9xOAde2qjIH+iDzcLvFPgC3gkQspFjU31M9OO5xAjzBxfL3KDiG2MtmTR +hNuKJX56eCOqkEp6WKaWOA35ccaKYHxNzMS49weCv95ZPpR9q0J1sgzD7HtVh4yu +XI01/BC8F0RmYjtsuUo+PmB6sO2K94uqqo0GPUos7Mhgrbff3L36EkOPgmRiA1AV +Zy1sKKxUKspGQ3m1fg+CA/+GZGckvYkVot1lFrwmrS2dok8EhT1HcVJde+++jx7z +JsRLgFRvKHXklwKCAQEAxsAfxIQjjjKmuyJCzIvxG7lnuzovdy4OEdSuJL4yK5m3 +4BHJHn+yHeRIcrDnJKUTUYffcH/OjOnJS94BA6wH1tEuvGQz6LV6UpwApZ1M/2md +nP0eC2L2JtSRL8mdxfyqXDloWMpD7rncBZ6ChLEZ6sWYa6WBQTARmPVePyUpNNG2 +qymxN3/vRBGGBunD3j6zX0M0szWK5iU+qsYDy3KzCKG8FU7XxwzRbP7iARRD5Hpt +Zmy2W52EJg1uhmlVXJMm32SEBfrD2oDmlnjAqaZdqi5Mq2e4uB3dhM9RwJppSALG +BY6k9DeanAFbOlawMJri2pk7B0phCn+DN2pg0+W3ywKCAQBeTwzfZCQxmaMRxGg8 +2PWlWXcJotFAjdTvL95bho6tve/ZcBNiKKf6qB43E40L07VjpyODUdQpjLnFhsO5 +7BH8b+AbTh3v8zXsYDwtAi6oZ56EQavPmR7ubxJPms+9BmmUOLQvZ+39ch0S8lDt +0oRxDp1l330FEGaSqhrYyCUg9khZXfYKd4IdnWNB0j0pu39iJ9/lXy/EHpsywB5X +nX8kKUh45fdRrPC4NauNG6fxomwEkUU99oWOwNGbIs87orOeUvXQs/i3TB8QjXI2 +wtBsdsOn+KTqRci7rU3ysp3GvJOCbesBeDcyrnnFsn6Udx0Plgyzd4gPd+FXgeX+ +2l/RAoIBAH81FKAY2xD2RlTb1tlIcGeIQWZKFXs4VPUApP0LZt0VI+UcPRdyL7SG +GgCeTTLdHQI/7rj4dGEoeRg/3XJWNyY8+KbHk5nMHaCmDJvzlAaduK10LDipfFba +Epr9dif0Ua15aNn7i4NOHg7Sp0L6f1YOZkHvykzI0VqPIWVVCYyu9TWUF8Mn9SIh +/SCLmjuy8ed1AlP5Xw9yoyt2VZNvtDtAGTuiHOVfxOL4N/rs149y9HZr+kOlC6G3 +Uxhgbqwz2tt8YCvblmNRwURpwRZUTvrPa28Bke713oRUlUSrD9txOwDvjZBpzmEv +VQ5/0YEqgSvcizVdW8L2XiunwJWfIAUCggEBALr4RF9TYa37CImZOs+vJ8FGRKMz +h1EUwO2PvuITvkTtu/7E4IjyxAo5dkAokkWQCGABciiDJJEYUWqcUX45qQChOgtm +NU2od6f9tgyDFxN5KS8cE32NXV3rJXs3bBZmIKLSPETf3uIPuEpFPjpdR5v5jlV+ +TDjH4RrItE3hDCvypTXhXXMmWp3VfYbgEfIP03uR2iIhL+/g3BUqbrywPEsTViSN +NM/uBDQyamXLXB1bQ2I/Ob41I82PD1iNCqGi7ZvZ3eVYGgUTQyw6Q4O8glTPP9cC +SFVXwE9gHbLe8TqfTZCWrM6crGX6Bb6hV2tqNsA+7J69U9NGuw5GNqXjafU= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_private_b.pem b/test/fixtures/keys/rsa_private_b.pem new file mode 100644 index 0000000..96d82ae --- /dev/null +++ b/test/fixtures/keys/rsa_private_b.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAyb1grrN+29fxeeEbTaSEja6TKDTpT/WXnqrFCS+h7IYcnDoA +VwcsPU5FZeUPvLKMzi9NHSJ34LQCurqHgH8X+cw0YT3gdYS/7qoQiXs+zKv615Nc +ttD3xlQLceY+NwznoPXyyZwOeZqyU5Hiqbrqu6hdr6gQYogMNLn2NxBW2pGegd6+ +ZGMCX3+/BtMP/6tXmttYjY+yhN2SrGz5cKhWpcHiC6X+B7uCKoKZy+t2jUxYVKUw +Wr1ZuM8kpSnuVCcv1OoMGEimEHA7v/eaF/y+z/VdQ4Y88GhTnVN4KbtgZ+o9Pohj +xLFU62VeTALixU5mPQKSgSICKfjev0FUUurF6wIDAQABAoIBAQCs11C/PM/iYNfl +mSSAWAStMrWni/Wc6QhXC2422ZV8hMZ8XwEtjtqrR6UTkLXz8HHMsR/7Zy2X2gJA +o1E2mS0ceoUiDxaA+RRL0W7Lq0j5qBsImZukkdLHG/iWRDJnjenhsParHsYUD6Lb +ELFGw/safjyOI4quMGtsvSqisKAJL5ZCPD5JHLhnNP8HXT6icSZrsqGhunb2tsa+ +Ogcx1+bZzqdTsbvXdbw07Lnd/LRU0NDhjeEVl4J2yFNYY+OIj7/qrxSnZnGLLG0Y +DFxiD+HCMvTBSooqvWI6FAipfyCGjUznGsVaRv7TuzHPuKE4LtbIC/Ac3Q10rKWq +PmHALir5AoGBAOhGUCToWfYnj2zH0GIZQxnkrv9iRqmdGeCDX6ZM00Bs5tASnRo0 +o90UtLbhWjHe1PKRKFyD4I7a8iIWxcWWun2XHgOtItctPN+lbjpTHTyE2yA1iZhe +dKCV3bAo4t+puKrPkZmaBqFD/fQx7DNxYdRERa1giiZGhlMUN3l7/S21AoGBAN5Y +nZ68NkTgklk4YBzsxwsMpQbgbihyG79gtDFxWonxZUQ29EsL01yd30pJNhg1LxDN +0fADfHVzkZ3qYz9knge9a75Yk8UBM3DM+xu+DRkjKhK5mPX5oLvj6061u3Scs6tj +orpU/mV1amz5gqrkefMaelsdHRuGGZQVx9KTV2kfAoGBAN7EAL1E8nK4Qj/r6xkK +bWZ6ArQABxFJELZYiPWvnLOfPka0c2PctIOmBiOXQa+urMDvIqyH9mhL6Al1mbwE +8VreAfU4qb+BLW649FyPteyC5r2fWxV9EZGp6fG3ZM9psShw5o1QQaeM1BTNhGFa +Dp9L0x+TBSvsW4t2SjYDCjA5AoGASzxxGWVWd7gFzWrmGuOD9pkwvkLzA3yZJwjx +8EkK+eJVAeAWic5WluBUzi43v7k/U9BRWYXUd2nDvEuziZ/iWXwfGSmf1umxHlo+ +HgURKZBcjDmBKLpvSSS2WsvjwnHD2hq81ZAtBOfWO0myjWECYuByxqHzV3zo6tLz +6q0wxsECgYA26twPrAoRqvfvPnNj6o0LrsE39Tj6jHIVijT7Lbcf2xVnaDiQ18PQ +RC6Tgkz5KZf8GKfMRMA3WopGn9QE2luI4RLIbhLozEDrkk2L7wSYqI9DZ1Hd26wf +v3+3jdpsXkzHwWYz1a2+FhCF5mJJRQl6kd/B0wu00vdfwviK9OVO7w== +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_private_encrypted.pem b/test/fixtures/keys/rsa_private_encrypted.pem new file mode 100644 index 0000000..f191428 --- /dev/null +++ b/test/fixtures/keys/rsa_private_encrypted.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,DB3D20E60E8FDC3356BD79712FF8EF7E + +K+vu0U3IFTJBBi6zW5Zng80O1jXq/ZmlOFs/j/SQpPwfW1Do9i/Dwa7ntBlTwrCm +sd3IIPgu2ikfLwxvbxsZN540oCaCqaZ/bmmyzH3MyVDA9MllUu+X8+Q3ATzcYa9R +U5XfF5DAXsSRnstCbmKagWVQpO0oX8k3ratfny6Ixq86Y82tK8+o5YiBFq1kqa+9 +4yat7IWQbqV5ifUtUPCHZwEqBt+WKazX05BqERjkckHdpfaDrBvSSPXTwoLm6uRR +ktkUVpO4tHMZ4VlcTfFtpz8gdYYod0nM6vz26hvbESHSwztSgMhmKdsE5eqmYfgu +F4WkEN4bqAiPjKK3jnUKPt/vg2oKYFQlVYFl9QnBjiRqcQTi3e9lwn1hI7uoMb6g +HuaCc57JJHPN/ZLP3ts4ZxFbwUjTGioh5Zh6WozG3L3+Ujwq/sDrAskRyzdcuP7I +Rs3oLbHY03OHyg8IbxR5Iu89l6FLqnR45yvbxXtZ7ImGOPM5Z9pB1CzDhGDx2F6g +J/Kf/7ZF2DmYUVbVKDfESEDhRfuMAVzhasDPTRqipSA5QvJVQY+J/6QDPrNNmHVB +4e4ouHIDWERUf0t1Be7THvP3X8OJozj2HApzqa5ZCaJDo8eaL8TCD5uH75ID5URJ +VscGHaUXT8/sxfHi1x8BibW5W5J/akFsnrnJU/1BZgGznIxjf5tKfHGppSIVdlKP +3ghYNmEIFPNJ6cxuUA0D2IOV4uO3FTCU6seIzvJhYkmXnticcZYGtmGxXKrodtzS +J1YuaNkkO/YRZah285lQ6QCIhCFo4Oa4ILjgoTQISuw7nQj5ESyncauzLUBXKX0c +XDUej64KNTvVF9UXdG48fYvNmSZWCnTye4UmPu17FmwpVra38U+EdoLyWyMIAI5t +rP6Hhgc9BxOo41Im9QpTcAPfKAknP8Rbm3ACJG5T9FKq/c29d1E//eFR6SL51e/a +yWdCgJN/FJOAX60+erPwoVoRFEttAeDPkklgFGdc8F4LIYAig9gEZ92ykFFz3fWz +jIcUVLrL+IokFbPVUBoMihqVyMQsWH+5Qq9wjxf6EDIf0BVtm9U4BJoOkPStFIfF +Kof7OVv7izyL8R/GIil9VQs9ftwkIUPeXx2Hw0bE3HJ3C8K4+mbLg3tKhGnBDU5Z +Xm5mLHoCRBa3ZRFWZtigX7POszdLAzftYo8o65Be4OtPS+tQAORk9gHsXATv7dDB +OGw61x5KA55LHVHhWaRvu3J8E7nhxw0q/HskyZhDC+Y+Xs6vmQSb4nO4ET4NYX1P +m3PMdgGoqRDJ2jZw4eoQdRKCM0EHSepSAYpO1tcAXhPZS4ITogoRgPpVgOebEQUL +nKNeNu/BxMSH/IH15jjDLF3TiEoguF9xdTaCxIBzE1SFpVO0u9m9vXpWdPThVgsb +VcEI487p7v9iImP3BYPT8ZYvytC26EH0hyOrwhahTvTb4vXghkLIyvPUg1lZHc6e +aPHb2AzYAHLnp/ehDQGKWrCOJ1JE2vBv8ZkLa+XZo7YASXBRZitPOMlvykEyzxmR +QAmNhKGvFmeM2mmHAp0aC03rgF3lxNsXQ1CyfEdq3UV9ReSnttq8gtrJfCwxV+wY +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_private_pkcs8.pem b/test/fixtures/keys/rsa_private_pkcs8.pem new file mode 100644 index 0000000..cd274ae --- /dev/null +++ b/test/fixtures/keys/rsa_private_pkcs8.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC33FiIiiexwLe/ +P8DZx5HsqFlmUO7/lvJ7necJVNwqdZ3ax5jpQB0p6uxfqeOvzcN3k5V7UFb/Am+n +kSNZMAZhsWzCU2Z4Pjh50QYz3f0Hour7/yIGStOLyYY3hgLK2K8TbhgjQPhdkw9+ +QtKlpvbL8fLgONAoGrVOFnRQGcr70iFffsm79mgZhKVMgYiHPJqJgGHvCtkGg9zM +gS7p63+Q3ZWedtFS2RhMX3uCBy/mH6EOlRCNBbRmA4xxNzyf5GQaki3T+Iz9tOMj +dPP+CwV2LqEdylmBuik8vrfTb3qIHLKKBAI8lXN26wWtA3kN4L7NP+cbKlCRlqct +vhmylLH1AgMBAAECggEBAJLZ6ti7yDKgY+LcT/NiBDqKyEUBlbMNZIW5vAPnBKbh +JIDO9WIv9Fs7qSpLbnFHnr0OYtGIfMPXtUiYkyw0QJSc+upHZMvbno4llpes0eHc +jWVTBWETON4oywvj/Kz53vRc9eiKhxVuVWyagNcQgYSprjzLA+9UTcWeB67Guyrf +8YJUE2LC23RiMA5nGYoSHfVRl0c75gj7A0X9nwpAI+xw3kcaVHRIhA6WowA3Pj1o +pK2t692+NLVRylpvMMSS4rziDexomFykCFukYWYB/kZOOSSETSsTWoMXXl1KqsoZ +8IW06NR4rXtIgQ3sTfbYKGZNF5nWFgZ+hJVx0We1Qg0CgYEA8UovlB4nrBm7xH+u +7XXBMbqxADQm5vaEZxw9eluc+tP7cIAI4sglMIvL/FMpbd2pEeP/BkR76NTDzzDu +PAZvUGRavgEjy0O9j2NAs/WPK4tZF+vFdunhnSh4EHAF4Ij9kbsUi90NOpbGfVqP +dOaHqzgHKoR23Cuusk9wFQ2XTV8CgYEAwxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrW +Ob+76rSfuL8wGR4OBNmQdhLuU9zTIh22pog+XPnLPAecC+4yu/wtJ2SPCKiKDbJB +re0CKPyRfGqzvA3njXwMxXazU4kGs+2Fg+xu/iKbaIjxXrclBLhkxhBtySrwAFhx +xOk6fFcPLSsCgYEAqS/Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc/Gh4c +nO+b7BNJ/+5L8WZog0vr6PgiLhrqBaCYm2wjpyoG2o2wDHm+NAlzN/wp3G2EFhrS +xdOux+S1c0kpRcyoiAO2n29rNDa+jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8CgYBY +DOIqnEsovsucvh3MNzHwkg8i7CdPGHSmUIN0J9/ItpPxYn2VdtccVOM6+3xZ8+uU +M/9iXGZ+TDkFsZk4/VUsaNmfYOQf1oyLA2ZsNcU90bQbeHNCi/H/19qOJFXgNaCE +sd5P3DMl9lptFGIjRVBHjvbfTQBUR5fi+BusMGfrTQKBgQCTtzMEJP2sef883AJr +XuGVPLzwLi9eTBvPzc5r5pfkvh7mDDmWFxHZm5kctvavqgy32uUPsQgMi1Kz67bU +s5dY9MCVrN2elhTLD8LOiAz8836o3AxFefm5cUWGaU/aZWDYR0QtNqFdyHyRaodo +JJfnfK+oK1Eq7+PvpXfVN9BkYw== +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_private_pkcs8_bad.pem b/test/fixtures/keys/rsa_private_pkcs8_bad.pem new file mode 100644 index 0000000..ca36972 --- /dev/null +++ b/test/fixtures/keys/rsa_private_pkcs8_bad.pem @@ -0,0 +1,28 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC33FiIiiexwLe/ +P8DZx5HsqFlmUO7/lvJ7necJVNwqdZ3ax5jpQB0p6uxfqeOvzcN3k5V7UFb/Am+n +kSNZMAZhsWzCU2Z4Pjh50QYz3f0Hour7/yIGStOLyYY3hgLK2K8TbhgjQPhdkw9+ +QtKlpvbL8fLgONAoGrVOFnRQGcr70iFffsm79mgZhKVMgYiHPJqJgGHvCtkGg9zM +gS7p63+Q3ZWedtFS2RhMX3uCBy/mH6EOlRCNBbRmA4xxNzyf5GQaki3T+Iz9tOMj +dPP+CwV2LqEdylmBuik8vrfTb3qIHLKKBAI8lXN26wWtA3kN4L7NP+cbKlCRlqct +vhmylLH1AgMBAAECggEBAJLZ6ti7yDKgY+LcT/NiBDqKyEUBlbMNZIW5vAPnBKbh +JIDO9WIv9Fs7qSpLbnFHnr0OYtGIfMPXtUiYkyw0QJSc+upHZMvbno4llpes0eHc +jWVTBWETON4oywvj/Kz53vRc9eiKhxVuVWyagNcQgYSprjzLA+9UTcWeB67Guyrf +8YJUE2LC23RiMA5nGYoSHfVRl0c75gj7A0X9nwpAI+xw3kcaVHRIhA6WowA3Pj1o +pK2t692+NLVRylpvMMSS4rziDexomFykCFukYWYB/kZOOSSETSsTWoMXXl1KqsoZ +8IW06NR4rXtIgQ3sTfbYKGZNF5nWFgZ+hJVx0We1Qg0CgYEA8UovlB4nrBm7xH+u +7XXBMbqxADQm5vaEZxw9eluc+tP7cIAI4sglMIvL/FMpbd2pEeP/BkR76NTDzzDu +PAZvUGRavgEjy0O9j2NAs/WPK4tZF+vFdunhnSh4EHAF4Ij9kbsUi90NOpbGfVqP +dOaHqzgHKoR23Cuusk9wFQ2XTV8CgYEAwxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrW +Ob+76rSfuL8wGR4OBNmQdhLuU9zTIh22pog+XPnLPAecC+4yu/wtJ2SPCKiKDbJB +re0CKPyRfGqzvA3njXwMxXazU4kGs+2Fg+xu/iKbaIjxXrclBLhkxhBtySrwAFhx +xOk6fFcPLSsCgYEAqS/Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc/Gh4c +nO+b7BNJ/+5L8WZog0vr6PgiLhrqBaCYm2wjpyoG2o2wDHm+NAlzN/wp3G2EFhrS +xdOux+S1c0kpRcyoiAO2n29rNDa+jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8CgYBY +DOIqnEsovsucvh3MNzHwkg8i7CdPGHSmUIN0J9/ItpPxYn2VdtccVOM6+3xZ8+uU +M/9iXGZ+TDkFsZk4/VUsaNmfYOQf1oyLA2ZsNcU90bQbeHNCi/H/19qOJFXgNaCE +sd5P3DMl9lptFGIjRVBHjvbfTQBUR5fi+BusMGfrTQKBgQCTtzMEJP2sef883AJr +XuGVPLzwLi9eTBvPzc5r5pfkvh7mDDmWFxHZm5kctvavqgy32uUPsQgMi1Kz67bU +s5dY9MCVrN2elhTLD8LOiAz8836o3AxFefm5cUWGaU/aZWDYR0QtNqFdyHyRaodo +JJfnfK+oK1Eq7+PvpXfVN9BkYw== +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_pss_private_2048.pem b/test/fixtures/keys/rsa_pss_private_2048.pem new file mode 100644 index 0000000..ffca137 --- /dev/null +++ b/test/fixtures/keys/rsa_pss_private_2048.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADALBgkqhkiG9w0BAQoEggSoMIIEpAIBAAKCAQEAy4OMdS84PlgI5CRL +bdbud9Ru7vprFr2YNNUmdT7D3YgApiv8CjzKXLiVDnbMET+lwmtag/EcZsxVCKov +su30pYASBriHOiMVYui9+ZaJoQ9yI6lOjG1RbuUBJXNSjHBJxqBqmcgZOb1mdRr/ +eXzpAMWJ3hfuLojU2+zUSJ3/rvepepcLFG2q9nA0+PJskJ7Pnh3L0ydnv3U3hduM +n5OVfm/Jx1FPyZpD184tJff+N+MY3s3hIcfuOnL9Pl4RPGeaTC4T1o460NaG6bG7 +c2Whg6NOaVgaFIaiNbrTTNCpVjeTyalsTXYlQQ3hiKjst0Q7pfFEkJDo8qiqLad1 +Msl59wIDAQABAoIBAQC6G8aqs0/f02nuGDLSc6cH9kCsUlz0ItW6GuJcfdVoFSNi +0v5d7lGwkSveWk0ryOSw8rOHzUqHx3xLvDZ6jpkXcBMMClu/kq3QEb8JK90YaKOc +cQvf52h83Pc7ZEatH1KYTcKudwp6fvXfSZ0vYEdD6WG2tHOgIollxSIsdjCHs1qi +7baNHdK9T4DveuEZNcZ+LraZ1haHmFeqIPcy+KvpGuTaLCg5FPhH2jsIkw9apr7i +iFLi+IJ7S5Bn/8XShmJWk4hPyx0jtIkC5r2iJnHf4x+XYWZfdo7oew3Dg6Pa7T6r +I164Nnaw0u0LvO4gQdvYaJ/j9A602nHTp7Tsq8chAoGBAOtVHgIqpmdzwR5KjotW +LuGXDdO9X6Xfge9ca2MlWH1jOj+zqEV7JtrjnZAzzOgP2kgqzpIR71Njs8wkaxTJ +Tle0Ke6R/ghU9YOQgRByKjqJfQXHZnYFPsMg0diNYLroJ4SG8LO4+2SygTYZ4eKL +qU0bda3QvQ7FL+rTNQBy01b9AoGBAN1jEQI80JxCT7AMvXE6nObIhbkASHte4yOE +1CBwcOuBEGcuvMOvQVMzKITgea6+kgsu4ids4dM5PTPapQgpKqIIQ2/eSesaf56g +73clGGSTPHJP0v+EfKg4+GYJf8o2swT0xDHkgWLgjjdsncB9hATc2j6DvHeau18d +pgCLz9kDAoGAXl/SGvhTp2UqayVnKMW1I07agrGNLA4II5+iiS4u4InskCNSNhr/ +KATj6TJ82AuTdCGGmdmLapuvPQzVzI42VsGvlzcA8wJvOwW2XIwMF1GPy8N9eZL8 +6m+89+Uqh4oWXvVmjgx+9JEJdFLI3Xs4t+1tMfll+AhoAPoWZUmnK1kCgYAvEBxR +iXQfg8lE97BeHcO1G/OxfGnsMCPBLT+bFcwrhGhkRv9B6kPM2BdJCB9WEpUhY3oY +P4FSUdy85UIoFfhGMdOEOJEmNZ/jrPq7LVueJd63vlhwkU2exV2o82QDLNWpvA7p +PFZ1Gp+hEKoIfaZPElQi7gZmtrIWaksb2pz42QKBgQCct9NP2qJfqeS4206RDnfv +M238/O2lNhLWdSwY0g+tcN+I1sGs3+4vvrm95cxwAmXZyIM11wjdMcAPNxibodY7 +vufsebPHDBA0j0yuTjGkXefUKd1GdO88i5fppzxB7prDX9//DsWWrFhIMMRNYe0Q +aeHd/NPuHcjZKcnaVBgukQ== +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_pss_private_2048_sha1_sha1_20.pem b/test/fixtures/keys/rsa_pss_private_2048_sha1_sha1_20.pem new file mode 100644 index 0000000..6b92715 --- /dev/null +++ b/test/fixtures/keys/rsa_pss_private_2048_sha1_sha1_20.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQowAASCBKcwggSjAgEAAoIBAQCpdutzsPFQ1100 +ouR5aAwYry8aAtG0c+zX9UqNXGCpRDWzPPpXHUZSB1BmTTL4EhK2tkAfblYNqzRu +CAYlKHbFpFLs2zLEorfp0WsFNPaBHE9JHpLIM4oXxPCUypZ7JAn56ZYonYCZ8Il5 +8SzD9aoF41RTEmpcx3XkL2RQa022RiSccYZKx/yzskUUAdTvTvYyujH1MkvsfVP+ +Ns5bRL6IVqowFd3xv6ctvfQMxz0rltgTC+wOm3CFtn+G63y6P/Z0U2DRdacsNkN6 +PFGXAIB0kSvKzs8gVocEBiSwMkcT/KD3R68PY18b2auqaGcm8gA+gaVJ36KAW4dO +AjbY+YitAgMBAAECggEAfPvfFXln0Ra1gE+vMDdjzITPuWBg57Uj9fbMIEwEYnKT +JHmRrNRDe9Y3HuxK7hjuQmFSE5xdzUD6rzgtyBP63TOfkV7tJ4dXGxS/2JxCPeDy +PNxWp18Ttwoh4as0pudikDYN8DCRm3eC/TO5r2EtH6CVHZuUZI8bTMsDMiihrQ8F +B8+KucBG5DDy/OlDeieAZxZA4Y0/c+W0DNZ/LIPGwaqMzYCSZJXyV0t33HytUwM2 +QZ+RbWqcUcrCI3lFAO8IyEULCi+RnSByZeJ0xwUkdQTI5jT6+G8BrO70Oiab8g+Q +Rx2s7PxWpIMVS7/JD1PsL4hLrVh3uqh8PZl3/FG9IQKBgQDZWkOR2LA+ixmD6XJb +Q+7zW2guHnK6wDrQFKmBGLaDdAER64WL1Unt6Umu7FPxth2niYMEgRexBgnj5hQN +LfPYTiIeXs5ErrU96fVQABsV0Hra1M2Rhve5nynjFFpbHjDXtizzLpE30MsC7YkN +EqD4YYzjWHrbk/UlQ7tx3eAvtQKBgQDHmNM4TRuyH2yaYxDqnho6fgJv7Z4KgbM0 +1wcUxi5kPDQsFtaVOzFhNserzsWvotQjLkC2+CK5qlCdm59ZlpUqszF6+YyUs5Gq +WmHdqryduT1VxSV/pd6wGEQo27fxFV7LsT1JhVMh9Iri8MK0b1BD6+kVUf5NcKDB +Od2o8A1gGQKBgA5Y3Pj1mrymJesFL91CYLWDpR7WN7CIG9m8Y2v4G6QVtjRenZQb +YiPoMErxoqDj6pUyiIl1lADFa0W13ED6dYwjrDDhBTCXb7NEjELZnvATsOhc/6zJ +gfSowvUQVN6K4aJ7jgAHZOKQT7ZDw7YvMpzyo4AmSQXRgG8TR34+rRu5AoGACApP +9+SjSPmbFl0HQWw9Aj4xOvEHfMTcwzQmRN/23nLOZzhETJ6lzpS2VmVt8TVN9lzW +nohAXdpOhQrP0HwQZjfxtlJ3J0ZUh9g8OQG3t2LO5bWbXRkBb3aKyFqRflSuDOaG +4X9NagC/14R7U2loglPuf71d0SDIWQBLvZJt94ECgYEAnY7aKHnWdLszcB8uyEkJ +EJkUEaa+K/nTqOzqffZ01cTWJmUG7a2KuvQ+UQM2BHk2+wBmUo45Iz/dyePOJY0B +Fu2agiV4+R4z2XVQnIvXgY5HaPxvLz0THksY/pD58gBmFaLMx4ADEwQ+s4Y2g12H +ABsKNRHfSnKTwOm/dYvcVqs= +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_pss_private_2048_sha256_sha256_16.pem b/test/fixtures/keys/rsa_pss_private_2048_sha256_sha256_16.pem new file mode 100644 index 0000000..ea45970 --- /dev/null +++ b/test/fixtures/keys/rsa_pss_private_2048_sha256_sha256_16.pem @@ -0,0 +1,29 @@ +-----BEGIN PRIVATE KEY----- +MIIE7wIBADA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3 +DQEBCDALBglghkgBZQMEAgGiAwIBEASCBKkwggSlAgEAAoIBAQDfqNM4C+QtD73i +ILqOkqfV8ha3O19jpX8UujIk1Z72bbbuwEzh0+sBw0dD0N8CgkXnePOEEd6q7HNm +byCNqRpDK6NDvaCMDWgEaD/PlHkRntvKh81IXSMC5imjRfOcZIE/Gnw7h8tanab0 +n75+ODvLJrmEWUG2q79Im1mWMx7Spod+Np6XEY+7I7nAUUWivr35Yx5DeyxY8rxF +GpsLtGsi7JNQO4aHyeBpj8tz0Fhv23uPywE2nGmPHfnkXWbrTcHGbzYBgEbeSH9K +UkRwczqDXNOPhtfaEHEFTm0MoeKCnJe1VOjSywev77dV1KZfpVh3Kh0ZRQIe9YOV +Jhj4lMx3AgMBAAECggEBAIc+IgK5Bg/NfgeXvNdrjPuM+PlxeHvb3h1dfebSGd5v +d3elZpgDug6F07kJO2Db/4M5mx7YY2m9swZU2j1u7MeDQqU6rDMkBCruEu/lmtPx +2Hv+ZD6Gux4MqU7mhKmkCJds34Rr16aCwCsZ0WmnfViZoQKLqnXYIsG31pNBdDjx +gke0HhX1LkA9yTVwlk8xOaHPqI4KfsFAyoiiHzyttGDexzb1PzmM0pybAPDMhpN/ +wXp2kLjyzmUmPe2Y2yva69WVWo7qS6joKjY75MQ1t20HYgEL69IApvCPu4CANfi9 +j3FAaV/+WrnhKCi6QyUi5PCI/+AJLsjNQmqTXIdBEoECgYEA+XsgFbeZ6+ZzEHa7 +HyFH6kiyBLd0q7w+ZLPsoOmEApDaP3yXSC7eJU7M/tPUPj8VQMMSK2D6fgmUDwhb +3mEXFZxf67UlPFsFjweYcBihwy4r8QKBwury6dEbHPSUq4mXFJF5XRQdGqRGkr/F +8OLZ0MwmHLUzczA67PxP/wF8TsECgYEA5YD4RxxJJYfAk1rFbqHRhNB8UeYVL+Wo +wsRET1JeFg+S5grLGUga+KBB8Jn7Ahaip7MVE0Iud1cgaDi4WBEJsbJ6oJTlHJEg +Jq7QAaBafwjeSCSnaEsVBVNvriy2WF7uAomLSKmW6uSUOBBFFt4+G+akG56EfOPc +7YKBfsf5ITcCgYBvjVZzX307tfeNTQmuibsWTxsKcN2CTNG5RZpw+PlGDG8KJDOg +2xQJqoqPBzjH/H0MUC03qE1ZPf8uGZa6gL9JsnpRctYLfselhMfsl5b9JxAO3AgZ +l+S2GAH/mH1BlmwvjjyuGehJmVrVE1r2sviiHCaOf5dZ0h8HCGrco1VqAQKBgQCf +fYkMofOTSUvjG2mpAHuCOQCsSaDfsFIfSBXQqgUIf7ouc8HAyAM2VOh+NAPj56cR +s7opsAxqkvnKc+BoEy8Rdl8RyWeO+qvFNicHelBph9gxeod8SvFIyjsKZ7gwoYf1 +63AIBxMCGeeHLodU5Q10hkv1hau8vv2BcPhdCstu8QKBgQDgO4Rr36Pa5rjbNbGN +PsJqALjthTeU0yaU8hdC7Hc7B/T6npEIhw3s1eNq7e0eeDltqz7tDnY8qysazbQZ +p1cV5TJ8+gtwGmDoADBnU1NXqX4Squfml6OhNEucpTdjux7JdLVmmQFraOT2Eu5f +9uuNtA+d8uhBEXhskuvEC552ug== +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_pss_private_2048_sha512_sha256_20.pem b/test/fixtures/keys/rsa_pss_private_2048_sha512_sha256_20.pem new file mode 100644 index 0000000..f8e8989 --- /dev/null +++ b/test/fixtures/keys/rsa_pss_private_2048_sha512_sha256_20.pem @@ -0,0 +1,29 @@ +-----BEGIN PRIVATE KEY----- +MIIE5wIBADA4BgkqhkiG9w0BAQowK6ANMAsGCWCGSAFlAwQCA6EaMBgGCSqGSIb3 +DQEBCDALBglghkgBZQMEAgEEggSmMIIEogIBAAKCAQEAvM9NgMCDqy5dqj5Ua2cZ +cc4zVr+fCF34bZn63OBeYG8RTJKM3j36lO/yamtfDctDhb87b45CS6ipEr8J57I9 +WF55TNngsn6GNpXgwAe0eFXUnKonuqnGEC7x8r3vkAg99PBKhAtFc5oTaaZDAFKM +zc5dIC/J0Y+kxhqjCPNI0ydQgZmKBrmYjM9cvbOYgRQL10GrWeJ+XHk2E33endaF ++4dwjgyrzInt/l6OTkiCL8F59J/htk1GPru9BT6w5yOS/vH6q6FD6uizULVznytd +lHjgnrVaHJmsqVjrYQa9OAZj9GBrTelBWvQ9b6+FBHUFHBp8HSp82lWCZThPrcZ/ +QwIDAQABAoIBADDzUfWic8CKuc/sbviVdzxRKHBCJ9oEeub3d9mR9gXsZcDDcfAg +g3nfp6q9gZxS6YOga6llaXyyEnuAufGu/UaO38Xz6tR8BxHZ07YViU11ezTOzJQR +df82HJZBdf2SlXWOYtNPFMd+16+ZYl+QB19INE6m9Rz2r9KIj2I/qM7NPPVhDRF0 +G4O0Yf2vaPhjoIaewn7xtQ6wmX7pAGcd8dmYEIGGkBi8aY3BVwrRK1X4AmD+oSmE +wXqR6MQIzD4KdypL4UD1Cb4GoFeVRclXvegOG+EKl0iD+mjTodB4yjoJh98NYe3+ +GtpR/2u3Ltq8RqWpIg8ryShQk/MIqGJ5KpkCgYEA80uNEYWlBt6QGNvVIYrhnw+V +2nLJWedioKV4H1sr9OLF/7WFOUfsaflpVybnmwfNV15lEyHb/m/sCM9jTrNk1t/q +qhRnvtmy3kntxWBeoA2o0TRg/XZKWjabZsr/4UE/Ztws2opOvl9x05IYeZlU+PbZ +B1lX2e+vtMOpllvRr28CgYEAxqtfrYv8brp/fAUqGu/MtdHxQdU1+vE+auN17gam +ZM6ojIeasX4k2z0Rd/+8Dga9wPgO7hjtFZ2NWD5UwfBiw5U2PVZ6bp3iKSBPGHEh +RsIR+nw8pFIgsQKoYnVK58tEnfQ31GSupKpYybHbaL5SdId+mfXU86SbKX/MefZX +g20CgYBjn8hAKI2O5ovy4fHALnJ9A5DFRsOUgN8uERPDIz44pLOXJelLr1vreSnd +ehzUqrk20Xxp/S9sXMA2S1XK4EKmikI5KungiJxp0bP/Yprcxzsdj2k34LxJfJrd +2Lo2rtUbdYUYaBIeek7N58EF6feVit8L11XV9APq7UQAQdD3GQKBgBmxuGIdpLw9 +apeDo3pwYS1yxZ0aEi0uXkA8wtfSDFslTy89qogiJGomb8fxT0URIiF+849fseoF +wm4TQasDiAJ7ndQ5BwSfbsya3R/wIbmhB+o5fy5RYOED0vtI6DMqWumC2GWjz+KE +FY+gbRwS4V8o1vrajHwmYdrwKGXtskvRAoGADe/EdlCj98r6Zle9giF1C0GDIDTx +8bR2m+Ogh9ZO218GpDMNaQ/WmbYVPDYMbRSOEA7y2gVkd1OeYhJMF6aYffp3aWhN +t9g0uojLY0jfEpWBLBQdlYOTl7hOnLWRRcOAKTlHVg+OuD/O/GmdQ2Rg2H/hAWlI +muuTQPuQTCV1aTc= +-----END PRIVATE KEY----- diff --git a/test/fixtures/keys/rsa_pss_public_2048.pem b/test/fixtures/keys/rsa_pss_public_2048.pem new file mode 100644 index 0000000..ade38f2 --- /dev/null +++ b/test/fixtures/keys/rsa_pss_public_2048.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIDALBgkqhkiG9w0BAQoDggEPADCCAQoCggEBAMuDjHUvOD5YCOQkS23W7nfU +bu76axa9mDTVJnU+w92IAKYr/Ao8yly4lQ52zBE/pcJrWoPxHGbMVQiqL7Lt9KWA +Ega4hzojFWLovfmWiaEPciOpToxtUW7lASVzUoxwScagapnIGTm9ZnUa/3l86QDF +id4X7i6I1Nvs1Eid/673qXqXCxRtqvZwNPjybJCez54dy9MnZ791N4XbjJ+TlX5v +ycdRT8maQ9fOLSX3/jfjGN7N4SHH7jpy/T5eETxnmkwuE9aOOtDWhumxu3NloYOj +TmlYGhSGojW600zQqVY3k8mpbE12JUEN4Yio7LdEO6XxRJCQ6PKoqi2ndTLJefcC +AwEAAQ== +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/rsa_pss_public_2048_sha1_sha1_20.pem b/test/fixtures/keys/rsa_pss_public_2048_sha1_sha1_20.pem new file mode 100644 index 0000000..68231d6 --- /dev/null +++ b/test/fixtures/keys/rsa_pss_public_2048_sha1_sha1_20.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQowAAOCAQ8AMIIBCgKCAQEAqXbrc7DxUNddNKLkeWgM +GK8vGgLRtHPs1/VKjVxgqUQ1szz6Vx1GUgdQZk0y+BIStrZAH25WDas0bggGJSh2 +xaRS7NsyxKK36dFrBTT2gRxPSR6SyDOKF8TwlMqWeyQJ+emWKJ2AmfCJefEsw/Wq +BeNUUxJqXMd15C9kUGtNtkYknHGGSsf8s7JFFAHU7072Mrox9TJL7H1T/jbOW0S+ +iFaqMBXd8b+nLb30DMc9K5bYEwvsDptwhbZ/hut8uj/2dFNg0XWnLDZDejxRlwCA +dJErys7PIFaHBAYksDJHE/yg90evD2NfG9mrqmhnJvIAPoGlSd+igFuHTgI22PmI +rQIDAQAB +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/rsa_pss_public_2048_sha256_sha256_16.pem b/test/fixtures/keys/rsa_pss_public_2048_sha256_sha256_16.pem new file mode 100644 index 0000000..9f8e15b --- /dev/null +++ b/test/fixtures/keys/rsa_pss_public_2048_sha256_sha256_16.pem @@ -0,0 +1,10 @@ +-----BEGIN PUBLIC KEY----- +MIIBUjA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3DQEB +CDALBglghkgBZQMEAgGiAwIBEAOCAQ8AMIIBCgKCAQEA36jTOAvkLQ+94iC6jpKn +1fIWtztfY6V/FLoyJNWe9m227sBM4dPrAcNHQ9DfAoJF53jzhBHequxzZm8gjaka +QyujQ72gjA1oBGg/z5R5EZ7byofNSF0jAuYpo0XznGSBPxp8O4fLWp2m9J++fjg7 +yya5hFlBtqu/SJtZljMe0qaHfjaelxGPuyO5wFFFor69+WMeQ3ssWPK8RRqbC7Rr +IuyTUDuGh8ngaY/Lc9BYb9t7j8sBNpxpjx355F1m603Bxm82AYBG3kh/SlJEcHM6 +g1zTj4bX2hBxBU5tDKHigpyXtVTo0ssHr++3VdSmX6VYdyodGUUCHvWDlSYY+JTM +dwIDAQAB +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/rsa_pss_public_2048_sha512_sha256_20.pem b/test/fixtures/keys/rsa_pss_public_2048_sha512_sha256_20.pem new file mode 100644 index 0000000..9ace7d6 --- /dev/null +++ b/test/fixtures/keys/rsa_pss_public_2048_sha512_sha256_20.pem @@ -0,0 +1,10 @@ +-----BEGIN PUBLIC KEY----- +MIIBTTA4BgkqhkiG9w0BAQowK6ANMAsGCWCGSAFlAwQCA6EaMBgGCSqGSIb3DQEB +CDALBglghkgBZQMEAgEDggEPADCCAQoCggEBALzPTYDAg6suXao+VGtnGXHOM1a/ +nwhd+G2Z+tzgXmBvEUySjN49+pTv8mprXw3LQ4W/O2+OQkuoqRK/CeeyPVheeUzZ +4LJ+hjaV4MAHtHhV1JyqJ7qpxhAu8fK975AIPfTwSoQLRXOaE2mmQwBSjM3OXSAv +ydGPpMYaowjzSNMnUIGZiga5mIzPXL2zmIEUC9dBq1niflx5NhN93p3WhfuHcI4M +q8yJ7f5ejk5Igi/BefSf4bZNRj67vQU+sOcjkv7x+quhQ+ros1C1c58rXZR44J61 +WhyZrKlY62EGvTgGY/Rga03pQVr0PW+vhQR1BRwafB0qfNpVgmU4T63Gf0MCAwEA +AQ== +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/rsa_public.pem b/test/fixtures/keys/rsa_public.pem new file mode 100644 index 0000000..8c30cfa --- /dev/null +++ b/test/fixtures/keys/rsa_public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt9xYiIonscC3vz/A2ceR +7KhZZlDu/5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe1BW/wJvp5EjWTAG +YbFswlNmeD44edEGM939B6Lq+/8iBkrTi8mGN4YCytivE24YI0D4XZMPfkLSpab2 +y/Hy4DjQKBq1ThZ0UBnK+9IhX37Ju/ZoGYSlTIGIhzyaiYBh7wrZBoPczIEu6et/ +kN2VnnbRUtkYTF97ggcv5h+hDpUQjQW0ZgOMcTc8n+RkGpIt0/iM/bTjI3Tz/gsF +di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC+zT/nGypQkZanLb4ZspSx +9QIDAQAB +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/rsa_public_1024.pem b/test/fixtures/keys/rsa_public_1024.pem new file mode 100644 index 0000000..f5c71a8 --- /dev/null +++ b/test/fixtures/keys/rsa_public_1024.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRMGk0Tp+sGoU4FbTF76l7g3uO +dSpqLBnP4UDKSdhv3p+lfr6FpiJh8KnXIPKMq5b4EX+X4Ghz7PKDMEs24ihiqwUM +l89exvFtW2bXwqq0XpMqdF6RCxbbBYy3DFPTCTvoHaoAfmwHzwBfelHt2PaYl4ZN +xhpD4UcP3/lrUB5KhwIDAQAB +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/rsa_public_2048.pem b/test/fixtures/keys/rsa_public_2048.pem new file mode 100644 index 0000000..0c80ceb --- /dev/null +++ b/test/fixtures/keys/rsa_public_2048.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArk4OqxBqU5/k0FoUDU7C +pZpjz6YJEXUpyqeJmFRVZPMUv/Rc7U4seLY+Qp6k26T/wlQ2WJWuyY+VJcbQNWLv +jJWks5HWknwDuVs6sjuTM8CfHWn1960JkK5Ec2TjRhCQ1KJy+uc3GJLtWb4rWVgT +bbaaC5fiR1/GeuJ8JH1Q50lB3mDsNGIk1U5jhNaYY82hYvlbErf6Ft5njHK0BOM5 +OTvQ6BBv7c363WNG7tYlNw1J40dup9OQPo5JmXN/h+sRbdgG8iUxrkRibuGv7loh +52QQgq2snznuRMdKidRfUZjCDGgwbgK23Q7n8VZ9Y10j8PIvPTLJ83PX4lOEA37J +lwIDAQAB +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/rsa_public_4096.pem b/test/fixtures/keys/rsa_public_4096.pem new file mode 100644 index 0000000..4fd0bbc --- /dev/null +++ b/test/fixtures/keys/rsa_public_4096.pem @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeStwofbjtZuol4lwKn1 +w08AzcSNLHfCqNFHa+W7er8is7LQsPljtPT4yn4lsao83ngHFvSC3tbMiRNDpUHY +qH2wBuUkuOmCtYkZLi0307H0CwcVV6W5P3tNEt80IJ+PqlRxtTknezUtbOasbIi/ +aornVWG+psgqDGrFZ4oTsWtiE0Svi7sDqN5E2dijmH/YYnlnwqszgzHdtAnARp1b +G34E64sqWCmLoGCfPdHtym/CSdxOLOsDV15jrwODZQ/TJZ5thkwKZRxu7g9fwlhA +1PiI5WDP4reXNaqa2bSgrzpAljQExYs4N0L7okSVOJQX9BEaoWtq8NLU8MpMdGoH +NDU0Xr60Lfr58Z5qn8RGEvlTxoCbPJzPV2zgzD/lmEqft6NnfTclveA3sd8xSrOB +Un4o3S8hS0b9Su7PBukHjM96/e0ReoIshSwXlQTLr2Ft8KwupyPm1ltNcTDtjqHc +IWU6Bg+kPy9mxSVtGGZYAPtqGzNBA/m+oOja/OSPxAblPdln691DaDuZs5nuZCGw +GcLaJWgiyoqvXAcyXDZFyH4OZZh8rsBLKbnFXHZ/ziG0cAozEygZEPJappw8Lx/a +dy7WL/SJjxooiKapc7Bnfy8eSLV3+XAKxhLW/MQ6ChJ+e/8ExAY02ca4MpCvqwIk +9TfV6FM8pWGqHzQFj0v3NL0CAwEAAQ== +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/rsa_public_b.pem b/test/fixtures/keys/rsa_public_b.pem new file mode 100644 index 0000000..bce0804 --- /dev/null +++ b/test/fixtures/keys/rsa_public_b.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyb1grrN+29fxeeEbTaSE +ja6TKDTpT/WXnqrFCS+h7IYcnDoAVwcsPU5FZeUPvLKMzi9NHSJ34LQCurqHgH8X ++cw0YT3gdYS/7qoQiXs+zKv615NcttD3xlQLceY+NwznoPXyyZwOeZqyU5Hiqbrq +u6hdr6gQYogMNLn2NxBW2pGegd6+ZGMCX3+/BtMP/6tXmttYjY+yhN2SrGz5cKhW +pcHiC6X+B7uCKoKZy+t2jUxYVKUwWr1ZuM8kpSnuVCcv1OoMGEimEHA7v/eaF/y+ +z/VdQ4Y88GhTnVN4KbtgZ+o9PohjxLFU62VeTALixU5mPQKSgSICKfjev0FUUurF +6wIDAQAB +-----END PUBLIC KEY----- diff --git a/test/fixtures/keys/rsa_public_sha1_signature_signedby_rsa_private.sha1 b/test/fixtures/keys/rsa_public_sha1_signature_signedby_rsa_private.sha1 new file mode 100644 index 0000000..5743580 --- /dev/null +++ b/test/fixtures/keys/rsa_public_sha1_signature_signedby_rsa_private.sha1 @@ -0,0 +1,2 @@ +yfF4}x=e Z'`a%Z>Ytmxd4Ec(9ȴ kL2+qSi2:[dfj/5П9O<8'. OeL82tӟ/3K,ЊlZڅt[4f#* Y 餡w/2ߗfꢅXhaYtmxd4Ec(9ȴ kL2+qSi2:[dfj/5П9O<8'. OeL82tӟ/3K,ЊlZڅt[4f#* Y 餡w/2ߗfꢅXha { + ctx.put_args(vec![file_path.clone()]); + ctx.eval_module_str(code, &file_path); + } + Err(e) => { + eprintln!("{}", e.to_string()); + assert!(false, "run js test file fail"); + } + } + JsValue::UnDefined + })) + .await; + rt.async_run_with_context(Box::new(|ctx| { + log::trace!("try _onExit"); + if let JsValue::Function(func) = ctx.get_global().get("_onExit") { + func.call(&[]); + }; + JsValue::UnDefined + })) + .await; + rt.async_run_with_context(Box::new(|ctx| { + log::trace!("try commonExitCheck"); + if let JsValue::Function(func) = ctx.get_global().get("commonExitCheck") { + func.call(&[]); + }; + JsValue::UnDefined + })) + .await; + rt.async_run_with_context(Box::new(|ctx| { + log::trace!("try assertPass"); + if let JsValue::Function(func) = ctx.get_global().get("assertPass") { + func.call(&[]); + }; + JsValue::UnDefined + })) + .await; + }); +} + +#[ignore = "unsupported, aes-wrap"] +fn test_crypto_aes_wrap() { + test_js_file("test/crypto/test-crypto-aes-wrap.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_async_sign_verify() { + test_js_file("test/crypto/test-crypto-async-sign-verify.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_authenticated() { + test_js_file("test/crypto/test-crypto-authenticated.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_authenticated_stream() { + test_js_file("test/crypto/test-crypto-authenticated-stream.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_binary_default() { + test_js_file("test/crypto/test-crypto-binary-default.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_certificate() { + test_js_file("test/crypto/test-crypto-certificate.js"); +} +#[ignore = "unsupported, md5"] +fn test_crypto_cipher_decipher() { + test_js_file("test/crypto/test-crypto-cipher-decipher.js"); +} +#[test] +fn test_crypto_cipheriv_decipheriv() { + test_js_file("test/crypto/test-crypto-cipheriv-decipheriv.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_classes() { + test_js_file("test/crypto/test-crypto-classes.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_des3_wrap() { + test_js_file("test/crypto/test-crypto-des3-wrap.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_dh_constructor() { + test_js_file("test/crypto/test-crypto-dh-constructor.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_dh_curves() { + test_js_file("test/crypto/test-crypto-dh-curves.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_dh() { + test_js_file("test/crypto/test-crypto-dh.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_dh_leak() { + test_js_file("test/crypto/test-crypto-dh-leak.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_dh_modp2() { + test_js_file("test/crypto/test-crypto-dh-modp2.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_dh_modp2_views() { + test_js_file("test/crypto/test-crypto-dh-modp2-views.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_dh_odd_key() { + test_js_file("test/crypto/test-crypto-dh-odd-key.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_dh_padding() { + test_js_file("test/crypto/test-crypto-dh-padding.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_dh_shared() { + test_js_file("test/crypto/test-crypto-dh-shared.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_dh_stateless() { + test_js_file("test/crypto/test-crypto-dh-stateless.js"); +} +#[ignore = "unsupported, domain"] +fn test_crypto_domain() { + test_js_file("test/crypto/test-crypto-domain.js"); +} +#[ignore = "unsupported, domain"] +fn test_crypto_domains() { + test_js_file("test/crypto/test-crypto-domains.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_ecb() { + test_js_file("test/crypto/test-crypto-ecb.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_ecdh_convert_key() { + test_js_file("test/crypto/test-crypto-ecdh-convert-key.js"); +} +#[ignore = "unsupported"] +fn test_crypto_fips() { + test_js_file("test/crypto/test-crypto-fips.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_from_binary() { + test_js_file("test/crypto/test-crypto-from-binary.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_getcipherinfo() { + test_js_file("test/crypto/test-crypto-getcipherinfo.js"); +} +#[test] +fn test_crypto_hash() { + test_js_file("test/crypto/test-crypto-hash.js"); +} +#[ignore = "unsupported, sha3-512"] +fn test_crypto_hash_stream_pipe() { + test_js_file("test/crypto/test-crypto-hash-stream-pipe.js"); +} +#[test] +fn test_crypto_hkdf() { + test_js_file("test/crypto/test-crypto-hkdf.js"); +} +#[test] +fn test_crypto_hmac() { + test_js_file("test/crypto/test-crypto-hmac.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto() { + test_js_file("test/crypto/test-crypto.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_keygen_deprecation() { + test_js_file("test/crypto/test-crypto-keygen-deprecation.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_keygen() { + test_js_file("test/crypto/test-crypto-keygen.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_key_objects() { + test_js_file("test/crypto/test-crypto-key-objects.js"); +} +#[ignore = "unsupported, work_thread"] +fn test_crypto_key_objects_messageport() { + test_js_file("test/crypto/test-crypto-key-objects-messageport.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_lazy_transform_writable() { + test_js_file("test/crypto/test-crypto-lazy-transform-writable.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_modp1_error() { + test_js_file("test/crypto/test-crypto-modp1-error.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_op_during_process_exit() { + test_js_file("test/crypto/test-crypto-op-during-process-exit.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_padding_aes256() { + test_js_file("test/crypto/test-crypto-padding-aes256.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_padding() { + test_js_file("test/crypto/test-crypto-padding.js"); +} +#[test] +fn test_crypto_pbkdf2() { + test_js_file("test/crypto/test-crypto-pbkdf2.js"); +} + +#[ignore = "unsupported, prime"] +fn test_crypto_prime() { + test_js_file("test/crypto/test-crypto-prime.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_private_decrypt_gh32240() { + test_js_file("test/crypto/test-crypto-private-decrypt-gh32240.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_psychic_signatures() { + test_js_file("test/crypto/test-crypto-psychic-signatures.js"); +} +#[test] +fn test_crypto_randomfillsync_regression() { + test_js_file("test/crypto/test-crypto-randomfillsync-regression.js"); +} +#[test] +fn test_crypto_random() { + test_js_file("test/crypto/test-crypto-random.js"); +} +#[test] +fn test_crypto_randomuuid() { + test_js_file("test/crypto/test-crypto-randomuuid.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_rsa_dsa() { + test_js_file("test/crypto/test-crypto-rsa-dsa.js"); +} +#[test] +fn test_crypto_scrypt() { + test_js_file("test/crypto/test-crypto-scrypt.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_secret_keygen() { + test_js_file("test/crypto/test-crypto-secret-keygen.js"); +} + +#[ignore = "unsupported, child_process"] +fn test_crypto_secure_heap() { + test_js_file("test/crypto/test-crypto-secure-heap.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_sign_verify() { + test_js_file("test/crypto/test-crypto-sign-verify.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_stream() { + test_js_file("test/crypto/test-crypto-stream.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_subtle_zero_length() { + test_js_file("test/crypto/test-crypto-subtle-zero-length.js"); +} +#[test] +fn test_crypto_timing_safe_equal() { + test_js_file("test/crypto/test-crypto-timing-safe-equal.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_update_encoding() { + test_js_file("test/crypto/test-crypto-update-encoding.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_verify_failure() { + test_js_file("test/crypto/test-crypto-verify-failure.js"); +} + +#[ignore = "unsupport, webcrypto"] +fn test_crypto_webcrypto_aes_decrypt_tag_too_small() { + test_js_file("test/crypto/test-crypto-webcrypto-aes-decrypt-tag-too-small.js"); +} + +#[ignore = "unsupport, worker thread"] +fn test_crypto_worker_thread() { + test_js_file("test/crypto/test-crypto-worker-thread.js"); +} +#[test] +#[ignore = "working"] +fn test_crypto_x509() { + test_js_file("test/crypto/test-crypto-x509.js"); +}