From e7d8b1f68338b960d7a360f9286967249ea32a54 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Thu, 8 Dec 2022 20:28:19 +0800 Subject: [PATCH 01/25] prepare tests --- Cargo.toml | 1 + test/crypto/test-crypto-aes-wrap.js | 64 + test/crypto/test-crypto-async-sign-verify.js | 145 ++ .../test-crypto-authenticated-stream.js | 144 ++ test/crypto/test-crypto-authenticated.js | 788 +++++++ test/crypto/test-crypto-binary-default.js | 723 +++++++ test/crypto/test-crypto-certificate.js | 121 ++ test/crypto/test-crypto-cipher-decipher.js | 285 +++ .../crypto/test-crypto-cipheriv-decipheriv.js | 219 ++ test/crypto/test-crypto-classes.js | 34 + test/crypto/test-crypto-des3-wrap.js | 27 + test/crypto/test-crypto-dh-constructor.js | 36 + test/crypto/test-crypto-dh-curves.js | 193 ++ test/crypto/test-crypto-dh-leak.js | 31 + test/crypto/test-crypto-dh-modp2-views.js | 26 + test/crypto/test-crypto-dh-modp2.js | 45 + test/crypto/test-crypto-dh-odd-key.js | 43 + test/crypto/test-crypto-dh-padding.js | 109 + test/crypto/test-crypto-dh-shared.js | 17 + test/crypto/test-crypto-dh-stateless.js | 248 +++ test/crypto/test-crypto-dh.js | 206 ++ test/crypto/test-crypto-domain.js | 49 + test/crypto/test-crypto-domains.js | 60 + test/crypto/test-crypto-ecb.js | 54 + test/crypto/test-crypto-ecdh-convert-key.js | 127 ++ test/crypto/test-crypto-fips.js | 277 +++ test/crypto/test-crypto-from-binary.js | 65 + test/crypto/test-crypto-getcipherinfo.js | 72 + test/crypto/test-crypto-hash-stream-pipe.js | 46 + test/crypto/test-crypto-hash.js | 280 +++ test/crypto/test-crypto-hkdf.js | 227 ++ test/crypto/test-crypto-hmac.js | 463 +++++ .../test-crypto-key-objects-messageport.js | 89 + test/crypto/test-crypto-key-objects.js | 884 ++++++++ test/crypto/test-crypto-keygen-deprecation.js | 53 + test/crypto/test-crypto-keygen.js | 1821 +++++++++++++++++ .../test-crypto-lazy-transform-writable.js | 38 + test/crypto/test-crypto-modp1-error.js | 28 + .../test-crypto-op-during-process-exit.js | 30 + test/crypto/test-crypto-padding-aes256.js | 60 + test/crypto/test-crypto-padding.js | 126 ++ test/crypto/test-crypto-pbkdf2.js | 243 +++ test/crypto/test-crypto-prime.js | 285 +++ .../test-crypto-private-decrypt-gh32240.js | 43 + test/crypto/test-crypto-psychic-signatures.js | 102 + test/crypto/test-crypto-random.js | 529 +++++ .../test-crypto-randomfillsync-regression.js | 20 + test/crypto/test-crypto-randomuuid.js | 60 + test/crypto/test-crypto-rsa-dsa.js | 476 +++++ test/crypto/test-crypto-scrypt.js | 270 +++ test/crypto/test-crypto-secret-keygen.js | 139 ++ test/crypto/test-crypto-secure-heap.js | 78 + test/crypto/test-crypto-sign-verify.js | 778 +++++++ test/crypto/test-crypto-stream.js | 85 + test/crypto/test-crypto-subtle-zero-length.js | 41 + test/crypto/test-crypto-update-encoding.js | 24 + test/crypto/test-crypto-verify-failure.js | 67 + ...pto-webcrypto-aes-decrypt-tag-too-small.js | 31 + test/crypto/test-crypto-worker-thread.js | 19 + test/crypto/test-crypto-x509.js | 264 +++ test/crypto/test-crypto.js | 329 +++ tests/test-crypto.rs | 267 +++ 62 files changed, 12504 insertions(+) create mode 100644 test/crypto/test-crypto-aes-wrap.js create mode 100644 test/crypto/test-crypto-async-sign-verify.js create mode 100644 test/crypto/test-crypto-authenticated-stream.js create mode 100644 test/crypto/test-crypto-authenticated.js create mode 100644 test/crypto/test-crypto-binary-default.js create mode 100644 test/crypto/test-crypto-certificate.js create mode 100644 test/crypto/test-crypto-cipher-decipher.js create mode 100644 test/crypto/test-crypto-cipheriv-decipheriv.js create mode 100644 test/crypto/test-crypto-classes.js create mode 100644 test/crypto/test-crypto-des3-wrap.js create mode 100644 test/crypto/test-crypto-dh-constructor.js create mode 100644 test/crypto/test-crypto-dh-curves.js create mode 100644 test/crypto/test-crypto-dh-leak.js create mode 100644 test/crypto/test-crypto-dh-modp2-views.js create mode 100644 test/crypto/test-crypto-dh-modp2.js create mode 100644 test/crypto/test-crypto-dh-odd-key.js create mode 100644 test/crypto/test-crypto-dh-padding.js create mode 100644 test/crypto/test-crypto-dh-shared.js create mode 100644 test/crypto/test-crypto-dh-stateless.js create mode 100644 test/crypto/test-crypto-dh.js create mode 100644 test/crypto/test-crypto-domain.js create mode 100644 test/crypto/test-crypto-domains.js create mode 100644 test/crypto/test-crypto-ecb.js create mode 100644 test/crypto/test-crypto-ecdh-convert-key.js create mode 100644 test/crypto/test-crypto-fips.js create mode 100644 test/crypto/test-crypto-from-binary.js create mode 100644 test/crypto/test-crypto-getcipherinfo.js create mode 100644 test/crypto/test-crypto-hash-stream-pipe.js create mode 100644 test/crypto/test-crypto-hash.js create mode 100644 test/crypto/test-crypto-hkdf.js create mode 100644 test/crypto/test-crypto-hmac.js create mode 100644 test/crypto/test-crypto-key-objects-messageport.js create mode 100644 test/crypto/test-crypto-key-objects.js create mode 100644 test/crypto/test-crypto-keygen-deprecation.js create mode 100644 test/crypto/test-crypto-keygen.js create mode 100644 test/crypto/test-crypto-lazy-transform-writable.js create mode 100644 test/crypto/test-crypto-modp1-error.js create mode 100644 test/crypto/test-crypto-op-during-process-exit.js create mode 100644 test/crypto/test-crypto-padding-aes256.js create mode 100644 test/crypto/test-crypto-padding.js create mode 100644 test/crypto/test-crypto-pbkdf2.js create mode 100644 test/crypto/test-crypto-prime.js create mode 100644 test/crypto/test-crypto-private-decrypt-gh32240.js create mode 100644 test/crypto/test-crypto-psychic-signatures.js create mode 100644 test/crypto/test-crypto-random.js create mode 100644 test/crypto/test-crypto-randomfillsync-regression.js create mode 100644 test/crypto/test-crypto-randomuuid.js create mode 100644 test/crypto/test-crypto-rsa-dsa.js create mode 100644 test/crypto/test-crypto-scrypt.js create mode 100644 test/crypto/test-crypto-secret-keygen.js create mode 100644 test/crypto/test-crypto-secure-heap.js create mode 100644 test/crypto/test-crypto-sign-verify.js create mode 100644 test/crypto/test-crypto-stream.js create mode 100644 test/crypto/test-crypto-subtle-zero-length.js create mode 100644 test/crypto/test-crypto-update-encoding.js create mode 100644 test/crypto/test-crypto-verify-failure.js create mode 100644 test/crypto/test-crypto-webcrypto-aes-decrypt-tag-too-small.js create mode 100644 test/crypto/test-crypto-worker-thread.js create mode 100644 test/crypto/test-crypto-x509.js create mode 100644 test/crypto/test-crypto.js create mode 100644 tests/test-crypto.rs diff --git a/Cargo.toml b/Cargo.toml index 4482cca..b6837e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ libc = "0.2" url = "2.2.2" lazy_static = "1.4" encoding = "0.2" +wasi-crypto-guest = "0.1.3" [features] default = [] 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..89d0e49 --- /dev/null +++ b/test/crypto/test-crypto-cipheriv-decipheriv.js @@ -0,0 +1,219 @@ +// 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'); + +function testCipher1(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, + `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) { + // 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) { + // 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}`); +} + +{ + const Cipheriv = crypto.Cipheriv; + const key = '123456789012345678901234'; + const iv = '12345678'; + + const instance = Cipheriv('des-ede3-cbc', 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('des-ede3-cbc', null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + + assert.throws( + () => crypto.createCipheriv('des-ede3-cbc', key, 10), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); +} + +{ + const Decipheriv = crypto.Decipheriv; + const key = '123456789012345678901234'; + const iv = '12345678'; + + const instance = Decipheriv('des-ede3-cbc', 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('des-ede3-cbc', null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + + assert.throws( + () => crypto.createDecipheriv('des-ede3-cbc', key, 10), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); +} + +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. +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..6588509 --- /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'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +const stream = require('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..c8320bd --- /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'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const fs = require('fs'); + +const fixtures = require('../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..87c50f8 --- /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'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { kMaxLength } = require('buffer'); +const assert = require('assert'); +const { + createSecretKey, + hkdf, + hkdfSync, + getHashes +} = require('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..1ba682f --- /dev/null +++ b/test/crypto/test-crypto-hmac.js @@ -0,0 +1,463 @@ +// 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 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; + + 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) { + 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..533a4f0 --- /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'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('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..9a82eea --- /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'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const cryptop = require('crypto').webcrypto; +const { kMaxLength } = require('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..702ad11 --- /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'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { randomFillSync } = require('crypto'); +const { notStrictEqual } = require('assert'); + +const ab = new ArrayBuffer(20); +const buf = Buffer.from(ab, 10); + +const before = buf.toString('hex'); + +randomFillSync(buf); + +const after = buf.toString('hex'); + +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..a719449 --- /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'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + randomUUID, +} = require('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..5fddea7 --- /dev/null +++ b/test/crypto/test-crypto-scrypt.js @@ -0,0 +1,270 @@ +// 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 crypto = require('crypto'); + +const { internalBinding } = require('internal/test/binding'); +if (typeof internalBinding('crypto').ScryptJob !== 'function') + common.skip('no scrypt support'); + +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')); + })); +} + +{ + 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-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/tests/test-crypto.rs b/tests/test-crypto.rs new file mode 100644 index 0000000..44ea6c4 --- /dev/null +++ b/tests/test-crypto.rs @@ -0,0 +1,267 @@ +#![allow(dead_code, unused_imports, unused_must_use)] + +use std::borrow::{Borrow, BorrowMut}; +use wasmedge_quickjs::*; + +fn test_js_file(file_path: &str) { + use wasmedge_quickjs as q; + let mut rt = q::Runtime::new(); + rt.run_with_context(|ctx| { + let code = std::fs::read_to_string(&file_path); + match code { + Ok(code) => { + ctx.put_args(vec![file_path.clone()]); + ctx.eval_module_str(code, &file_path); + if let JsValue::Bool(false) = ctx.get_global().get("assertPass") { + assert!(false, "js assert fail"); + } + } + Err(e) => { + eprintln!("{}", e.to_string()); + assert!(false, "run js test file fail"); + } + } + ctx.js_loop().unwrap(); + }); +} + +#[test] +fn test_crypto_aes_wrap() { + test_js_file("test/fs/test-crypto-aes-wrap.js"); +} +#[test] +fn test_crypto_async_sign_verify() { + test_js_file("test/fs/test-crypto-async-sign-verify.js"); +} +#[test] +fn test_crypto_authenticated() { + test_js_file("test/fs/test-crypto-authenticated.js"); +} +#[test] +fn test_crypto_authenticated_stream() { + test_js_file("test/fs/test-crypto-authenticated-stream.js"); +} +#[test] +fn test_crypto_binary_default() { + test_js_file("test/fs/test-crypto-binary-default.js"); +} +#[test] +fn test_crypto_certificate() { + test_js_file("test/fs/test-crypto-certificate.js"); +} +#[test] +fn test_crypto_cipher_decipher() { + test_js_file("test/fs/test-crypto-cipher-decipher.js"); +} +#[test] +fn test_crypto_cipheriv_decipheriv() { + test_js_file("test/fs/test-crypto-cipheriv-decipheriv.js"); +} +#[test] +fn test_crypto_classes() { + test_js_file("test/fs/test-crypto-classes.js"); +} +#[test] +fn test_crypto_des3_wrap() { + test_js_file("test/fs/test-crypto-des3-wrap.js"); +} +#[test] +fn test_crypto_dh_constructor() { + test_js_file("test/fs/test-crypto-dh-constructor.js"); +} +#[test] +fn test_crypto_dh_curves() { + test_js_file("test/fs/test-crypto-dh-curves.js"); +} +#[test] +fn test_crypto_dh() { + test_js_file("test/fs/test-crypto-dh.js"); +} +#[test] +fn test_crypto_dh_leak() { + test_js_file("test/fs/test-crypto-dh-leak.js"); +} +#[test] +fn test_crypto_dh_modp2() { + test_js_file("test/fs/test-crypto-dh-modp2.js"); +} +#[test] +fn test_crypto_dh_modp2_views() { + test_js_file("test/fs/test-crypto-dh-modp2-views.js"); +} +#[test] +fn test_crypto_dh_odd_key() { + test_js_file("test/fs/test-crypto-dh-odd-key.js"); +} +#[test] +fn test_crypto_dh_padding() { + test_js_file("test/fs/test-crypto-dh-padding.js"); +} +#[test] +fn test_crypto_dh_shared() { + test_js_file("test/fs/test-crypto-dh-shared.js"); +} +#[test] +fn test_crypto_dh_stateless() { + test_js_file("test/fs/test-crypto-dh-stateless.js"); +} +#[test] +fn test_crypto_domain() { + test_js_file("test/fs/test-crypto-domain.js"); +} +#[test] +fn test_crypto_domains() { + test_js_file("test/fs/test-crypto-domains.js"); +} +#[test] +fn test_crypto_ecb() { + test_js_file("test/fs/test-crypto-ecb.js"); +} +#[test] +fn test_crypto_ecdh_convert_key() { + test_js_file("test/fs/test-crypto-ecdh-convert-key.js"); +} +#[test] +fn test_crypto_fips() { + test_js_file("test/fs/test-crypto-fips.js"); +} +#[test] +fn test_crypto_from_binary() { + test_js_file("test/fs/test-crypto-from-binary.js"); +} +#[test] +fn test_crypto_getcipherinfo() { + test_js_file("test/fs/test-crypto-getcipherinfo.js"); +} +#[test] +fn test_crypto_hash() { + test_js_file("test/fs/test-crypto-hash.js"); +} +#[test] +fn test_crypto_hash_stream_pipe() { + test_js_file("test/fs/test-crypto-hash-stream-pipe.js"); +} +#[test] +fn test_crypto_hkdf() { + test_js_file("test/fs/test-crypto-hkdf.js"); +} +#[test] +fn test_crypto_hmac() { + test_js_file("test/fs/test-crypto-hmac.js"); +} +#[test] +fn test_crypto() { + test_js_file("test/fs/test-crypto.js"); +} +#[test] +fn test_crypto_keygen_deprecation() { + test_js_file("test/fs/test-crypto-keygen-deprecation.js"); +} +#[test] +fn test_crypto_keygen() { + test_js_file("test/fs/test-crypto-keygen.js"); +} +#[test] +fn test_crypto_key_objects() { + test_js_file("test/fs/test-crypto-key-objects.js"); +} +#[test] +fn test_crypto_key_objects_messageport() { + test_js_file("test/fs/test-crypto-key-objects-messageport.js"); +} +#[test] +fn test_crypto_lazy_transform_writable() { + test_js_file("test/fs/test-crypto-lazy-transform-writable.js"); +} +#[test] +fn test_crypto_modp1_error() { + test_js_file("test/fs/test-crypto-modp1-error.js"); +} +#[test] +fn test_crypto_op_during_process_exit() { + test_js_file("test/fs/test-crypto-op-during-process-exit.js"); +} +#[test] +fn test_crypto_padding_aes256() { + test_js_file("test/fs/test-crypto-padding-aes256.js"); +} +#[test] +fn test_crypto_padding() { + test_js_file("test/fs/test-crypto-padding.js"); +} +#[test] +fn test_crypto_pbkdf2() { + test_js_file("test/fs/test-crypto-pbkdf2.js"); +} +#[test] +fn test_crypto_prime() { + test_js_file("test/fs/test-crypto-prime.js"); +} +#[test] +fn test_crypto_private_decrypt_gh32240() { + test_js_file("test/fs/test-crypto-private-decrypt-gh32240.js"); +} +#[test] +fn test_crypto_psychic_signatures() { + test_js_file("test/fs/test-crypto-psychic-signatures.js"); +} +#[test] +fn test_crypto_randomfillsync_regression() { + test_js_file("test/fs/test-crypto-randomfillsync-regression.js"); +} +#[test] +fn test_crypto_random() { + test_js_file("test/fs/test-crypto-random.js"); +} +#[test] +fn test_crypto_randomuuid() { + test_js_file("test/fs/test-crypto-randomuuid.js"); +} +#[test] +fn test_crypto_rsa_dsa() { + test_js_file("test/fs/test-crypto-rsa-dsa.js"); +} +#[test] +fn test_crypto_scrypt() { + test_js_file("test/fs/test-crypto-scrypt.js"); +} +#[test] +fn test_crypto_secret_keygen() { + test_js_file("test/fs/test-crypto-secret-keygen.js"); +} +#[test] +fn test_crypto_secure_heap() { + test_js_file("test/fs/test-crypto-secure-heap.js"); +} +#[test] +fn test_crypto_sign_verify() { + test_js_file("test/fs/test-crypto-sign-verify.js"); +} +#[test] +fn test_crypto_stream() { + test_js_file("test/fs/test-crypto-stream.js"); +} +#[test] +fn test_crypto_subtle_zero_length() { + test_js_file("test/fs/test-crypto-subtle-zero-length.js"); +} +#[test] +fn test_crypto_update_encoding() { + test_js_file("test/fs/test-crypto-update-encoding.js"); +} +#[test] +fn test_crypto_verify_failure() { + test_js_file("test/fs/test-crypto-verify-failure.js"); +} +#[test] +fn test_crypto_webcrypto_aes_decrypt_tag_too_small() { + test_js_file("test/fs/test-crypto-webcrypto-aes-decrypt-tag-too-small.js"); +} +#[test] +fn test_crypto_worker_thread() { + test_js_file("test/fs/test-crypto-worker-thread.js"); +} +#[test] +fn test_crypto_x509() { + test_js_file("test/fs/test-crypto-x509.js"); +} From ed6601418268f2b4168b52904611f87953b1765d Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Thu, 8 Dec 2022 21:05:12 +0800 Subject: [PATCH 02/25] impl: fips --- modules/crypto.js | 268 ++++++++++++++++++++++++++++++++++ modules/internal/errors.js | 9 ++ src/internal_module/crypto.rs | 12 ++ src/internal_module/mod.rs | 1 + tests/test-crypto.rs | 60 ++++++++ 5 files changed, 350 insertions(+) create mode 100644 modules/crypto.js create mode 100644 src/internal_module/crypto.rs diff --git a/modules/crypto.js b/modules/crypto.js new file mode 100644 index 0000000..8274160 --- /dev/null +++ b/modules/crypto.js @@ -0,0 +1,268 @@ +// 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 } from "./internal/errors"; +import { crypto as constants } from "./internal_binding/constants"; +import { getOptionValue } from "./internal/options"; +import { + timingSafeEqual, +} from "./internal_binding/crypto"; +/* +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 { createHash, 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 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/errors.js b/modules/internal/errors.js index acc4b89..1e91826 100644 --- a/modules/internal/errors.js +++ b/modules/internal/errors.js @@ -816,3 +816,12 @@ 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"; + } +} diff --git a/src/internal_module/crypto.rs b/src/internal_module/crypto.rs new file mode 100644 index 0000000..11cfdef --- /dev/null +++ b/src/internal_module/crypto.rs @@ -0,0 +1,12 @@ +use core::arch; + +use crate::quickjs_sys::*; +use crate::EventLoop; + +struct Crypto; + +impl ModuleInit for Crypto { + fn init_module(ctx: &mut Context, m: &mut JsModuleDef) {} +} + +pub fn init_module(ctx: &mut Context) {} diff --git a/src/internal_module/mod.rs b/src/internal_module/mod.rs index d0aa3ac..3ea6022 100644 --- a/src/internal_module/mod.rs +++ b/src/internal_module/mod.rs @@ -8,3 +8,4 @@ pub mod os; #[cfg(feature = "tensorflow")] pub mod tensorflow_module; pub mod wasi_net_module; +pub mod crypto; \ No newline at end of file diff --git a/tests/test-crypto.rs b/tests/test-crypto.rs index 44ea6c4..a284cda 100644 --- a/tests/test-crypto.rs +++ b/tests/test-crypto.rs @@ -26,242 +26,302 @@ fn test_js_file(file_path: &str) { } #[test] +#[ignore = "working"] fn test_crypto_aes_wrap() { test_js_file("test/fs/test-crypto-aes-wrap.js"); } #[test] +#[ignore = "working"] fn test_crypto_async_sign_verify() { test_js_file("test/fs/test-crypto-async-sign-verify.js"); } #[test] +#[ignore = "working"] fn test_crypto_authenticated() { test_js_file("test/fs/test-crypto-authenticated.js"); } #[test] +#[ignore = "working"] fn test_crypto_authenticated_stream() { test_js_file("test/fs/test-crypto-authenticated-stream.js"); } #[test] +#[ignore = "working"] fn test_crypto_binary_default() { test_js_file("test/fs/test-crypto-binary-default.js"); } #[test] +#[ignore = "working"] fn test_crypto_certificate() { test_js_file("test/fs/test-crypto-certificate.js"); } #[test] +#[ignore = "working"] fn test_crypto_cipher_decipher() { test_js_file("test/fs/test-crypto-cipher-decipher.js"); } #[test] +#[ignore = "working"] fn test_crypto_cipheriv_decipheriv() { test_js_file("test/fs/test-crypto-cipheriv-decipheriv.js"); } #[test] +#[ignore = "working"] fn test_crypto_classes() { test_js_file("test/fs/test-crypto-classes.js"); } #[test] +#[ignore = "working"] fn test_crypto_des3_wrap() { test_js_file("test/fs/test-crypto-des3-wrap.js"); } #[test] +#[ignore = "working"] fn test_crypto_dh_constructor() { test_js_file("test/fs/test-crypto-dh-constructor.js"); } #[test] +#[ignore = "working"] fn test_crypto_dh_curves() { test_js_file("test/fs/test-crypto-dh-curves.js"); } #[test] +#[ignore = "working"] fn test_crypto_dh() { test_js_file("test/fs/test-crypto-dh.js"); } #[test] +#[ignore = "working"] fn test_crypto_dh_leak() { test_js_file("test/fs/test-crypto-dh-leak.js"); } #[test] +#[ignore = "working"] fn test_crypto_dh_modp2() { test_js_file("test/fs/test-crypto-dh-modp2.js"); } #[test] +#[ignore = "working"] fn test_crypto_dh_modp2_views() { test_js_file("test/fs/test-crypto-dh-modp2-views.js"); } #[test] +#[ignore = "working"] fn test_crypto_dh_odd_key() { test_js_file("test/fs/test-crypto-dh-odd-key.js"); } #[test] +#[ignore = "working"] fn test_crypto_dh_padding() { test_js_file("test/fs/test-crypto-dh-padding.js"); } #[test] +#[ignore = "working"] fn test_crypto_dh_shared() { test_js_file("test/fs/test-crypto-dh-shared.js"); } #[test] +#[ignore = "working"] fn test_crypto_dh_stateless() { test_js_file("test/fs/test-crypto-dh-stateless.js"); } #[test] +#[ignore = "unsupported, domain"] fn test_crypto_domain() { test_js_file("test/fs/test-crypto-domain.js"); } #[test] +#[ignore = "unsupported, domain"] fn test_crypto_domains() { test_js_file("test/fs/test-crypto-domains.js"); } #[test] +#[ignore = "working"] fn test_crypto_ecb() { test_js_file("test/fs/test-crypto-ecb.js"); } #[test] +#[ignore = "working"] fn test_crypto_ecdh_convert_key() { test_js_file("test/fs/test-crypto-ecdh-convert-key.js"); } #[test] +#[ignore = "unsupported"] fn test_crypto_fips() { test_js_file("test/fs/test-crypto-fips.js"); } #[test] +#[ignore = "working"] fn test_crypto_from_binary() { test_js_file("test/fs/test-crypto-from-binary.js"); } #[test] +#[ignore = "working"] fn test_crypto_getcipherinfo() { test_js_file("test/fs/test-crypto-getcipherinfo.js"); } #[test] +#[ignore = "working"] fn test_crypto_hash() { test_js_file("test/fs/test-crypto-hash.js"); } #[test] +#[ignore = "working"] fn test_crypto_hash_stream_pipe() { test_js_file("test/fs/test-crypto-hash-stream-pipe.js"); } #[test] +#[ignore = "working"] fn test_crypto_hkdf() { test_js_file("test/fs/test-crypto-hkdf.js"); } #[test] +#[ignore = "working"] fn test_crypto_hmac() { test_js_file("test/fs/test-crypto-hmac.js"); } #[test] +#[ignore = "working"] fn test_crypto() { test_js_file("test/fs/test-crypto.js"); } #[test] +#[ignore = "working"] fn test_crypto_keygen_deprecation() { test_js_file("test/fs/test-crypto-keygen-deprecation.js"); } #[test] +#[ignore = "working"] fn test_crypto_keygen() { test_js_file("test/fs/test-crypto-keygen.js"); } #[test] +#[ignore = "working"] fn test_crypto_key_objects() { test_js_file("test/fs/test-crypto-key-objects.js"); } #[test] +#[ignore = "working"] fn test_crypto_key_objects_messageport() { test_js_file("test/fs/test-crypto-key-objects-messageport.js"); } #[test] +#[ignore = "working"] fn test_crypto_lazy_transform_writable() { test_js_file("test/fs/test-crypto-lazy-transform-writable.js"); } #[test] +#[ignore = "working"] fn test_crypto_modp1_error() { test_js_file("test/fs/test-crypto-modp1-error.js"); } #[test] +#[ignore = "working"] fn test_crypto_op_during_process_exit() { test_js_file("test/fs/test-crypto-op-during-process-exit.js"); } #[test] +#[ignore = "working"] fn test_crypto_padding_aes256() { test_js_file("test/fs/test-crypto-padding-aes256.js"); } #[test] +#[ignore = "working"] fn test_crypto_padding() { test_js_file("test/fs/test-crypto-padding.js"); } #[test] +#[ignore = "working"] fn test_crypto_pbkdf2() { test_js_file("test/fs/test-crypto-pbkdf2.js"); } #[test] +#[ignore = "working"] fn test_crypto_prime() { test_js_file("test/fs/test-crypto-prime.js"); } #[test] +#[ignore = "working"] fn test_crypto_private_decrypt_gh32240() { test_js_file("test/fs/test-crypto-private-decrypt-gh32240.js"); } #[test] +#[ignore = "working"] fn test_crypto_psychic_signatures() { test_js_file("test/fs/test-crypto-psychic-signatures.js"); } #[test] +#[ignore = "working"] fn test_crypto_randomfillsync_regression() { test_js_file("test/fs/test-crypto-randomfillsync-regression.js"); } #[test] +#[ignore = "working"] fn test_crypto_random() { test_js_file("test/fs/test-crypto-random.js"); } #[test] +#[ignore = "working"] fn test_crypto_randomuuid() { test_js_file("test/fs/test-crypto-randomuuid.js"); } #[test] +#[ignore = "working"] fn test_crypto_rsa_dsa() { test_js_file("test/fs/test-crypto-rsa-dsa.js"); } #[test] +#[ignore = "working"] fn test_crypto_scrypt() { test_js_file("test/fs/test-crypto-scrypt.js"); } #[test] +#[ignore = "working"] fn test_crypto_secret_keygen() { test_js_file("test/fs/test-crypto-secret-keygen.js"); } #[test] +#[ignore = "working"] fn test_crypto_secure_heap() { test_js_file("test/fs/test-crypto-secure-heap.js"); } #[test] +#[ignore = "working"] fn test_crypto_sign_verify() { test_js_file("test/fs/test-crypto-sign-verify.js"); } #[test] +#[ignore = "working"] fn test_crypto_stream() { test_js_file("test/fs/test-crypto-stream.js"); } #[test] +#[ignore = "working"] fn test_crypto_subtle_zero_length() { test_js_file("test/fs/test-crypto-subtle-zero-length.js"); } #[test] +#[ignore = "working"] fn test_crypto_update_encoding() { test_js_file("test/fs/test-crypto-update-encoding.js"); } #[test] +#[ignore = "working"] fn test_crypto_verify_failure() { test_js_file("test/fs/test-crypto-verify-failure.js"); } #[test] +#[ignore = "working"] fn test_crypto_webcrypto_aes_decrypt_tag_too_small() { test_js_file("test/fs/test-crypto-webcrypto-aes-decrypt-tag-too-small.js"); } #[test] +#[ignore = "unsupport, worker thread"] fn test_crypto_worker_thread() { test_js_file("test/fs/test-crypto-worker-thread.js"); } #[test] +#[ignore = "working"] fn test_crypto_x509() { test_js_file("test/fs/test-crypto-x509.js"); } From 9d486a916500407d96a463ebca04640f80da1a8f Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Wed, 14 Dec 2022 00:47:12 +0800 Subject: [PATCH 03/25] test: crypto-[randomuuid. randomfillsync-regression, timing-safe-equal] --- modules/assert.js | 1 + modules/buffer.js | 11 +- modules/crypto.js | 61 ++- modules/internal/crypto/random.js | 463 ++++++++++++++++++ modules/internal/errors.js | 18 + modules/internal/options.js | 12 +- modules/internal/util.js | 4 +- modules/internal/validators.js | 24 + src/internal_module/crypto.rs | 94 +++- src/internal_module/fs.rs | 2 +- src/internal_module/mod.rs | 2 +- src/quickjs_sys/mod.rs | 1 + test/common.js | 3 + test/crypto/test-crypto-random.js | 20 +- .../test-crypto-randomfillsync-regression.js | 8 +- test/crypto/test-crypto-randomuuid.js | 8 +- test/crypto/test-crypto-timing-safe-equal.js | 95 ++++ tests/test-crypto.rs | 140 +++--- tests/test-path.rs | 14 +- 19 files changed, 861 insertions(+), 120 deletions(-) create mode 100644 modules/internal/crypto/random.js create mode 100644 test/crypto/test-crypto-timing-safe-equal.js 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 index 8274160..2140c5d 100644 --- a/modules/crypto.js +++ b/modules/crypto.js @@ -1,13 +1,26 @@ // 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 } from "./internal/errors"; +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 { - timingSafeEqual, -} from "./internal_binding/crypto"; -/* + 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, @@ -19,7 +32,7 @@ import { randomInt, randomUUID, } from "./internal/crypto/random"; -import { pbkdf2, pbkdf2Sync } from "./internal/crypto/pbkdf2"; +/*import { pbkdf2, pbkdf2Sync } from "./internal/crypto/pbkdf2"; import { scrypt, scryptSync } from "./internal/crypto/scrypt"; import { hkdf, hkdfSync } from "./internal/crypto/hkdf"; import { @@ -125,10 +138,10 @@ Object.defineProperty(constants, "defaultCipherList", { /* const getDiffieHellman = createDiffieHellmanGroup; */ -function getFipsCrypto () { +function getFipsCrypto() { throw new Error("crypto.getFipsCrypto is unimplemented") } -function setFipsCrypto (_val) { +function setFipsCrypto(_val) { throw new Error("crypto.setFipsCrypto is unimplemented") } const getFips = fipsForced ? getFipsForced : getFipsCrypto; @@ -136,11 +149,11 @@ const setFips = fipsForced ? setFipsForced : setFipsCrypto; /* const sign = signOneShot; const verify = verifyOneShot; - +*/ export default { - Certificate, + /*Certificate,*/ checkPrime, - checkPrimeSync, + checkPrimeSync,/* Cipheriv, constants, createCipheriv, @@ -163,9 +176,9 @@ export default { generateKey, generateKeyPair, generateKeyPairSync, - generateKeySync, + generateKeySync,*/ generatePrime, - generatePrimeSync, + generatePrimeSync,/* getCipherInfo, getCiphers, getCurves, @@ -182,30 +195,30 @@ export default { privateDecrypt, privateEncrypt, publicDecrypt, - publicEncrypt, + publicEncrypt,*/ randomBytes, randomFill, randomFillSync, randomInt, - randomUUID, + randomUUID,/* scrypt, scryptSync, secureHeapUsed, setEngine, setFips, Sign, - sign, + sign,*/ timingSafeEqual, - Verify, + /*Verify, verify, webcrypto, - X509Certificate, + X509Certificate,*/ }; -*/ + export { - /*Certificate, + /*Certificate,*/ checkPrime, - checkPrimeSync, + checkPrimeSync,/* Cipheriv,*/ constants, /*createCipheriv, @@ -228,9 +241,9 @@ export { generateKey, generateKeyPair, generateKeyPairSync, - generateKeySync, + generateKeySync,*/ generatePrime, - generatePrimeSync, + generatePrimeSync,/* getCipherInfo, getCiphers, getCurves, @@ -247,12 +260,12 @@ export { privateDecrypt, privateEncrypt, publicDecrypt, - publicEncrypt, + publicEncrypt,*/ randomBytes, randomFill, randomFillSync, randomInt, - randomUUID, + randomUUID,/* scrypt, scryptSync, secureHeapUsed, diff --git a/modules/internal/crypto/random.js b/modules/internal/crypto/random.js new file mode 100644 index 0000000..0abd6e8 --- /dev/null +++ b/modules/internal/crypto/random.js @@ -0,0 +1,463 @@ +'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 { random_fill } from "_node:crypto"; + +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, 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, 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); + }); +} + +function lazyDOMException(msg, name) { + let e = new Error(msg) + e.name = name; + return e; +} + +// 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/errors.js b/modules/internal/errors.js index 1e91826..69ada9f 100644 --- a/modules/internal/errors.js +++ b/modules/internal/errors.js @@ -825,3 +825,21 @@ export class ERR_CRYPTO_FIPS_FORCED extends Error { 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"; + } +} 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/util.js b/modules/internal/util.js index d9dc172..6ae6501 100644 --- a/modules/internal/util.js +++ b/modules/internal/util.js @@ -160,6 +160,7 @@ export function isError(e) { return e instanceof Error; } +export const kEmptyObject = Object.freeze(Object.create(null)); export default { createDeferredPromise, @@ -170,5 +171,6 @@ export default { deprecate, promisify, removeColors, - isError + isError, + kEmptyObject }; \ No newline at end of file diff --git a/modules/internal/validators.js b/modules/internal/validators.js index 33cf96b..36f79c4 100644 --- a/modules/internal/validators.js +++ b/modules/internal/validators.js @@ -191,6 +191,29 @@ 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); + } +} + export default { validatePort, validateFunction, @@ -200,5 +223,6 @@ export default { validateAbortSignal, validateCallback, validateInteger, + validateNumber, getValidMode } \ No newline at end of file diff --git a/src/internal_module/crypto.rs b/src/internal_module/crypto.rs index 11cfdef..4dba849 100644 --- a/src/internal_module/crypto.rs +++ b/src/internal_module/crypto.rs @@ -1,12 +1,102 @@ use core::arch; +use crate::event_loop::wasi_fs::{Errno, Size}; use crate::quickjs_sys::*; use crate::EventLoop; +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)), + } +} + +fn timing_safe_equal(_ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { + if let Some(JsValue::ArrayBuffer(a)) = argv.get(0) { + if let Some(JsValue::ArrayBuffer(b)) = argv.get(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]; + } + return eq.into(); + } + } + JsValue::UnDefined +} + +fn random_fill(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { + println!("{:?}", argv); + if let Some(JsValue::ArrayBuffer(buf)) = argv.get(0) { + if let Some(JsValue::Int(offset)) = argv.get(1) { + if let Some(JsValue::Int(size)) = argv.get(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(()) => { + println!("{:?}", buf.to_vec()); + JsValue::UnDefined + } + Err(e) => { + let err = super::fs::errno_to_js_object(ctx, e); + JsValue::Exception(ctx.throw_error(err)) + } + }; + } + } + } + JsValue::UnDefined +} + struct Crypto; impl ModuleInit for Crypto { - fn init_module(ctx: &mut Context, m: &mut JsModuleDef) {} + 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(), + ); + } } -pub fn init_module(ctx: &mut Context) {} +pub fn init_module(ctx: &mut Context) { + ctx.register_module( + "_node:crypto\0", + Crypto, + &["timing_safe_equal\0", "random_fill\0"], + ) +} diff --git a/src/internal_module/fs.rs b/src/internal_module/fs.rs index 6d1d0e1..d1e7c27 100644 --- a/src/internal_module/fs.rs +++ b/src/internal_module/fs.rs @@ -76,7 +76,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 3ea6022..48b8f49 100644 --- a/src/internal_module/mod.rs +++ b/src/internal_module/mod.rs @@ -1,4 +1,5 @@ pub mod core; +pub mod crypto; pub mod encoding; pub mod fs; pub mod httpx; @@ -8,4 +9,3 @@ pub mod os; #[cfg(feature = "tensorflow")] pub mod tensorflow_module; pub mod wasi_net_module; -pub mod crypto; \ No newline at end of file diff --git a/src/quickjs_sys/mod.rs b/src/quickjs_sys/mod.rs index 16fdbdd..5abbbdd 100644 --- a/src/quickjs_sys/mod.rs +++ b/src/quickjs_sys/mod.rs @@ -344,6 +344,7 @@ impl Context { super::internal_module::httpx::init_module(&mut ctx); super::internal_module::os::init_module(&mut ctx); super::internal_module::fs::init_module(&mut ctx); + super::internal_module::crypto::init_module(&mut ctx); ctx } diff --git a/test/common.js b/test/common.js index 80c2966..26b3d8c 100644 --- a/test/common.js +++ b/test/common.js @@ -283,6 +283,8 @@ export function getArrayBufferViews(buf) { return out; } +export const hasCrypto = true; + const common = { isDumbTerminal, isFreeBSD, @@ -294,6 +296,7 @@ const common = { isWindows, isAIX, isMainThread, + hasCrypto, mustCall, mustCallAtLeast, mustNotCall, diff --git a/test/crypto/test-crypto-random.js b/test/crypto/test-crypto-random.js index 9a82eea..f9f9fef 100644 --- a/test/crypto/test-crypto-random.js +++ b/test/crypto/test-crypto-random.js @@ -21,15 +21,15 @@ // Flags: --pending-deprecation 'use strict'; -const common = require('../common'); +import common from '../common'; if (!common.hasCrypto) common.skip('missing crypto'); -const assert = require('assert'); -const crypto = require('crypto'); -const cryptop = require('crypto').webcrypto; -const { kMaxLength } = require('buffer'); +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); @@ -38,7 +38,7 @@ common.expectWarning('DeprecationWarning', 'crypto.pseudoRandomBytes is deprecated.', 'DEP0115'); { - [crypto.randomBytes, crypto.pseudoRandomBytes].forEach((f) => { + [crypto.randomBytes/*, crypto.pseudoRandomBytes*/].forEach((f) => { [undefined, null, false, true, {}, []].forEach((value) => { const errObj = { code: 'ERR_INVALID_ARG_TYPE', @@ -101,7 +101,7 @@ common.expectWarning('DeprecationWarning', }); } -{ +/*{ [ new Uint16Array(10), new Uint32Array(10), @@ -111,7 +111,7 @@ common.expectWarning('DeprecationWarning', const after = Buffer.from(buf.buffer).toString('hex'); assert.notStrictEqual(before, after); }); -} +}*/ { [ @@ -334,12 +334,12 @@ assert.throws( ); }); -['pseudoRandomBytes', 'prng', 'rng'].forEach((f) => { +/*['pseudoRandomBytes', 'prng', 'rng'].forEach((f) => { const desc = Object.getOwnPropertyDescriptor(crypto, f); assert.ok(desc); assert.strictEqual(desc.configurable, true); assert.strictEqual(desc.enumerable, false); -}); +});*/ { diff --git a/test/crypto/test-crypto-randomfillsync-regression.js b/test/crypto/test-crypto-randomfillsync-regression.js index 702ad11..92ba251 100644 --- a/test/crypto/test-crypto-randomfillsync-regression.js +++ b/test/crypto/test-crypto-randomfillsync-regression.js @@ -1,12 +1,12 @@ // Copyright Joyent and Node contributors. All rights reserved. MIT license. 'use strict'; -const common = require('../common'); +import common from '../common'; if (!common.hasCrypto) common.skip('missing crypto'); -const { randomFillSync } = require('crypto'); -const { notStrictEqual } = require('assert'); +import { randomFillSync } from 'crypto'; +import assert from 'assert'; const ab = new ArrayBuffer(20); const buf = Buffer.from(ab, 10); @@ -17,4 +17,4 @@ randomFillSync(buf); const after = buf.toString('hex'); -notStrictEqual(before, after); +assert.notStrictEqual(before, after); diff --git a/test/crypto/test-crypto-randomuuid.js b/test/crypto/test-crypto-randomuuid.js index a719449..02589f8 100644 --- a/test/crypto/test-crypto-randomuuid.js +++ b/test/crypto/test-crypto-randomuuid.js @@ -2,15 +2,15 @@ 'use strict'; -const common = require('../common'); +import common from '../common'; if (!common.hasCrypto) common.skip('missing crypto'); -const assert = require('assert'); -const { +import assert from 'assert'; +import { randomUUID, -} = require('crypto'); +} from 'crypto'; const last = new Set([ '00000000-0000-0000-0000-000000000000', 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/tests/test-crypto.rs b/tests/test-crypto.rs index a284cda..1bd948c 100644 --- a/tests/test-crypto.rs +++ b/tests/test-crypto.rs @@ -12,9 +12,6 @@ fn test_js_file(file_path: &str) { Ok(code) => { ctx.put_args(vec![file_path.clone()]); ctx.eval_module_str(code, &file_path); - if let JsValue::Bool(false) = ctx.get_global().get("assertPass") { - assert!(false, "js assert fail"); - } } Err(e) => { eprintln!("{}", e.to_string()); @@ -22,306 +19,319 @@ fn test_js_file(file_path: &str) { } } ctx.js_loop().unwrap(); + if let JsValue::Function(func) = ctx.get_global().get("_onExit") { + func.call(&[]); + } + ctx.js_loop().unwrap(); + if let JsValue::Function(func) = ctx.get_global().get("commonExitCheck") { + func.call(&[]); + } + ctx.js_loop().unwrap(); + if let JsValue::Bool(false) = ctx.get_global().get("assertPass") { + assert!(false, "js assert fail"); + } }); } #[test] #[ignore = "working"] fn test_crypto_aes_wrap() { - test_js_file("test/fs/test-crypto-aes-wrap.js"); + test_js_file("test/crypto/test-crypto-aes-wrap.js"); } #[test] #[ignore = "working"] fn test_crypto_async_sign_verify() { - test_js_file("test/fs/test-crypto-async-sign-verify.js"); + test_js_file("test/crypto/test-crypto-async-sign-verify.js"); } #[test] #[ignore = "working"] fn test_crypto_authenticated() { - test_js_file("test/fs/test-crypto-authenticated.js"); + test_js_file("test/crypto/test-crypto-authenticated.js"); } #[test] #[ignore = "working"] fn test_crypto_authenticated_stream() { - test_js_file("test/fs/test-crypto-authenticated-stream.js"); + test_js_file("test/crypto/test-crypto-authenticated-stream.js"); } #[test] #[ignore = "working"] fn test_crypto_binary_default() { - test_js_file("test/fs/test-crypto-binary-default.js"); + test_js_file("test/crypto/test-crypto-binary-default.js"); } #[test] #[ignore = "working"] fn test_crypto_certificate() { - test_js_file("test/fs/test-crypto-certificate.js"); + test_js_file("test/crypto/test-crypto-certificate.js"); } #[test] #[ignore = "working"] fn test_crypto_cipher_decipher() { - test_js_file("test/fs/test-crypto-cipher-decipher.js"); + test_js_file("test/crypto/test-crypto-cipher-decipher.js"); } #[test] #[ignore = "working"] fn test_crypto_cipheriv_decipheriv() { - test_js_file("test/fs/test-crypto-cipheriv-decipheriv.js"); + test_js_file("test/crypto/test-crypto-cipheriv-decipheriv.js"); } #[test] #[ignore = "working"] fn test_crypto_classes() { - test_js_file("test/fs/test-crypto-classes.js"); + test_js_file("test/crypto/test-crypto-classes.js"); } #[test] #[ignore = "working"] fn test_crypto_des3_wrap() { - test_js_file("test/fs/test-crypto-des3-wrap.js"); + test_js_file("test/crypto/test-crypto-des3-wrap.js"); } #[test] #[ignore = "working"] fn test_crypto_dh_constructor() { - test_js_file("test/fs/test-crypto-dh-constructor.js"); + test_js_file("test/crypto/test-crypto-dh-constructor.js"); } #[test] #[ignore = "working"] fn test_crypto_dh_curves() { - test_js_file("test/fs/test-crypto-dh-curves.js"); + test_js_file("test/crypto/test-crypto-dh-curves.js"); } #[test] #[ignore = "working"] fn test_crypto_dh() { - test_js_file("test/fs/test-crypto-dh.js"); + test_js_file("test/crypto/test-crypto-dh.js"); } #[test] #[ignore = "working"] fn test_crypto_dh_leak() { - test_js_file("test/fs/test-crypto-dh-leak.js"); + test_js_file("test/crypto/test-crypto-dh-leak.js"); } #[test] #[ignore = "working"] fn test_crypto_dh_modp2() { - test_js_file("test/fs/test-crypto-dh-modp2.js"); + test_js_file("test/crypto/test-crypto-dh-modp2.js"); } #[test] #[ignore = "working"] fn test_crypto_dh_modp2_views() { - test_js_file("test/fs/test-crypto-dh-modp2-views.js"); + test_js_file("test/crypto/test-crypto-dh-modp2-views.js"); } #[test] #[ignore = "working"] fn test_crypto_dh_odd_key() { - test_js_file("test/fs/test-crypto-dh-odd-key.js"); + test_js_file("test/crypto/test-crypto-dh-odd-key.js"); } #[test] #[ignore = "working"] fn test_crypto_dh_padding() { - test_js_file("test/fs/test-crypto-dh-padding.js"); + test_js_file("test/crypto/test-crypto-dh-padding.js"); } #[test] #[ignore = "working"] fn test_crypto_dh_shared() { - test_js_file("test/fs/test-crypto-dh-shared.js"); + test_js_file("test/crypto/test-crypto-dh-shared.js"); } #[test] #[ignore = "working"] fn test_crypto_dh_stateless() { - test_js_file("test/fs/test-crypto-dh-stateless.js"); + test_js_file("test/crypto/test-crypto-dh-stateless.js"); } #[test] #[ignore = "unsupported, domain"] fn test_crypto_domain() { - test_js_file("test/fs/test-crypto-domain.js"); + test_js_file("test/crypto/test-crypto-domain.js"); } #[test] #[ignore = "unsupported, domain"] fn test_crypto_domains() { - test_js_file("test/fs/test-crypto-domains.js"); + test_js_file("test/crypto/test-crypto-domains.js"); } #[test] #[ignore = "working"] fn test_crypto_ecb() { - test_js_file("test/fs/test-crypto-ecb.js"); + test_js_file("test/crypto/test-crypto-ecb.js"); } #[test] #[ignore = "working"] fn test_crypto_ecdh_convert_key() { - test_js_file("test/fs/test-crypto-ecdh-convert-key.js"); + test_js_file("test/crypto/test-crypto-ecdh-convert-key.js"); } #[test] #[ignore = "unsupported"] fn test_crypto_fips() { - test_js_file("test/fs/test-crypto-fips.js"); + test_js_file("test/crypto/test-crypto-fips.js"); } #[test] #[ignore = "working"] fn test_crypto_from_binary() { - test_js_file("test/fs/test-crypto-from-binary.js"); + test_js_file("test/crypto/test-crypto-from-binary.js"); } #[test] #[ignore = "working"] fn test_crypto_getcipherinfo() { - test_js_file("test/fs/test-crypto-getcipherinfo.js"); + test_js_file("test/crypto/test-crypto-getcipherinfo.js"); } #[test] #[ignore = "working"] fn test_crypto_hash() { - test_js_file("test/fs/test-crypto-hash.js"); + test_js_file("test/crypto/test-crypto-hash.js"); } #[test] #[ignore = "working"] fn test_crypto_hash_stream_pipe() { - test_js_file("test/fs/test-crypto-hash-stream-pipe.js"); + test_js_file("test/crypto/test-crypto-hash-stream-pipe.js"); } #[test] #[ignore = "working"] fn test_crypto_hkdf() { - test_js_file("test/fs/test-crypto-hkdf.js"); + test_js_file("test/crypto/test-crypto-hkdf.js"); } #[test] #[ignore = "working"] fn test_crypto_hmac() { - test_js_file("test/fs/test-crypto-hmac.js"); + test_js_file("test/crypto/test-crypto-hmac.js"); } #[test] #[ignore = "working"] fn test_crypto() { - test_js_file("test/fs/test-crypto.js"); + test_js_file("test/crypto/test-crypto.js"); } #[test] #[ignore = "working"] fn test_crypto_keygen_deprecation() { - test_js_file("test/fs/test-crypto-keygen-deprecation.js"); + test_js_file("test/crypto/test-crypto-keygen-deprecation.js"); } #[test] #[ignore = "working"] fn test_crypto_keygen() { - test_js_file("test/fs/test-crypto-keygen.js"); + test_js_file("test/crypto/test-crypto-keygen.js"); } #[test] #[ignore = "working"] fn test_crypto_key_objects() { - test_js_file("test/fs/test-crypto-key-objects.js"); + test_js_file("test/crypto/test-crypto-key-objects.js"); } #[test] #[ignore = "working"] fn test_crypto_key_objects_messageport() { - test_js_file("test/fs/test-crypto-key-objects-messageport.js"); + test_js_file("test/crypto/test-crypto-key-objects-messageport.js"); } #[test] #[ignore = "working"] fn test_crypto_lazy_transform_writable() { - test_js_file("test/fs/test-crypto-lazy-transform-writable.js"); + test_js_file("test/crypto/test-crypto-lazy-transform-writable.js"); } #[test] #[ignore = "working"] fn test_crypto_modp1_error() { - test_js_file("test/fs/test-crypto-modp1-error.js"); + test_js_file("test/crypto/test-crypto-modp1-error.js"); } #[test] #[ignore = "working"] fn test_crypto_op_during_process_exit() { - test_js_file("test/fs/test-crypto-op-during-process-exit.js"); + test_js_file("test/crypto/test-crypto-op-during-process-exit.js"); } #[test] #[ignore = "working"] fn test_crypto_padding_aes256() { - test_js_file("test/fs/test-crypto-padding-aes256.js"); + test_js_file("test/crypto/test-crypto-padding-aes256.js"); } #[test] #[ignore = "working"] fn test_crypto_padding() { - test_js_file("test/fs/test-crypto-padding.js"); + test_js_file("test/crypto/test-crypto-padding.js"); } #[test] #[ignore = "working"] fn test_crypto_pbkdf2() { - test_js_file("test/fs/test-crypto-pbkdf2.js"); + test_js_file("test/crypto/test-crypto-pbkdf2.js"); } #[test] #[ignore = "working"] fn test_crypto_prime() { - test_js_file("test/fs/test-crypto-prime.js"); + test_js_file("test/crypto/test-crypto-prime.js"); } #[test] #[ignore = "working"] fn test_crypto_private_decrypt_gh32240() { - test_js_file("test/fs/test-crypto-private-decrypt-gh32240.js"); + test_js_file("test/crypto/test-crypto-private-decrypt-gh32240.js"); } #[test] #[ignore = "working"] fn test_crypto_psychic_signatures() { - test_js_file("test/fs/test-crypto-psychic-signatures.js"); + test_js_file("test/crypto/test-crypto-psychic-signatures.js"); } #[test] -#[ignore = "working"] fn test_crypto_randomfillsync_regression() { - test_js_file("test/fs/test-crypto-randomfillsync-regression.js"); + test_js_file("test/crypto/test-crypto-randomfillsync-regression.js"); } #[test] #[ignore = "working"] fn test_crypto_random() { - test_js_file("test/fs/test-crypto-random.js"); + test_js_file("test/crypto/test-crypto-random.js"); } #[test] -#[ignore = "working"] fn test_crypto_randomuuid() { - test_js_file("test/fs/test-crypto-randomuuid.js"); + test_js_file("test/crypto/test-crypto-randomuuid.js"); } #[test] #[ignore = "working"] fn test_crypto_rsa_dsa() { - test_js_file("test/fs/test-crypto-rsa-dsa.js"); + test_js_file("test/crypto/test-crypto-rsa-dsa.js"); } #[test] #[ignore = "working"] fn test_crypto_scrypt() { - test_js_file("test/fs/test-crypto-scrypt.js"); + test_js_file("test/crypto/test-crypto-scrypt.js"); } #[test] #[ignore = "working"] fn test_crypto_secret_keygen() { - test_js_file("test/fs/test-crypto-secret-keygen.js"); + test_js_file("test/crypto/test-crypto-secret-keygen.js"); } #[test] #[ignore = "working"] fn test_crypto_secure_heap() { - test_js_file("test/fs/test-crypto-secure-heap.js"); + test_js_file("test/crypto/test-crypto-secure-heap.js"); } #[test] #[ignore = "working"] fn test_crypto_sign_verify() { - test_js_file("test/fs/test-crypto-sign-verify.js"); + test_js_file("test/crypto/test-crypto-sign-verify.js"); } #[test] #[ignore = "working"] fn test_crypto_stream() { - test_js_file("test/fs/test-crypto-stream.js"); + test_js_file("test/crypto/test-crypto-stream.js"); } #[test] #[ignore = "working"] fn test_crypto_subtle_zero_length() { - test_js_file("test/fs/test-crypto-subtle-zero-length.js"); + 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/fs/test-crypto-update-encoding.js"); + test_js_file("test/crypto/test-crypto-update-encoding.js"); } #[test] #[ignore = "working"] fn test_crypto_verify_failure() { - test_js_file("test/fs/test-crypto-verify-failure.js"); + test_js_file("test/crypto/test-crypto-verify-failure.js"); } #[test] #[ignore = "working"] fn test_crypto_webcrypto_aes_decrypt_tag_too_small() { - test_js_file("test/fs/test-crypto-webcrypto-aes-decrypt-tag-too-small.js"); + test_js_file("test/crypto/test-crypto-webcrypto-aes-decrypt-tag-too-small.js"); } #[test] #[ignore = "unsupport, worker thread"] fn test_crypto_worker_thread() { - test_js_file("test/fs/test-crypto-worker-thread.js"); + test_js_file("test/crypto/test-crypto-worker-thread.js"); } #[test] #[ignore = "working"] fn test_crypto_x509() { - test_js_file("test/fs/test-crypto-x509.js"); + test_js_file("test/crypto/test-crypto-x509.js"); } diff --git a/tests/test-path.rs b/tests/test-path.rs index 084d565..8059a57 100644 --- a/tests/test-path.rs +++ b/tests/test-path.rs @@ -12,9 +12,6 @@ fn test_js_file(file_path: &str) { Ok(code) => { ctx.put_args(vec![file_path.clone()]); ctx.eval_module_str(code, &file_path); - if let JsValue::Bool(false) = ctx.get_global().get("assertPass") { - assert!(false, "js assert fail"); - } } Err(e) => { eprintln!("{}", e.to_string()); @@ -22,6 +19,17 @@ fn test_js_file(file_path: &str) { } } ctx.js_loop().unwrap(); + if let JsValue::Function(func) = ctx.get_global().get("_onExit") { + func.call(&[]); + } + ctx.js_loop().unwrap(); + if let JsValue::Function(func) = ctx.get_global().get("commonExitCheck") { + func.call(&[]); + } + ctx.js_loop().unwrap(); + if let JsValue::Bool(false) = ctx.get_global().get("assertPass") { + assert!(false, "js assert fail"); + } }); } From d4b7db1da519b59be6983f054edea76f4f619d01 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Wed, 14 Dec 2022 21:13:05 +0800 Subject: [PATCH 04/25] impl: crypto-random --- modules/internal/crypto/random.js | 6 ++++-- tests/test-crypto.rs | 15 +++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/modules/internal/crypto/random.js b/modules/internal/crypto/random.js index 0abd6e8..adcd730 100644 --- a/modules/internal/crypto/random.js +++ b/modules/internal/crypto/random.js @@ -12,6 +12,8 @@ import { isArrayBufferView, isAnyArrayBuffer, isTypedArray, isFloat32Array, isFl import { random_fill } from "_node:crypto"; +import process from "process"; + const kMaxInt32 = 2 ** 31 - 1; const kMaxPossibleLength = Math.min(kMaxLength, kMaxInt32); @@ -84,7 +86,7 @@ function randomFillSync(buf, offset = 0, size) { if (size === 0) return buf; - random_fill(buf.buffer, offset + (buf.byteOffset ?? 0), size); + random_fill(buf.buffer ?? buf, offset + (buf.byteOffset ?? 0), size); return buf; } @@ -124,7 +126,7 @@ function randomFill(buf, offset, size, callback) { } setTimeout(() => { - random_fill(buf.buffer, offset + (buf.byteOffset ?? 0), size); + random_fill(buf.buffer ?? buf, offset + (buf.byteOffset ?? 0), size); callback(null, buf); }, 0); } diff --git a/tests/test-crypto.rs b/tests/test-crypto.rs index 1bd948c..b2f53e8 100644 --- a/tests/test-crypto.rs +++ b/tests/test-crypto.rs @@ -243,8 +243,8 @@ fn test_crypto_padding() { fn test_crypto_pbkdf2() { test_js_file("test/crypto/test-crypto-pbkdf2.js"); } -#[test] -#[ignore = "working"] + +#[ignore = "unsupported, prime"] fn test_crypto_prime() { test_js_file("test/crypto/test-crypto-prime.js"); } @@ -263,7 +263,6 @@ fn test_crypto_randomfillsync_regression() { test_js_file("test/crypto/test-crypto-randomfillsync-regression.js"); } #[test] -#[ignore = "working"] fn test_crypto_random() { test_js_file("test/crypto/test-crypto-random.js"); } @@ -286,8 +285,8 @@ fn test_crypto_scrypt() { fn test_crypto_secret_keygen() { test_js_file("test/crypto/test-crypto-secret-keygen.js"); } -#[test] -#[ignore = "working"] + +#[ignore = "unsupported, child_process"] fn test_crypto_secure_heap() { test_js_file("test/crypto/test-crypto-secure-heap.js"); } @@ -320,12 +319,12 @@ fn test_crypto_update_encoding() { fn test_crypto_verify_failure() { test_js_file("test/crypto/test-crypto-verify-failure.js"); } -#[test] -#[ignore = "working"] + +#[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"); } -#[test] + #[ignore = "unsupport, worker thread"] fn test_crypto_worker_thread() { test_js_file("test/crypto/test-crypto-worker-thread.js"); From 1b1f7364e7690898e19663205e634e44102adf95 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Wed, 14 Dec 2022 23:43:39 +0800 Subject: [PATCH 05/25] impl: pdkdf2_internal --- modules/crypto.js | 12 +- modules/internal/crypto/hashnames.js | 79 +++++ modules/internal/crypto/pbkdf2.js | 123 ++++++++ modules/internal/crypto/random.js | 9 +- modules/internal/crypto/util.js | 411 +++++++++++++++++++++++++++ modules/internal/errors.js | 9 + modules/internal/util.js | 6 + modules/internal/validators.js | 23 +- src/internal_module/crypto.rs | 45 +++ 9 files changed, 704 insertions(+), 13 deletions(-) create mode 100644 modules/internal/crypto/hashnames.js create mode 100644 modules/internal/crypto/pbkdf2.js create mode 100644 modules/internal/crypto/util.js diff --git a/modules/crypto.js b/modules/crypto.js index 2140c5d..4554d0c 100644 --- a/modules/crypto.js +++ b/modules/crypto.js @@ -32,8 +32,8 @@ import { randomInt, randomUUID, } from "./internal/crypto/random"; -/*import { pbkdf2, pbkdf2Sync } from "./internal/crypto/pbkdf2"; -import { scrypt, scryptSync } from "./internal/crypto/scrypt"; +import { pbkdf2, pbkdf2Sync } from "./internal/crypto/pbkdf2"; +/*import { scrypt, scryptSync } from "./internal/crypto/scrypt"; import { hkdf, hkdfSync } from "./internal/crypto/hkdf"; import { generateKey, @@ -189,9 +189,9 @@ export default { hkdf, hkdfSync, Hmac, - KeyObject, + KeyObject,*/ pbkdf2, - pbkdf2Sync, + pbkdf2Sync,/* privateDecrypt, privateEncrypt, publicDecrypt, @@ -254,9 +254,9 @@ export { hkdf, hkdfSync, Hmac, - KeyObject, + KeyObject,*/ pbkdf2, - pbkdf2Sync, + pbkdf2Sync,/* privateDecrypt, privateEncrypt, publicDecrypt, diff --git a/modules/internal/crypto/hashnames.js b/modules/internal/crypto/hashnames.js new file mode 100644 index 0000000..e9de697 --- /dev/null +++ b/modules/internal/crypto/hashnames.js @@ -0,0 +1,79 @@ +'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/pbkdf2.js b/modules/internal/crypto/pbkdf2.js new file mode 100644 index 0000000..2ed3014 --- /dev/null +++ b/modules/internal/crypto/pbkdf2.js @@ -0,0 +1,123 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +'use strict'; + + +import { Buffer } from 'buffer'; + +import { validateFunction, validateInteger, validateString, validateUint32 } from '../validators'; + +import { codes } from '../errors'; +const { ERR_MISSING_OPTION } = codes; + +import { getArrayBufferOrView, getDefaultEncoding, normalizeHashName, kKeyObject } from './util'; + +import { lazyDOMException } from '../util'; + +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'); + + const job = new PBKDF2Job( + kCryptoJobAsync, + password, + salt, + iterations, + keylen, + digest); + + const encoding = getDefaultEncoding(); + job.ondone = (err, result) => { + if (err !== undefined) + return Function.prototype.call.call(callback, job, err); + const buf = Buffer.from(result); + if (encoding === 'buffer') + return Function.prototype.call.call(callback, job, null, buf); + Function.prototype.call.call(callback, job, null, buf.toString(encoding)); + }; + + job.run(); +} + +function pbkdf2Sync(password, salt, iterations, keylen, digest) { + ({ password, salt, iterations, keylen, digest } = + check(password, salt, iterations, keylen, digest)); + + const job = new PBKDF2Job( + kCryptoJobSync, + password, + salt, + iterations, + keylen, + digest); + + const { 0: err, 1: result } = job.run(); + if (err !== undefined) + throw err; + + 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 }; +} + +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 index adcd730..e1ba6f8 100644 --- a/modules/internal/crypto/random.js +++ b/modules/internal/crypto/random.js @@ -1,3 +1,4 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. 'use strict'; import { kEmptyObject } from '../util'; @@ -10,6 +11,8 @@ import { validateNumber, validateBoolean, validateFunction, validateInt32, valid import { isArrayBufferView, isAnyArrayBuffer, isTypedArray, isFloat32Array, isFloat64Array } from '../util/types'; +import { lazyDOMException } from '../util'; + import { random_fill } from "_node:crypto"; import process from "process"; @@ -241,12 +244,6 @@ function asyncRefillRandomIntCache() { }); } -function lazyDOMException(msg, name) { - let e = new Error(msg) - e.name = name; - return e; -} - // Really just the Web Crypto API alternative // to require('crypto').randomFillSync() with an // additional limitation that the input buffer is diff --git a/modules/internal/crypto/util.js b/modules/internal/crypto/util.js new file mode 100644 index 0000000..a307509 --- /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 = cachedResult(() => filterDuplicateStrings(_getCiphers())); +const getHashes = cachedResult(() => filterDuplicateStrings(_getHashes())); +const getCurves = cachedResult(() => filterDuplicateStrings(_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/errors.js b/modules/internal/errors.js index 69ada9f..c5b6e5c 100644 --- a/modules/internal/errors.js +++ b/modules/internal/errors.js @@ -843,3 +843,12 @@ export class ERR_OPERATION_FAILED extends Error { 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"; + } +} diff --git a/modules/internal/util.js b/modules/internal/util.js index 6ae6501..b18e561 100644 --- a/modules/internal/util.js +++ b/modules/internal/util.js @@ -162,6 +162,12 @@ export function isError(e) { export const kEmptyObject = Object.freeze(Object.create(null)); +export function lazyDOMException(msg, name) { + let e = new Error(msg) + e.name = name; + return e; +} + export default { createDeferredPromise, customInspectSymbol, diff --git a/modules/internal/validators.js b/modules/internal/validators.js index 36f79c4..1327f06 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) { @@ -214,6 +215,25 @@ export function validateNumber(value, name, min = undefined, max) { } } +/** + * @callback validateArray + * @param {*} value + * @param {string} name + * @param {number} [minLength] + * @returns {asserts value is any[]} + */ + +/** @type {validateArray} */ +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); + } +}); + export default { validatePort, validateFunction, @@ -224,5 +244,6 @@ export default { validateCallback, validateInteger, validateNumber, + validateArray, getValidMode } \ No newline at end of file diff --git a/src/internal_module/crypto.rs b/src/internal_module/crypto.rs index 4dba849..ebdcae3 100644 --- a/src/internal_module/crypto.rs +++ b/src/internal_module/crypto.rs @@ -77,6 +77,51 @@ fn random_fill(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsVal JsValue::UnDefined } +use wasi_crypto_guest::error::Error; +use wasi_crypto_guest::symmetric::*; + +fn pbkdf2( + alg: &'static str, + password: &[u8], + salt: &[u8], + iters: usize, + key_len: usize, +) -> Result, Error> { + let tag_len = match alg { + "HMAC/SHA-256" => 32, + "HMAC/SHA-512" => 64, + _ => unreachable!(), + }; + fn pass(alg: &'static str, key: &[u8], salt: &[u8]) -> Result, Error> { + let mut h = SymmetricState::new(alg, Some(&SymmetricKey::from_raw(alg, key)?), None)?; + h.absorb(salt)?; + h.squeeze_tag() + } + let res = (0..(key_len + tag_len - 1) / tag_len) + .map(|idx| -> Result, Error> { + let mut salt_2 = salt.to_vec(); + let idx = idx + 1; + salt_2.push(((idx >> 24) & 0xff) as u8); + salt_2.push(((idx >> 16) & 0xff) as u8); + salt_2.push(((idx >> 8) & 0xff) as u8); + salt_2.push(((idx) & 0xff) as u8); + let mut res_t = pass(alg, password, &salt_2)?; + let mut res_u = res_t.clone(); + for _ in 0..iters - 1 { + res_u = pass(alg, password, &res_u)?; + for k in 0..res_t.len() { + res_t[k] ^= res_u[k]; + } + } + Ok(res_t) + }) + .filter_map(|v| v.ok()) + .flatten() + .take(key_len) + .collect::>(); + Ok(res) +} + struct Crypto; impl ModuleInit for Crypto { From 8c5fdcb8626528a18dc04734b4090aa95135abc9 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Thu, 15 Dec 2022 21:31:06 +0800 Subject: [PATCH 06/25] test: crypto-pbkdf2 --- modules/internal/crypto/pbkdf2.js | 53 +++++++++++++------------------ modules/internal/crypto/util.js | 18 +++++------ modules/internal/errors.js | 6 ++++ modules/internal/util.js | 27 +++++++++++++++- modules/internal/validators.js | 2 +- src/internal_module/crypto.rs | 37 +++++++++++++++++++-- test/crypto/test-crypto-pbkdf2.js | 10 +++--- tests/test-crypto.rs | 1 - 8 files changed, 104 insertions(+), 50 deletions(-) diff --git a/modules/internal/crypto/pbkdf2.js b/modules/internal/crypto/pbkdf2.js index 2ed3014..e6b54ca 100644 --- a/modules/internal/crypto/pbkdf2.js +++ b/modules/internal/crypto/pbkdf2.js @@ -6,14 +6,15 @@ import { Buffer } from 'buffer'; import { validateFunction, validateInteger, validateString, validateUint32 } from '../validators'; -import { codes } from '../errors'; -const { ERR_MISSING_OPTION } = codes; +import { ERR_CRYPTO_INVALID_DIGEST, ERR_MISSING_OPTION } from '../errors'; import { getArrayBufferOrView, getDefaultEncoding, normalizeHashName, kKeyObject } from './util'; import { lazyDOMException } from '../util'; -function pbkdf2(password, salt, iterations, keylen, digest, callback) { +import { pbkdf2_sync } from "_node:crypto"; + +export function pbkdf2(password, salt, iterations, keylen, digest, callback) { if (typeof digest === 'function') { callback = digest; digest = undefined; @@ -24,42 +25,32 @@ function pbkdf2(password, salt, iterations, keylen, digest, callback) { validateFunction(callback, 'callback'); - const job = new PBKDF2Job( - kCryptoJobAsync, - password, - salt, - iterations, - keylen, - digest); + if (!["SHA256", "SHA512"].includes(digest.toUpperCase())) { + throw new ERR_CRYPTO_INVALID_DIGEST(digest); + } const encoding = getDefaultEncoding(); - job.ondone = (err, result) => { - if (err !== undefined) - return Function.prototype.call.call(callback, job, err); - const buf = Buffer.from(result); - if (encoding === 'buffer') - return Function.prototype.call.call(callback, job, null, buf); - Function.prototype.call.call(callback, job, null, buf.toString(encoding)); - }; - job.run(); + 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); } -function pbkdf2Sync(password, salt, iterations, keylen, digest) { +export function pbkdf2Sync(password, salt, iterations, keylen, digest) { ({ password, salt, iterations, keylen, digest } = check(password, salt, iterations, keylen, digest)); - const job = new PBKDF2Job( - kCryptoJobSync, - password, - salt, - iterations, - keylen, - digest); + if (!["SHA256", "SHA512"].includes(digest.toUpperCase())) { + throw new ERR_CRYPTO_INVALID_DIGEST(digest); + } - const { 0: err, 1: result } = job.run(); - if (err !== undefined) - throw err; + let result = pbkdf2_sync(password.buffer ?? password, salt.buffer ?? salt, iterations, keylen, digest.toUpperCase()); const buf = Buffer.from(result); const encoding = getDefaultEncoding(); @@ -77,7 +68,7 @@ function check(password, salt, iterations, keylen, digest) { return { password, salt, iterations, keylen, digest }; } -async function pbkdf2DeriveBits(algorithm, baseKey, length) { +export async function pbkdf2DeriveBits(algorithm, baseKey, length) { const { iterations } = algorithm; let { hash } = algorithm; const salt = getArrayBufferOrView(algorithm.salt, 'algorithm.salt'); diff --git a/modules/internal/crypto/util.js b/modules/internal/crypto/util.js index a307509..d39da12 100644 --- a/modules/internal/crypto/util.js +++ b/modules/internal/crypto/util.js @@ -1,13 +1,13 @@ // Copyright Joyent and Node contributors. All rights reserved. MIT license. 'use strict'; -const { +/*const { getCiphers: _getCiphers, getCurves: _getCurves, getHashes: _getHashes, setEngine: _setEngine, secureHeapUsed: _secureHeapUsed, -} = internalBinding('crypto'); +} = internalBinding('crypto');*/ import { getOptionValue } from '../options'; @@ -67,9 +67,9 @@ function toBuf(val, encoding) { return val; } -const getCiphers = cachedResult(() => filterDuplicateStrings(_getCiphers())); -const getHashes = cachedResult(() => filterDuplicateStrings(_getHashes())); -const getCurves = cachedResult(() => filterDuplicateStrings(_getCurves())); +const getCiphers = {}; //cachedResult(() => filterDuplicateStrings(_getCiphers())); +const getHashes = {}; //cachedResult(() => filterDuplicateStrings(_getHashes())); +const getCurves = {}; //cachedResult(() => filterDuplicateStrings(_getCurves())); function setEngine(id, flags) { validateString(id, 'id'); @@ -81,8 +81,8 @@ function setEngine(id, flags) { if (flags === 0) flags = ENGINE_METHOD_ALL; - if (!_setEngine(id, flags)) - throw new ERR_CRYPTO_ENGINE_UNKNOWN(id); + /*if (!_setEngine(id, flags)) + throw new ERR_CRYPTO_ENGINE_UNKNOWN(id);*/ } const getArrayBufferOrView = hideStackFrames((buffer, name, encoding) => { @@ -367,14 +367,14 @@ function validateKeyOps(keyOps, usagesSet) { } function secureHeapUsed() { - const val = _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 }; + return { total, used, utilization, min };*/ } export { diff --git a/modules/internal/errors.js b/modules/internal/errors.js index c5b6e5c..6c29216 100644 --- a/modules/internal/errors.js +++ b/modules/internal/errors.js @@ -852,3 +852,9 @@ export class ERR_CRYPTO_ENGINE_UNKNOWN extends Error { 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"; + } +} \ No newline at end of file diff --git a/modules/internal/util.js b/modules/internal/util.js index b18e561..40b527e 100644 --- a/modules/internal/util.js +++ b/modules/internal/util.js @@ -168,6 +168,29 @@ export function lazyDOMException(msg, 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, customInspectSymbol, @@ -178,5 +201,7 @@ export default { promisify, removeColors, isError, - kEmptyObject + kEmptyObject, + cachedResult, + filterDuplicateStrings }; \ No newline at end of file diff --git a/modules/internal/validators.js b/modules/internal/validators.js index 1327f06..39dd2ea 100644 --- a/modules/internal/validators.js +++ b/modules/internal/validators.js @@ -224,7 +224,7 @@ export function validateNumber(value, name, min = undefined, max) { */ /** @type {validateArray} */ -const validateArray = hideStackFrames((value, name, minLength = 0) => { +export const validateArray = hideStackFrames((value, name, minLength = 0) => { if (!Array.isArray(value)) { throw new ERR_INVALID_ARG_TYPE(name, 'Array', value); } diff --git a/src/internal_module/crypto.rs b/src/internal_module/crypto.rs index ebdcae3..05bb1cd 100644 --- a/src/internal_module/crypto.rs +++ b/src/internal_module/crypto.rs @@ -51,7 +51,6 @@ fn timing_safe_equal(_ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) - } fn random_fill(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { - println!("{:?}", argv); if let Some(JsValue::ArrayBuffer(buf)) = argv.get(0) { if let Some(JsValue::Int(offset)) = argv.get(1) { if let Some(JsValue::Int(size)) = argv.get(2) { @@ -122,6 +121,36 @@ fn pbkdf2( Ok(res) } +fn pbkdf2_sync(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { + if let Some(JsValue::ArrayBuffer(password)) = argv.get(0) { + if let Some(JsValue::ArrayBuffer(salt)) = argv.get(1) { + if let Some(JsValue::Int(iters)) = argv.get(2) { + if let Some(JsValue::Int(key_len)) = argv.get(3) { + if let Some(JsValue::String(alg)) = argv.get(4) { + return match { + pbkdf2(match alg.as_str() { + "SHA256" => "HMAC/SHA-256", + "SHA512" => "HMAC/SHA-512", + _ => unreachable!() + }, password.as_ref(), salt.as_ref(), *iters as usize, *key_len as usize) + } { + Ok(res) => { + println!("{:?}", res.to_vec()); + ctx.new_array_buffer(res.as_slice()).into() + } + Err(_e) => { + // TODO + JsValue::UnDefined + } + }; + } + } + } + } + } + JsValue::UnDefined +} + struct Crypto; impl ModuleInit for Crypto { @@ -135,6 +164,10 @@ impl ModuleInit for Crypto { "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(), + ); } } @@ -142,6 +175,6 @@ pub fn init_module(ctx: &mut Context) { ctx.register_module( "_node:crypto\0", Crypto, - &["timing_safe_equal\0", "random_fill\0"], + &["timing_safe_equal\0", "random_fill\0", "pbkdf2_sync\0"], ) } diff --git a/test/crypto/test-crypto-pbkdf2.js b/test/crypto/test-crypto-pbkdf2.js index 533a4f0..fad242d 100644 --- a/test/crypto/test-crypto-pbkdf2.js +++ b/test/crypto/test-crypto-pbkdf2.js @@ -1,12 +1,12 @@ // Copyright Joyent and Node contributors. All rights reserved. MIT license. 'use strict'; -const common = require('../common'); +import common from '../common'; if (!common.hasCrypto) common.skip('missing crypto'); -const assert = require('assert'); -const crypto = require('crypto'); +import assert from 'assert'; +import crypto from 'crypto'; function runPBKDF2(password, salt, iterations, keylen, hash) { const syncResult = @@ -221,14 +221,14 @@ assert.throws( } ); -if (!common.hasOpenSSL3) { +/*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. diff --git a/tests/test-crypto.rs b/tests/test-crypto.rs index b2f53e8..bbe9a86 100644 --- a/tests/test-crypto.rs +++ b/tests/test-crypto.rs @@ -239,7 +239,6 @@ fn test_crypto_padding() { test_js_file("test/crypto/test-crypto-padding.js"); } #[test] -#[ignore = "working"] fn test_crypto_pbkdf2() { test_js_file("test/crypto/test-crypto-pbkdf2.js"); } From 0f92de688d5de95e42b1417b8fc53dc431c70a1e Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Mon, 19 Dec 2022 00:02:56 +0800 Subject: [PATCH 07/25] impl: scrypt_internal --- modules/internal/crypto/scrypt.js | 130 ++++++++++++++++++++++++ modules/internal/errors.js | 17 +++- src/internal_module/crypto.rs | 162 ++++++++++++++++++++++++++++++ 3 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 modules/internal/crypto/scrypt.js diff --git a/modules/internal/crypto/scrypt.js b/modules/internal/crypto/scrypt.js new file mode 100644 index 0000000..68cf5ee --- /dev/null +++ b/modules/internal/crypto/scrypt.js @@ -0,0 +1,130 @@ +// 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'; + +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 job = new ScryptJob( + kCryptoJobAsync, password, salt, N, r, p, maxmem, keylen); + + const encoding = getDefaultEncoding(); + job.ondone = (error, result) => { + if (error !== undefined) + return Function.prototype.call.call(callback, job, error); + const buf = Buffer.from(result); + if (encoding === 'buffer') + return Function.prototype.call.call(callback, job, null, buf); + Function.prototype.call.call(callback, job, null, buf.toString(encoding)); + }; + + job.run(); +} + +function scryptSync(password, salt, keylen, options = defaults) { + options = check(password, salt, keylen, options); + const { N, r, p, maxmem } = options; + ({ password, salt, keylen } = options); + const job = new ScryptJob( + kCryptoJobSync, password, salt, N, r, p, maxmem, keylen); + const { 0: err, 1: result } = job.run(); + + if (err !== undefined) + throw err; + + 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; + } + + return { password, salt, keylen, N, r, p, maxmem }; +} + +module.exports = { + scrypt, + scryptSync, +}; diff --git a/modules/internal/errors.js b/modules/internal/errors.js index 6c29216..da3d886 100644 --- a/modules/internal/errors.js +++ b/modules/internal/errors.js @@ -852,9 +852,24 @@ export class ERR_CRYPTO_ENGINE_UNKNOWN extends Error { 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"; } -} \ No newline at end of file +} + +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"; + } +} diff --git a/src/internal_module/crypto.rs b/src/internal_module/crypto.rs index 05bb1cd..49408bb 100644 --- a/src/internal_module/crypto.rs +++ b/src/internal_module/crypto.rs @@ -151,6 +151,168 @@ fn pbkdf2_sync(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsVal JsValue::UnDefined } +struct ScryptRom { + b: Vec, + r: usize, + n: usize, + p: usize, + xy: Vec, + v: Vec, + b32: Vec, + x: Vec, + xx: Vec, +} + +fn blockxor(a: &[u8], b: &mut [u8]) { + for i in 0..a.len() { + b[i] ^= a[i]; + } +} + +fn read_u32le(p: &[u8]) -> u32 { + (p[0] as u32) + ((p[1] as u32) << 8) + ((p[2] as u32) << 16) + ((p[3] as u32) << 24) +} + +#[inline(always)] +#[allow(non_snake_case)] +fn R(i: i32, r: i32) -> i32 { + i.rotate_left(r as u32) +} + +impl ScryptRom { + fn romix(&mut self, i: usize, r: usize) { + let block_start = i * 128 * r; + let offset = (2 * r - 1) * 64; + let block_len = 128 * r; + self.xy[0..block_len] + .copy_from_slice(&self.b[block_start..(block_start + block_len)]); + for i1 in 0..self.n { + self.v[i1 * block_len..(i1 + 1) * block_len].copy_from_slice(&self.xy[0..block_len]); + self.blockmix(block_len); + } + + for _ in 0..self.n { + let j = read_u32le(&self.xy[offset..]) as usize & (self.n - 1); + blockxor( + &self.v[j * block_len..(j + 1) * block_len], + &mut self.xy[0..block_len], + ); + self.blockmix(block_len); + } + self.b[block_start..block_start + block_len].copy_from_slice(&self.xy[0..block_len]); + } + + fn blockmix(&mut self, block_len: usize) { + self.xx[0..64].copy_from_slice(&self.xy[(2 * self.r - 1) * 64..(2 * self.r) * 64]); + for i in 0..2 * self.r { + blockxor(&self.xy[i * 64..(i + 1) * 64], &mut self.xx[0..64]); + self.salsa20_8(); + self.xy[block_len + (i * 64)..block_len + (i * 64) + 64] + .copy_from_slice(&self.xx[0..64]); + } + for i in 0..self.r { + self.xy.copy_within( + block_len + (i * 2) * 64..block_len + (i * 2) * 64 + 64, + i * 64, + ); + self.xy.copy_within( + block_len + (i * 2 + 1) * 64..block_len + (i * 2 + 1) * 64 + 64, + (i + self.r) * 64, + ); + } + } + + fn salsa20_8(&mut self) { + for i in 0..16 { + self.b32[i] = ((self.xx[i * 4 + 0] & 0xff) as i32) << 0; + self.b32[i] |= ((self.xx[i * 4 + 1] & 0xff) as i32) << 8; + self.b32[i] |= ((self.xx[i * 4 + 2] & 0xff) as i32) << 16; + self.b32[i] |= ((self.xx[i * 4 + 3] & 0xff) as i32) << 24; + } + + self.x.copy_from_slice(&self.b32); + + for _ in 0..4 { + self.x[4] ^= R(self.x[0] + self.x[12], 7); + self.x[8] ^= R(self.x[4] + self.x[0], 9); + self.x[12] ^= R(self.x[8] + self.x[4], 13); + self.x[0] ^= R(self.x[12] + self.x[8], 18); + self.x[9] ^= R(self.x[5] + self.x[1], 7); + self.x[13] ^= R(self.x[9] + self.x[5], 9); + self.x[1] ^= R(self.x[13] + self.x[9], 13); + self.x[5] ^= R(self.x[1] + self.x[13], 18); + self.x[14] ^= R(self.x[10] + self.x[6], 7); + self.x[2] ^= R(self.x[14] + self.x[10], 9); + self.x[6] ^= R(self.x[2] + self.x[14], 13); + self.x[10] ^= R(self.x[6] + self.x[2], 18); + self.x[3] ^= R(self.x[15] + self.x[11], 7); + self.x[7] ^= R(self.x[3] + self.x[15], 9); + self.x[11] ^= R(self.x[7] + self.x[3], 13); + self.x[15] ^= R(self.x[11] + self.x[7], 18); + self.x[1] ^= R(self.x[0] + self.x[3], 7); + self.x[2] ^= R(self.x[1] + self.x[0], 9); + self.x[3] ^= R(self.x[2] + self.x[1], 13); + self.x[0] ^= R(self.x[3] + self.x[2], 18); + self.x[6] ^= R(self.x[5] + self.x[4], 7); + self.x[7] ^= R(self.x[6] + self.x[5], 9); + self.x[4] ^= R(self.x[7] + self.x[6], 13); + self.x[5] ^= R(self.x[4] + self.x[7], 18); + self.x[11] ^= R(self.x[10] + self.x[9], 7); + self.x[8] ^= R(self.x[11] + self.x[10], 9); + self.x[9] ^= R(self.x[8] + self.x[11], 13); + self.x[10] ^= R(self.x[9] + self.x[8], 18); + self.x[12] ^= R(self.x[15] + self.x[14], 7); + self.x[13] ^= R(self.x[12] + self.x[15], 9); + self.x[14] ^= R(self.x[13] + self.x[12], 13); + self.x[15] ^= R(self.x[14] + self.x[13], 18); + } + + for i in 0..16 { + self.b32[i] += self.x[i]; + } + + for i in 0..16 { + self.xx[i * 4 + 0] = (self.b32[i] >> 0 & 0xff) as u8; + self.xx[i * 4 + 1] = (self.b32[i] >> 8 & 0xff) as u8; + self.xx[i * 4 + 2] = (self.b32[i] >> 16 & 0xff) as u8; + self.xx[i * 4 + 3] = (self.b32[i] >> 24 & 0xff) as u8; + } + } +} + +fn scrypt_rom(b: &[u8], r: usize, n: usize, p: usize) -> Result, Error> { + let mut rom = ScryptRom { + b: b.to_vec(), + r, + n, + p, + xy: vec![0; 256 * r], + v: vec![0; 128 * r * n], + b32: vec![0; 16], + x: vec![0; 16], + xx: vec![0; 64], + }; + for i in 0..p { + rom.romix(i, r); + } + Ok(rom.b) +} + +fn scrypt( + password: &[u8], + salt: &[u8], + n: usize, + r: usize, + p: usize, + keylen: usize, +) -> Result, Error> { + let blen = p * 128 * r; + let b = pbkdf2("HMAC/SHA-256", password, salt, 1, blen)?; + let s = scrypt_rom(&b, r, n, p)?; + let f = pbkdf2("HMAC/SHA-256", password, &s, 1, keylen)?; + Ok(f) +} + struct Crypto; impl ModuleInit for Crypto { From eef9d3fb6a0c6c3cd770973ed0e4933da91d05e6 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Tue, 20 Dec 2022 00:32:13 +0800 Subject: [PATCH 08/25] test: crypto-scrypt --- modules/crypto.js | 12 +-- modules/internal/crypto/scrypt.js | 41 +++++----- src/internal_module/crypto.rs | 124 ++++++++++++++++++++---------- test/crypto/test-crypto-scrypt.js | 14 ++-- 4 files changed, 115 insertions(+), 76 deletions(-) diff --git a/modules/crypto.js b/modules/crypto.js index 4554d0c..c5b6ca6 100644 --- a/modules/crypto.js +++ b/modules/crypto.js @@ -33,8 +33,8 @@ import { 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 { scrypt, scryptSync } from "./internal/crypto/scrypt"; +/*import { hkdf, hkdfSync } from "./internal/crypto/hkdf"; import { generateKey, generateKeyPair, @@ -200,9 +200,9 @@ export default { randomFill, randomFillSync, randomInt, - randomUUID,/* + randomUUID, scrypt, - scryptSync, + scryptSync,/* secureHeapUsed, setEngine, setFips, @@ -265,9 +265,9 @@ export { randomFill, randomFillSync, randomInt, - randomUUID,/* + randomUUID, scrypt, - scryptSync, + scryptSync,/* secureHeapUsed, setEngine,*/ setFips, diff --git a/modules/internal/crypto/scrypt.js b/modules/internal/crypto/scrypt.js index 68cf5ee..1525b87 100644 --- a/modules/internal/crypto/scrypt.js +++ b/modules/internal/crypto/scrypt.js @@ -20,6 +20,8 @@ import { getDefaultEncoding, } from './util'; +import { scrypt_sync } from "_node:crypto"; + const defaults = { N: 16384, r: 8, @@ -38,33 +40,24 @@ function scrypt(password, salt, keylen, options, callback = defaults) { ({ password, salt, keylen } = options); validateFunction(callback, 'callback'); - - const job = new ScryptJob( - kCryptoJobAsync, password, salt, N, r, p, maxmem, keylen); - const encoding = getDefaultEncoding(); - job.ondone = (error, result) => { - if (error !== undefined) - return Function.prototype.call.call(callback, job, error); + setTimeout(() => { + let result = scrypt_sync(password.buffer ?? password, salt.buffer ?? salt, N, r, p, keylen); const buf = Buffer.from(result); - if (encoding === 'buffer') - return Function.prototype.call.call(callback, job, null, buf); - Function.prototype.call.call(callback, job, null, buf.toString(encoding)); - }; - - job.run(); + 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); - const job = new ScryptJob( - kCryptoJobSync, password, salt, N, r, p, maxmem, keylen); - const { 0: err, 1: result } = job.run(); - if (err !== undefined) - throw err; + let result = scrypt_sync(password.buffer ?? password, salt.buffer ?? salt, N, r, p, keylen); const buf = Buffer.from(result); const encoding = getDefaultEncoding(); @@ -121,10 +114,20 @@ function check(password, salt, keylen, options) { 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 }; } -module.exports = { +export { scrypt, scryptSync, }; diff --git a/src/internal_module/crypto.rs b/src/internal_module/crypto.rs index 49408bb..407560b 100644 --- a/src/internal_module/crypto.rs +++ b/src/internal_module/crypto.rs @@ -61,10 +61,7 @@ fn random_fill(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsVal (buf_len - *offset as usize).min(*size as usize), ) } { - Ok(()) => { - println!("{:?}", buf.to_vec()); - JsValue::UnDefined - } + Ok(()) => JsValue::UnDefined, Err(e) => { let err = super::fs::errno_to_js_object(ctx, e); JsValue::Exception(ctx.throw_error(err)) @@ -121,34 +118,41 @@ fn pbkdf2( Ok(res) } +macro_rules! get_arg { + ($argv:ident, $m:path, $i:expr) => { + if let Some($m(val)) = $argv.get($i) { + val + } else { + return JsValue::UnDefined; + } + }; +} + fn pbkdf2_sync(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { - if let Some(JsValue::ArrayBuffer(password)) = argv.get(0) { - if let Some(JsValue::ArrayBuffer(salt)) = argv.get(1) { - if let Some(JsValue::Int(iters)) = argv.get(2) { - if let Some(JsValue::Int(key_len)) = argv.get(3) { - if let Some(JsValue::String(alg)) = argv.get(4) { - return match { - pbkdf2(match alg.as_str() { - "SHA256" => "HMAC/SHA-256", - "SHA512" => "HMAC/SHA-512", - _ => unreachable!() - }, password.as_ref(), salt.as_ref(), *iters as usize, *key_len as usize) - } { - Ok(res) => { - println!("{:?}", res.to_vec()); - ctx.new_array_buffer(res.as_slice()).into() - } - Err(_e) => { - // TODO - JsValue::UnDefined - } - }; - } - } - } + 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( + match alg.as_str() { + "SHA256" => "HMAC/SHA-256", + "SHA512" => "HMAC/SHA-512", + _ => unreachable!(), + }, + password.as_ref(), + salt.as_ref(), + *iters as usize, + *key_len as usize, + ) + } { + Ok(res) => ctx.new_array_buffer(res.as_slice()).into(), + Err(_e) => { + // TODO + JsValue::UnDefined } } - JsValue::UnDefined } struct ScryptRom { @@ -169,28 +173,21 @@ fn blockxor(a: &[u8], b: &mut [u8]) { } } -fn read_u32le(p: &[u8]) -> u32 { - (p[0] as u32) + ((p[1] as u32) << 8) + ((p[2] as u32) << 16) + ((p[3] as u32) << 24) -} - -#[inline(always)] -#[allow(non_snake_case)] -fn R(i: i32, r: i32) -> i32 { - i.rotate_left(r as u32) -} - impl ScryptRom { fn romix(&mut self, i: usize, r: usize) { let block_start = i * 128 * r; let offset = (2 * r - 1) * 64; let block_len = 128 * r; - self.xy[0..block_len] - .copy_from_slice(&self.b[block_start..(block_start + block_len)]); + self.xy[0..block_len].copy_from_slice(&self.b[block_start..(block_start + block_len)]); for i1 in 0..self.n { self.v[i1 * block_len..(i1 + 1) * block_len].copy_from_slice(&self.xy[0..block_len]); self.blockmix(block_len); } + fn read_u32le(p: &[u8]) -> u32 { + (p[0] as u32) + ((p[1] as u32) << 8) + ((p[2] as u32) << 16) + ((p[3] as u32) << 24) + } + for _ in 0..self.n { let j = read_u32le(&self.xy[offset..]) as usize & (self.n - 1); blockxor( @@ -223,6 +220,12 @@ impl ScryptRom { } fn salsa20_8(&mut self) { + #[inline(always)] + #[allow(non_snake_case)] + fn R(i: i32, r: i32) -> i32 { + i.rotate_left(r as u32) + } + for i in 0..16 { self.b32[i] = ((self.xx[i * 4 + 0] & 0xff) as i32) << 0; self.b32[i] |= ((self.xx[i * 4 + 1] & 0xff) as i32) << 8; @@ -313,6 +316,34 @@ fn scrypt( Ok(f) } +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) => { + // TODO + JsValue::UnDefined + } + } +} + struct Crypto; impl ModuleInit for Crypto { @@ -330,6 +361,10 @@ impl ModuleInit for Crypto { "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(), + ); } } @@ -337,6 +372,11 @@ pub fn init_module(ctx: &mut Context) { ctx.register_module( "_node:crypto\0", Crypto, - &["timing_safe_equal\0", "random_fill\0", "pbkdf2_sync\0"], + &[ + "timing_safe_equal\0", + "random_fill\0", + "pbkdf2_sync\0", + "scrypt_sync\0", + ], ) } diff --git a/test/crypto/test-crypto-scrypt.js b/test/crypto/test-crypto-scrypt.js index 5fddea7..ee63873 100644 --- a/test/crypto/test-crypto-scrypt.js +++ b/test/crypto/test-crypto-scrypt.js @@ -2,16 +2,12 @@ // Flags: --expose-internals 'use strict'; -const common = require('../common'); +import common from '../common'; if (!common.hasCrypto) common.skip('missing crypto'); -const assert = require('assert'); -const crypto = require('crypto'); - -const { internalBinding } = require('internal/test/binding'); -if (typeof internalBinding('crypto').ScryptJob !== 'function') - common.skip('no scrypt support'); +import assert from'assert'; +import crypto from 'crypto'; const good = [ // Zero-length key is legal, functions as a parameter validation check. @@ -189,7 +185,7 @@ for (const options of toobig) { assert.deepStrictEqual(actual.toString('hex'), expected.toString('hex')); })); } - +/* deprecated { const defaultEncoding = crypto.DEFAULT_ENCODING; const defaults = { N: 16384, p: 1, r: 8 }; @@ -206,7 +202,7 @@ for (const options of toobig) { crypto.DEFAULT_ENCODING = defaultEncoding; } - +*/ for (const { args, expected } of badargs) { assert.throws(() => crypto.scrypt(...args), expected); assert.throws(() => crypto.scryptSync(...args), expected); From 7d528863837cbd9306d02171d51990bc23fc32b9 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Tue, 20 Dec 2022 23:29:55 +0800 Subject: [PATCH 09/25] impl: hkdf-internal --- modules/crypto.js | 26 +- modules/internal/crypto/hashnames.js | 1 + modules/internal/crypto/hkdf.js | 170 ++++++ modules/internal/crypto/keys.js | 738 +++++++++++++++++++++++++++ modules/internal/crypto/util.js | 6 +- modules/internal/errors.js | 28 + modules/internal/validators.js | 23 +- src/internal_module/crypto.rs | 13 + test/common.js | 3 +- test/crypto/test-crypto-hkdf.js | 10 +- tests/test-crypto.rs | 4 +- 11 files changed, 996 insertions(+), 26 deletions(-) create mode 100644 modules/internal/crypto/hkdf.js create mode 100644 modules/internal/crypto/keys.js diff --git a/modules/crypto.js b/modules/crypto.js index c5b6ca6..dcb8cf9 100644 --- a/modules/crypto.js +++ b/modules/crypto.js @@ -71,13 +71,13 @@ import { } from "./internal/crypto/sig"; import { createHash, Hash, Hmac } from "./internal/crypto/hash"; import { X509Certificate } from "./internal/crypto/x509"; -import { +*/import { getCiphers, getCurves, getHashes, secureHeapUsed, setEngine, -} from "./internal/crypto/util"; +} from "./internal/crypto/util";/* import Certificate from "./internal/crypto/certificate"; */ const webcrypto = undefined; @@ -179,12 +179,12 @@ export default { generateKeySync,*/ generatePrime, generatePrimeSync,/* - getCipherInfo, + getCipherInfo,*/ getCiphers, - getCurves, - getDiffieHellman, + getCurves,/* + getDiffieHellman,*/ getFips, - getHashes, + getHashes,/* Hash, hkdf, hkdfSync, @@ -202,10 +202,10 @@ export default { randomInt, randomUUID, scrypt, - scryptSync,/* + scryptSync, secureHeapUsed, setEngine, - setFips, + setFips,/* Sign, sign,*/ timingSafeEqual, @@ -244,12 +244,12 @@ export { generateKeySync,*/ generatePrime, generatePrimeSync,/* - getCipherInfo, + getCipherInfo,*/ getCiphers, - getCurves, + getCurves,/* getDiffieHellman,*/ getFips, - /*getHashes, + getHashes,/* Hash, hkdf, hkdfSync, @@ -267,9 +267,9 @@ export { randomInt, randomUUID, scrypt, - scryptSync,/* + scryptSync, secureHeapUsed, - setEngine,*/ + setEngine, setFips, /*Sign, sign,*/ diff --git a/modules/internal/crypto/hashnames.js b/modules/internal/crypto/hashnames.js index e9de697..5153f65 100644 --- a/modules/internal/crypto/hashnames.js +++ b/modules/internal/crypto/hashnames.js @@ -1,3 +1,4 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. 'use strict'; const kHashContextNode = 1; diff --git a/modules/internal/crypto/hkdf.js b/modules/internal/crypto/hkdf.js new file mode 100644 index 0000000..f7df3f3 --- /dev/null +++ b/modules/internal/crypto/hkdf.js @@ -0,0 +1,170 @@ +// 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, +} 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, +} from '../errors'; + +const validateParameters = hideStackFrames((hash, key, salt, info, length) => { + validateString(hash, 'digest'); + key = prepareKey(key); + salt = validateByteSource(salt, 'salt'); + info = validateByteSource(info, 'info'); + + validateInteger(length, 'length', 0, kMaxLength); + + if (info.byteLength > 1024) { + throw ERR_OUT_OF_RANGE( + 'info', + 'must not contain more than 1024 bytes', + info.byteLength); + } + + 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'); + + const job = new HKDFJob(kCryptoJobAsync, hash, key, salt, info, length); + + job.ondone = (error, bits) => { + if (error) return Function.prototype.call.call(callback, job, error); + Function.prototype.call.call(callback, job, null, bits); + }; + + job.run(); +} + +function hkdfSync(hash, key, salt, info, length) { + ({ + hash, + key, + salt, + info, + length, + } = validateParameters(hash, key, salt, info, length)); + + const job = new HKDFJob(kCryptoJobSync, hash, key, salt, info, length); + const { 0: err, 1: bits } = job.run(); + if (err !== undefined) + throw err; + + return bits; +} + +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); + }); + }); +} + +module.exports = { + hkdf, + hkdfSync, + hkdfDeriveBits, +}; diff --git a/modules/internal/crypto/keys.js b/modules/internal/crypto/keys.js new file mode 100644 index 0000000..2a3f2cc --- /dev/null +++ b/modules/internal/crypto/keys.js @@ -0,0 +1,738 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +'use strict'; + +const { + KeyObjectHandle, +} = internalBinding('crypto'); + +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'); + +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 KeyObjectHandle(); + 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/util.js b/modules/internal/crypto/util.js index d39da12..e0d88ff 100644 --- a/modules/internal/crypto/util.js +++ b/modules/internal/crypto/util.js @@ -67,9 +67,9 @@ function toBuf(val, encoding) { return val; } -const getCiphers = {}; //cachedResult(() => filterDuplicateStrings(_getCiphers())); -const getHashes = {}; //cachedResult(() => filterDuplicateStrings(_getHashes())); -const getCurves = {}; //cachedResult(() => filterDuplicateStrings(_getCurves())); +const getCiphers = () => ["aes-128-gcm", "aes-256-gcm"]; +const getHashes = () => ["sha256", "sha512", "sha512-256"]; +const getCurves = () => []; function setEngine(id, flags) { validateString(id, 'id'); diff --git a/modules/internal/errors.js b/modules/internal/errors.js index da3d886..ad18a1a 100644 --- a/modules/internal/errors.js +++ b/modules/internal/errors.js @@ -873,3 +873,31 @@ export class ERR_CRYPTO_SCRYPT_NOT_SUPPORTED extends Error { 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"; + } +} diff --git a/modules/internal/validators.js b/modules/internal/validators.js index 39dd2ea..5080f62 100644 --- a/modules/internal/validators.js +++ b/modules/internal/validators.js @@ -234,6 +234,26 @@ export const validateArray = hideStackFrames((value, name, minLength = 0) => { } }); +/** + * @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); + } +}); + export default { validatePort, validateFunction, @@ -245,5 +265,6 @@ export default { validateInteger, validateNumber, validateArray, - getValidMode + getValidMode, + validateOneOf } \ No newline at end of file diff --git a/src/internal_module/crypto.rs b/src/internal_module/crypto.rs index 407560b..3f376e0 100644 --- a/src/internal_module/crypto.rs +++ b/src/internal_module/crypto.rs @@ -344,6 +344,19 @@ fn scrypt_sync(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsVal } } +fn hkdf(key: &[u8], salt: &[u8], info: &[u8], key_len: usize) -> Result, Error> { + let mut h = SymmetricState::new( + "HKDF-EXTRACT/SHA-256", + Some(&SymmetricKey::from_raw("HKDF-EXTRACT/SHA-256", key)?), + None, + )?; + h.absorb(salt)?; + let pk = h.squeeze_key("HKDF-EXPAND/SHA-256")?; + let mut p = SymmetricState::new("HKDF-EXPAND/SHA-256", Some(&pk), None)?; + p.absorb(info)?; + p.squeeze(key_len) +} + struct Crypto; impl ModuleInit for Crypto { diff --git a/test/common.js b/test/common.js index 26b3d8c..5cf58c5 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 = false; const mustCallChecks = []; function runCallChecks() { @@ -297,6 +297,7 @@ const common = { isAIX, isMainThread, hasCrypto, + hasOpenSSL3, mustCall, mustCallAtLeast, mustNotCall, diff --git a/test/crypto/test-crypto-hkdf.js b/test/crypto/test-crypto-hkdf.js index 87c50f8..f9ee5d2 100644 --- a/test/crypto/test-crypto-hkdf.js +++ b/test/crypto/test-crypto-hkdf.js @@ -2,19 +2,19 @@ 'use strict'; -const common = require('../common'); +import common from '../common'; if (!common.hasCrypto) common.skip('missing crypto'); -const { kMaxLength } = require('buffer'); -const assert = require('assert'); -const { +import { kMaxLength } from 'buffer'; +import assert from 'assert'; +import { createSecretKey, hkdf, hkdfSync, getHashes -} = require('crypto'); +} from 'crypto'; { assert.throws(() => hkdf(), { diff --git a/tests/test-crypto.rs b/tests/test-crypto.rs index bbe9a86..f957dd0 100644 --- a/tests/test-crypto.rs +++ b/tests/test-crypto.rs @@ -208,8 +208,7 @@ fn test_crypto_keygen() { fn test_crypto_key_objects() { test_js_file("test/crypto/test-crypto-key-objects.js"); } -#[test] -#[ignore = "working"] +#[ignore = "unsupported, work_thread"] fn test_crypto_key_objects_messageport() { test_js_file("test/crypto/test-crypto-key-objects-messageport.js"); } @@ -275,7 +274,6 @@ fn test_crypto_rsa_dsa() { test_js_file("test/crypto/test-crypto-rsa-dsa.js"); } #[test] -#[ignore = "working"] fn test_crypto_scrypt() { test_js_file("test/crypto/test-crypto-scrypt.js"); } From 3ebea945bc4d7cf9af18f4e1343f226176fd7830 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Sat, 24 Dec 2022 16:00:09 +0800 Subject: [PATCH 10/25] test: hkdf, remove wasi-crypto-guest dependency --- Cargo.toml | 1 - modules/crypto.js | 12 +- modules/internal/crypto/hkdf.js | 48 +- modules/internal/crypto/keys.js | 19 +- modules/internal/errors.js | 7 + src/internal_module/crypto.rs | 395 --- src/internal_module/crypto/lib.rs | 366 +++ src/internal_module/crypto/mod.rs | 206 ++ src/internal_module/crypto/raw.rs | 3860 +++++++++++++++++++++++++++++ test/common.js | 2 +- test/crypto/test-crypto-hkdf.js | 7 +- 11 files changed, 4488 insertions(+), 435 deletions(-) delete mode 100644 src/internal_module/crypto.rs create mode 100644 src/internal_module/crypto/lib.rs create mode 100644 src/internal_module/crypto/mod.rs create mode 100644 src/internal_module/crypto/raw.rs diff --git a/Cargo.toml b/Cargo.toml index b6837e0..4482cca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ libc = "0.2" url = "2.2.2" lazy_static = "1.4" encoding = "0.2" -wasi-crypto-guest = "0.1.3" [features] default = [] diff --git a/modules/crypto.js b/modules/crypto.js index dcb8cf9..06ffe8c 100644 --- a/modules/crypto.js +++ b/modules/crypto.js @@ -34,8 +34,8 @@ import { } 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 { +import { hkdf, hkdfSync } from "./internal/crypto/hkdf"; +/*import { generateKey, generateKeyPair, generateKeyPairSync, @@ -185,9 +185,9 @@ export default { getDiffieHellman,*/ getFips, getHashes,/* - Hash, + Hash,*/ hkdf, - hkdfSync, + hkdfSync,/* Hmac, KeyObject,*/ pbkdf2, @@ -250,9 +250,9 @@ export { getDiffieHellman,*/ getFips, getHashes,/* - Hash, + Hash,*/ hkdf, - hkdfSync, + hkdfSync,/* Hmac, KeyObject,*/ pbkdf2, diff --git a/modules/internal/crypto/hkdf.js b/modules/internal/crypto/hkdf.js index f7df3f3..9f3683c 100644 --- a/modules/internal/crypto/hkdf.js +++ b/modules/internal/crypto/hkdf.js @@ -16,12 +16,13 @@ import { toBuf, validateByteSource, kKeyObject, + getHashes, } from '../crypto/util'; import { createSecretKey, isKeyObject, -} from '../keys'; +} from './keys'; import { lazyDOMException, @@ -37,23 +38,37 @@ import { 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); salt = validateByteSource(salt, 'salt'); info = validateByteSource(info, 'info'); validateInteger(length, 'length', 0, kMaxLength); - if (info.byteLength > 1024) { - throw ERR_OUT_OF_RANGE( + 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, @@ -68,7 +83,7 @@ function prepareKey(key) { return key; if (isAnyArrayBuffer(key)) - return createSecretKey(key); + return getArrayBufferOrView(key); key = toBuf(key); @@ -86,7 +101,7 @@ function prepareKey(key) { key); } - return createSecretKey(key); + return getArrayBufferOrView(key); } function hkdf(hash, key, salt, info, length, callback) { @@ -100,14 +115,10 @@ function hkdf(hash, key, salt, info, length, callback) { validateFunction(callback, 'callback'); - const job = new HKDFJob(kCryptoJobAsync, hash, key, salt, info, length); - - job.ondone = (error, bits) => { - if (error) return Function.prototype.call.call(callback, job, error); - Function.prototype.call.call(callback, job, null, bits); - }; - - job.run(); + 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) { @@ -118,13 +129,8 @@ function hkdfSync(hash, key, salt, info, length) { info, length, } = validateParameters(hash, key, salt, info, length)); - - const job = new HKDFJob(kCryptoJobSync, hash, key, salt, info, length); - const { 0: err, 1: bits } = job.run(); - if (err !== undefined) - throw err; - - return bits; + let result = hkdf_sync(key.buffer ?? key, salt.buffer ?? salt, info.buffer ?? info, length, hash.toUpperCase()); + return result; } async function hkdfDeriveBits(algorithm, baseKey, length) { @@ -163,7 +169,7 @@ async function hkdfDeriveBits(algorithm, baseKey, length) { }); } -module.exports = { +export { hkdf, hkdfSync, hkdfDeriveBits, diff --git a/modules/internal/crypto/keys.js b/modules/internal/crypto/keys.js index 2a3f2cc..abc78a3 100644 --- a/modules/internal/crypto/keys.js +++ b/modules/internal/crypto/keys.js @@ -1,9 +1,9 @@ // Copyright Joyent and Node contributors. All rights reserved. MIT license. 'use strict'; -const { - KeyObjectHandle, -} = internalBinding('crypto'); +class KeyObjectHandle { + // TODO +} const kKeyTypeSecret = Symbol("kKeyTypeSecret"); const kKeyTypePublic = Symbol("kKeyTypePublic"); @@ -43,11 +43,14 @@ import { isArrayBufferView, } from '../util/types'; -const { +/*const { JSTransferable, kClone, kDeserialize, -} = require('internal/worker/js_transferable'); +} = require('internal/worker/js_transferable');*/ + +const kClone = Symbol('kClone'); +const kDeserialize = Symbol('kDeserialize'); import { customInspectSymbol as kInspect, @@ -624,7 +627,7 @@ function isKeyObject(obj) { // 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 { +class CryptoKey /*extends JSTransferable*/ { constructor() { throw new ERR_ILLEGAL_CONSTRUCTOR(); } @@ -690,13 +693,13 @@ class CryptoKey extends JSTransferable { // 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 { +class InternalCryptoKey /*extends JSTransferable*/ { constructor( keyObject, algorithm, keyUsages, extractable) { - super(); + // super(); // Using symbol properties here currently instead of private // properties because (for now) the performance penalty of // private fields is still too high. diff --git a/modules/internal/errors.js b/modules/internal/errors.js index ad18a1a..24fc459 100644 --- a/modules/internal/errors.js +++ b/modules/internal/errors.js @@ -901,3 +901,10 @@ export class ERR_ILLEGAL_CONSTRUCTOR extends TypeError { this.code = "ERR_ILLEGAL_CONSTRUCTOR"; } } + +export class ERR_CRYPTO_INVALID_KEYLEN extends RangeError { + constructor() { + super(`Invalid key length`); + this.code = "ERR_CRYPTO_INVALID_KEYLEN"; + } +} diff --git a/src/internal_module/crypto.rs b/src/internal_module/crypto.rs deleted file mode 100644 index 3f376e0..0000000 --- a/src/internal_module/crypto.rs +++ /dev/null @@ -1,395 +0,0 @@ -use core::arch; - -use crate::event_loop::wasi_fs::{Errno, Size}; -use crate::quickjs_sys::*; -use crate::EventLoop; - -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)), - } -} - -fn timing_safe_equal(_ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { - if let Some(JsValue::ArrayBuffer(a)) = argv.get(0) { - if let Some(JsValue::ArrayBuffer(b)) = argv.get(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]; - } - return eq.into(); - } - } - JsValue::UnDefined -} - -fn random_fill(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue { - if let Some(JsValue::ArrayBuffer(buf)) = argv.get(0) { - if let Some(JsValue::Int(offset)) = argv.get(1) { - if let Some(JsValue::Int(size)) = argv.get(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)) - } - }; - } - } - } - JsValue::UnDefined -} - -use wasi_crypto_guest::error::Error; -use wasi_crypto_guest::symmetric::*; - -fn pbkdf2( - alg: &'static str, - password: &[u8], - salt: &[u8], - iters: usize, - key_len: usize, -) -> Result, Error> { - let tag_len = match alg { - "HMAC/SHA-256" => 32, - "HMAC/SHA-512" => 64, - _ => unreachable!(), - }; - fn pass(alg: &'static str, key: &[u8], salt: &[u8]) -> Result, Error> { - let mut h = SymmetricState::new(alg, Some(&SymmetricKey::from_raw(alg, key)?), None)?; - h.absorb(salt)?; - h.squeeze_tag() - } - let res = (0..(key_len + tag_len - 1) / tag_len) - .map(|idx| -> Result, Error> { - let mut salt_2 = salt.to_vec(); - let idx = idx + 1; - salt_2.push(((idx >> 24) & 0xff) as u8); - salt_2.push(((idx >> 16) & 0xff) as u8); - salt_2.push(((idx >> 8) & 0xff) as u8); - salt_2.push(((idx) & 0xff) as u8); - let mut res_t = pass(alg, password, &salt_2)?; - let mut res_u = res_t.clone(); - for _ in 0..iters - 1 { - res_u = pass(alg, password, &res_u)?; - for k in 0..res_t.len() { - res_t[k] ^= res_u[k]; - } - } - Ok(res_t) - }) - .filter_map(|v| v.ok()) - .flatten() - .take(key_len) - .collect::>(); - Ok(res) -} - -macro_rules! get_arg { - ($argv:ident, $m:path, $i:expr) => { - if let Some($m(val)) = $argv.get($i) { - val - } else { - return JsValue::UnDefined; - } - }; -} - -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( - match alg.as_str() { - "SHA256" => "HMAC/SHA-256", - "SHA512" => "HMAC/SHA-512", - _ => unreachable!(), - }, - password.as_ref(), - salt.as_ref(), - *iters as usize, - *key_len as usize, - ) - } { - Ok(res) => ctx.new_array_buffer(res.as_slice()).into(), - Err(_e) => { - // TODO - JsValue::UnDefined - } - } -} - -struct ScryptRom { - b: Vec, - r: usize, - n: usize, - p: usize, - xy: Vec, - v: Vec, - b32: Vec, - x: Vec, - xx: Vec, -} - -fn blockxor(a: &[u8], b: &mut [u8]) { - for i in 0..a.len() { - b[i] ^= a[i]; - } -} - -impl ScryptRom { - fn romix(&mut self, i: usize, r: usize) { - let block_start = i * 128 * r; - let offset = (2 * r - 1) * 64; - let block_len = 128 * r; - self.xy[0..block_len].copy_from_slice(&self.b[block_start..(block_start + block_len)]); - for i1 in 0..self.n { - self.v[i1 * block_len..(i1 + 1) * block_len].copy_from_slice(&self.xy[0..block_len]); - self.blockmix(block_len); - } - - fn read_u32le(p: &[u8]) -> u32 { - (p[0] as u32) + ((p[1] as u32) << 8) + ((p[2] as u32) << 16) + ((p[3] as u32) << 24) - } - - for _ in 0..self.n { - let j = read_u32le(&self.xy[offset..]) as usize & (self.n - 1); - blockxor( - &self.v[j * block_len..(j + 1) * block_len], - &mut self.xy[0..block_len], - ); - self.blockmix(block_len); - } - self.b[block_start..block_start + block_len].copy_from_slice(&self.xy[0..block_len]); - } - - fn blockmix(&mut self, block_len: usize) { - self.xx[0..64].copy_from_slice(&self.xy[(2 * self.r - 1) * 64..(2 * self.r) * 64]); - for i in 0..2 * self.r { - blockxor(&self.xy[i * 64..(i + 1) * 64], &mut self.xx[0..64]); - self.salsa20_8(); - self.xy[block_len + (i * 64)..block_len + (i * 64) + 64] - .copy_from_slice(&self.xx[0..64]); - } - for i in 0..self.r { - self.xy.copy_within( - block_len + (i * 2) * 64..block_len + (i * 2) * 64 + 64, - i * 64, - ); - self.xy.copy_within( - block_len + (i * 2 + 1) * 64..block_len + (i * 2 + 1) * 64 + 64, - (i + self.r) * 64, - ); - } - } - - fn salsa20_8(&mut self) { - #[inline(always)] - #[allow(non_snake_case)] - fn R(i: i32, r: i32) -> i32 { - i.rotate_left(r as u32) - } - - for i in 0..16 { - self.b32[i] = ((self.xx[i * 4 + 0] & 0xff) as i32) << 0; - self.b32[i] |= ((self.xx[i * 4 + 1] & 0xff) as i32) << 8; - self.b32[i] |= ((self.xx[i * 4 + 2] & 0xff) as i32) << 16; - self.b32[i] |= ((self.xx[i * 4 + 3] & 0xff) as i32) << 24; - } - - self.x.copy_from_slice(&self.b32); - - for _ in 0..4 { - self.x[4] ^= R(self.x[0] + self.x[12], 7); - self.x[8] ^= R(self.x[4] + self.x[0], 9); - self.x[12] ^= R(self.x[8] + self.x[4], 13); - self.x[0] ^= R(self.x[12] + self.x[8], 18); - self.x[9] ^= R(self.x[5] + self.x[1], 7); - self.x[13] ^= R(self.x[9] + self.x[5], 9); - self.x[1] ^= R(self.x[13] + self.x[9], 13); - self.x[5] ^= R(self.x[1] + self.x[13], 18); - self.x[14] ^= R(self.x[10] + self.x[6], 7); - self.x[2] ^= R(self.x[14] + self.x[10], 9); - self.x[6] ^= R(self.x[2] + self.x[14], 13); - self.x[10] ^= R(self.x[6] + self.x[2], 18); - self.x[3] ^= R(self.x[15] + self.x[11], 7); - self.x[7] ^= R(self.x[3] + self.x[15], 9); - self.x[11] ^= R(self.x[7] + self.x[3], 13); - self.x[15] ^= R(self.x[11] + self.x[7], 18); - self.x[1] ^= R(self.x[0] + self.x[3], 7); - self.x[2] ^= R(self.x[1] + self.x[0], 9); - self.x[3] ^= R(self.x[2] + self.x[1], 13); - self.x[0] ^= R(self.x[3] + self.x[2], 18); - self.x[6] ^= R(self.x[5] + self.x[4], 7); - self.x[7] ^= R(self.x[6] + self.x[5], 9); - self.x[4] ^= R(self.x[7] + self.x[6], 13); - self.x[5] ^= R(self.x[4] + self.x[7], 18); - self.x[11] ^= R(self.x[10] + self.x[9], 7); - self.x[8] ^= R(self.x[11] + self.x[10], 9); - self.x[9] ^= R(self.x[8] + self.x[11], 13); - self.x[10] ^= R(self.x[9] + self.x[8], 18); - self.x[12] ^= R(self.x[15] + self.x[14], 7); - self.x[13] ^= R(self.x[12] + self.x[15], 9); - self.x[14] ^= R(self.x[13] + self.x[12], 13); - self.x[15] ^= R(self.x[14] + self.x[13], 18); - } - - for i in 0..16 { - self.b32[i] += self.x[i]; - } - - for i in 0..16 { - self.xx[i * 4 + 0] = (self.b32[i] >> 0 & 0xff) as u8; - self.xx[i * 4 + 1] = (self.b32[i] >> 8 & 0xff) as u8; - self.xx[i * 4 + 2] = (self.b32[i] >> 16 & 0xff) as u8; - self.xx[i * 4 + 3] = (self.b32[i] >> 24 & 0xff) as u8; - } - } -} - -fn scrypt_rom(b: &[u8], r: usize, n: usize, p: usize) -> Result, Error> { - let mut rom = ScryptRom { - b: b.to_vec(), - r, - n, - p, - xy: vec![0; 256 * r], - v: vec![0; 128 * r * n], - b32: vec![0; 16], - x: vec![0; 16], - xx: vec![0; 64], - }; - for i in 0..p { - rom.romix(i, r); - } - Ok(rom.b) -} - -fn scrypt( - password: &[u8], - salt: &[u8], - n: usize, - r: usize, - p: usize, - keylen: usize, -) -> Result, Error> { - let blen = p * 128 * r; - let b = pbkdf2("HMAC/SHA-256", password, salt, 1, blen)?; - let s = scrypt_rom(&b, r, n, p)?; - let f = pbkdf2("HMAC/SHA-256", password, &s, 1, keylen)?; - Ok(f) -} - -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) => { - // TODO - JsValue::UnDefined - } - } -} - -fn hkdf(key: &[u8], salt: &[u8], info: &[u8], key_len: usize) -> Result, Error> { - let mut h = SymmetricState::new( - "HKDF-EXTRACT/SHA-256", - Some(&SymmetricKey::from_raw("HKDF-EXTRACT/SHA-256", key)?), - None, - )?; - h.absorb(salt)?; - let pk = h.squeeze_key("HKDF-EXPAND/SHA-256")?; - let mut p = SymmetricState::new("HKDF-EXPAND/SHA-256", Some(&pk), None)?; - p.absorb(info)?; - p.squeeze(key_len) -} - -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(), - ); - } -} - -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", - ], - ) -} diff --git a/src/internal_module/crypto/lib.rs b/src/internal_module/crypto/lib.rs new file mode 100644 index 0000000..1bf0af1 --- /dev/null +++ b/src/internal_module/crypto/lib.rs @@ -0,0 +1,366 @@ +use super::raw; + +const NONE_OPTS: raw::OptOptions = raw::OptOptions { + tag: raw::OPT_OPTIONS_U_NONE.raw(), + u: raw::OptOptionsUnion { none: () }, +}; + +/// Behaviour like +/// +/// ```js +/// let hmac = createHmac(alg, key); +/// infos.forEach(info => hmac.update(info)); +/// let return = hmac.digest(); +/// ``` +pub fn hmac( + alg: &str, + key: impl AsRef<[u8]>, + infos: &[impl AsRef<[u8]>], +) -> Result, raw::CryptoErrno> { + let key = key.as_ref(); + let hmac_alg = match alg { + "sha256" | "SHA256" | "HMAC/SHA-256" => "HMAC/SHA-256", + "sha512" | "SHA512" | "HMAC/SHA-512" => "HMAC/SHA-512", + _ => unreachable!(), + }; + unsafe { + let hmac_key = raw::symmetric_key_import(hmac_alg, key.as_ptr(), key.len())?; + let hmac_handle = raw::symmetric_state_open( + hmac_alg, + raw::OptSymmetricKey { + tag: raw::OPT_SYMMETRIC_KEY_U_SOME.raw(), + u: raw::OptSymmetricKeyUnion { some: hmac_key }, + }, + NONE_OPTS, + )?; + for info in infos { + let info = info.as_ref(); + raw::symmetric_state_absorb(hmac_handle, info.as_ptr(), info.len())?; + } + let tag = raw::symmetric_state_squeeze_tag(hmac_handle)?; + raw::symmetric_state_close(hmac_handle)?; + raw::symmetric_key_close(hmac_key)?; + let len = raw::symmetric_tag_len(tag)?; + let mut out = vec![0; len]; + raw::symmetric_tag_pull(tag, out.as_mut_ptr(), out.len())?; + raw::symmetric_tag_close(tag)?; + Ok(out) + } +} + +fn hkdf_extract( + alg: &str, + key: impl AsRef<[u8]>, + salt: impl AsRef<[u8]>, +) -> Result { + let (extract_alg, expand_alg) = match alg { + "sha256" | "SHA256" => ("HKDF-EXTRACT/SHA-256", "HKDF-EXPAND/SHA-256"), + "sha512" | "SHA512" => ("HKDF-EXTRACT/SHA-512", "HKDF-EXPAND/SHA-512"), + _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), + }; + let key = key.as_ref(); + let salt = salt.as_ref(); + if !key.is_empty() { + unsafe { + let extract_key = raw::symmetric_key_import(extract_alg, key.as_ptr(), key.len())?; + let extract_handle = raw::symmetric_state_open( + extract_alg, + raw::OptSymmetricKey { + tag: raw::OPT_SYMMETRIC_KEY_U_SOME.raw(), + u: raw::OptSymmetricKeyUnion { some: extract_key }, + }, + NONE_OPTS, + )?; + raw::symmetric_state_absorb(extract_handle, salt.as_ptr(), salt.len())?; + let expand_key = raw::symmetric_state_squeeze_key(extract_handle, expand_alg)?; + raw::symmetric_state_close(extract_handle)?; + raw::symmetric_key_close(extract_key)?; + Ok(expand_key) + } + } else { + let res = hmac(alg, salt, &[key])?; + unsafe { raw::symmetric_key_import(expand_alg, res.as_ptr(), res.len()) } + } +} + +fn hkdf_extract_raw( + alg: &str, + key: impl AsRef<[u8]>, + salt: impl AsRef<[u8]>, +) -> Result, raw::CryptoErrno> { + hmac(alg, salt, &[key]) +} + +/// As same as `hkdf`, but use hmac to manual expand +pub fn hkdf_hmac( + alg: &str, + key: impl AsRef<[u8]>, + salt: impl AsRef<[u8]>, + info: impl AsRef<[u8]>, + key_len: usize, +) -> Result, raw::CryptoErrno> { + let key = key.as_ref(); + let salt = salt.as_ref(); + let info = info.as_ref(); + let (_, _, hash_len) = match alg { + "sha256" | "SHA256" => ("HKDF-EXTRACT/SHA-256", "HKDF-EXPAND/SHA-256", 32), + "sha512" | "SHA512" => ("HKDF-EXTRACT/SHA-512", "HKDF-EXPAND/SHA-512", 64), + _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), + }; + let expand_key = hkdf_extract_raw(alg, key, salt)?; + let mut out = vec![0; key_len]; + let mut last = [].as_slice(); + for (idx, chunk) in out.chunks_mut(hash_len).enumerate() { + let counter = [idx as u8 + 1]; + chunk.clone_from_slice(&hmac(alg, &expand_key, &[last, info, &counter])?[..chunk.len()]); + last = chunk; + } + Ok(out) +} + +/// Behaviour like `crypto.hkdfSync` +pub fn hkdf( + alg: &str, + key: impl AsRef<[u8]>, + salt: impl AsRef<[u8]>, + info: impl AsRef<[u8]>, + key_len: usize, +) -> Result, raw::CryptoErrno> { + let key = key.as_ref(); + let salt = salt.as_ref(); + let info = info.as_ref(); + let (_, expand_alg) = match alg { + "sha256" | "SHA256" => ("HKDF-EXTRACT/SHA-256", "HKDF-EXPAND/SHA-256"), + "sha512" | "SHA512" => ("HKDF-EXTRACT/SHA-512", "HKDF-EXPAND/SHA-512"), + _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), + }; + let mut out = vec![0; key_len]; + let expand_key = hkdf_extract(alg, key, salt)?; + unsafe { + let expand_handle = raw::symmetric_state_open( + expand_alg, + raw::OptSymmetricKey { + tag: raw::OPT_SYMMETRIC_KEY_U_SOME.raw(), + u: raw::OptSymmetricKeyUnion { some: expand_key }, + }, + NONE_OPTS, + )?; + raw::symmetric_state_absorb(expand_handle, info.as_ptr(), info.len())?; + raw::symmetric_state_squeeze(expand_handle, out.as_mut_ptr(), out.len())?; + raw::symmetric_state_close(expand_handle)?; + raw::symmetric_key_close(expand_key)?; + } + Ok(out) +} + +/// Behaviour like `crypto.pbkdf2Sync` +pub fn pbkdf2( + alg: &str, + password: impl AsRef<[u8]>, + salt: impl AsRef<[u8]>, + iters: usize, + key_len: usize, +) -> Result, raw::CryptoErrno> { + let hash_len = match alg { + "sha256" | "SHA256" | "HMAC/SHA-256" => 32, + "sha512" | "SHA512" | "HMAC/SHA-512" => 64, + _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), + }; + let mut out = vec![0; key_len]; + for (idx, chunk) in out.chunks_mut(hash_len).enumerate() { + let mut salt_2 = salt.as_ref().to_vec(); + let idx = idx + 1; + salt_2.push(((idx >> 24) & 0xff) as u8); + salt_2.push(((idx >> 16) & 0xff) as u8); + salt_2.push(((idx >> 8) & 0xff) as u8); + salt_2.push(((idx) & 0xff) as u8); + let mut res_t = hmac(alg, password.as_ref(), &[&salt_2])?; + let mut res_u = res_t.clone(); + for _ in 0..iters - 1 { + res_u = hmac(alg, password.as_ref(), &[&res_u])?; + for k in 0..res_t.len() { + res_t[k] ^= res_u[k]; + } + } + chunk.copy_from_slice(&res_t[..chunk.len()]); + } + Ok(out) +} + +struct ScryptRom { + b: Vec, + r: usize, + n: usize, + xy: Vec, + v: Vec, + b32: Vec, + x: Vec, + xx: Vec, +} + +fn blockxor(a: &[u8], b: &mut [u8]) { + for i in 0..a.len() { + b[i] ^= a[i]; + } +} + +impl ScryptRom { + fn romix(&mut self, i: usize, r: usize) { + let block_start = i * 128 * r; + let offset = (2 * r - 1) * 64; + let block_len = 128 * r; + self.xy[0..block_len].copy_from_slice(&self.b[block_start..(block_start + block_len)]); + for i1 in 0..self.n { + self.v[i1 * block_len..(i1 + 1) * block_len].copy_from_slice(&self.xy[0..block_len]); + self.blockmix(block_len); + } + + fn read_u32le(p: &[u8]) -> u32 { + (p[0] as u32) + ((p[1] as u32) << 8) + ((p[2] as u32) << 16) + ((p[3] as u32) << 24) + } + + for _ in 0..self.n { + let j = read_u32le(&self.xy[offset..]) as usize & (self.n - 1); + blockxor( + &self.v[j * block_len..(j + 1) * block_len], + &mut self.xy[0..block_len], + ); + self.blockmix(block_len); + } + self.b[block_start..block_start + block_len].copy_from_slice(&self.xy[0..block_len]); + } + + fn blockmix(&mut self, block_len: usize) { + self.xx[0..64].copy_from_slice(&self.xy[(2 * self.r - 1) * 64..(2 * self.r) * 64]); + for i in 0..2 * self.r { + blockxor(&self.xy[i * 64..(i + 1) * 64], &mut self.xx[0..64]); + self.salsa20_8(); + self.xy[block_len + (i * 64)..block_len + (i * 64) + 64] + .copy_from_slice(&self.xx[0..64]); + } + for i in 0..self.r { + self.xy.copy_within( + block_len + (i * 2) * 64..block_len + (i * 2) * 64 + 64, + i * 64, + ); + self.xy.copy_within( + block_len + (i * 2 + 1) * 64..block_len + (i * 2 + 1) * 64 + 64, + (i + self.r) * 64, + ); + } + } + + fn salsa20_8(&mut self) { + #[inline(always)] + #[allow(non_snake_case)] + fn R(i: i32, r: i32) -> i32 { + i.rotate_left(r as u32) + } + + for i in 0..16 { + self.b32[i] = ((self.xx[i * 4 + 0] & 0xff) as i32) << 0; + self.b32[i] |= ((self.xx[i * 4 + 1] & 0xff) as i32) << 8; + self.b32[i] |= ((self.xx[i * 4 + 2] & 0xff) as i32) << 16; + self.b32[i] |= ((self.xx[i * 4 + 3] & 0xff) as i32) << 24; + } + + self.x.copy_from_slice(&self.b32); + + for _ in 0..4 { + self.x[4] ^= R(self.x[0] + self.x[12], 7); + self.x[8] ^= R(self.x[4] + self.x[0], 9); + self.x[12] ^= R(self.x[8] + self.x[4], 13); + self.x[0] ^= R(self.x[12] + self.x[8], 18); + self.x[9] ^= R(self.x[5] + self.x[1], 7); + self.x[13] ^= R(self.x[9] + self.x[5], 9); + self.x[1] ^= R(self.x[13] + self.x[9], 13); + self.x[5] ^= R(self.x[1] + self.x[13], 18); + self.x[14] ^= R(self.x[10] + self.x[6], 7); + self.x[2] ^= R(self.x[14] + self.x[10], 9); + self.x[6] ^= R(self.x[2] + self.x[14], 13); + self.x[10] ^= R(self.x[6] + self.x[2], 18); + self.x[3] ^= R(self.x[15] + self.x[11], 7); + self.x[7] ^= R(self.x[3] + self.x[15], 9); + self.x[11] ^= R(self.x[7] + self.x[3], 13); + self.x[15] ^= R(self.x[11] + self.x[7], 18); + self.x[1] ^= R(self.x[0] + self.x[3], 7); + self.x[2] ^= R(self.x[1] + self.x[0], 9); + self.x[3] ^= R(self.x[2] + self.x[1], 13); + self.x[0] ^= R(self.x[3] + self.x[2], 18); + self.x[6] ^= R(self.x[5] + self.x[4], 7); + self.x[7] ^= R(self.x[6] + self.x[5], 9); + self.x[4] ^= R(self.x[7] + self.x[6], 13); + self.x[5] ^= R(self.x[4] + self.x[7], 18); + self.x[11] ^= R(self.x[10] + self.x[9], 7); + self.x[8] ^= R(self.x[11] + self.x[10], 9); + self.x[9] ^= R(self.x[8] + self.x[11], 13); + self.x[10] ^= R(self.x[9] + self.x[8], 18); + self.x[12] ^= R(self.x[15] + self.x[14], 7); + self.x[13] ^= R(self.x[12] + self.x[15], 9); + self.x[14] ^= R(self.x[13] + self.x[12], 13); + self.x[15] ^= R(self.x[14] + self.x[13], 18); + } + + for i in 0..16 { + self.b32[i] += self.x[i]; + } + + for i in 0..16 { + self.xx[i * 4 + 0] = (self.b32[i] >> 0 & 0xff) as u8; + self.xx[i * 4 + 1] = (self.b32[i] >> 8 & 0xff) as u8; + self.xx[i * 4 + 2] = (self.b32[i] >> 16 & 0xff) as u8; + self.xx[i * 4 + 3] = (self.b32[i] >> 24 & 0xff) as u8; + } + } +} + +fn scrypt_rom(b: &[u8], r: usize, n: usize, p: usize) -> Vec { + let mut rom = ScryptRom { + b: b.to_vec(), + r, + n, + xy: vec![0; 256 * r], + v: vec![0; 128 * r * n], + b32: vec![0; 16], + x: vec![0; 16], + xx: vec![0; 64], + }; + for i in 0..p { + rom.romix(i, r); + } + rom.b +} + +/// Behaviour like `crypto.scryptSync` +pub fn scrypt( + password: impl AsRef<[u8]>, + salt: impl AsRef<[u8]>, + n: usize, + r: usize, + p: usize, + keylen: usize, +) -> Result, raw::CryptoErrno> { + let blen = p * 128 * r; + let b = pbkdf2("HMAC/SHA-256", &password, salt, 1, blen)?; + let s = scrypt_rom(&b, r, n, p); + let f = pbkdf2("HMAC/SHA-256", &password, &s, 1, keylen)?; + Ok(f) +} + +/// Convert u8 array to hex string, +/// behaviour like `Buffer.from(arr).toString("hex")` +/// +/// # Examples +/// +/// ``` +/// use crypto_wasi::u8array_to_hex; +/// +/// assert_eq!(u8array_to_hex([01, 23, 45]), "01172d".to_string()); +/// ``` +pub fn u8array_to_hex(arr: impl AsRef<[u8]>) -> String { + arr.as_ref() + .iter() + .map(|v| format!("{:02x}", v)) + .collect::>() + .join("") +} diff --git a/src/internal_module/crypto/mod.rs b/src/internal_module/crypto/mod.rs new file mode 100644 index 0000000..15d571b --- /dev/null +++ b/src/internal_module/crypto/mod.rs @@ -0,0 +1,206 @@ +mod lib; +mod raw; + +use core::arch; + +use crate::event_loop::wasi_fs::{Errno, Size}; +use crate::quickjs_sys::*; +use crate::EventLoop; + +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)) + } + }; +} + +use self::lib::{hkdf_hmac, pbkdf2, scrypt}; + +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( + alg.as_str(), + password.as_ref(), + salt.as_ref(), + *iters 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 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)) + } + } +} + +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(), + ); + } +} + +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", + ], + ) +} diff --git a/src/internal_module/crypto/raw.rs b/src/internal_module/crypto/raw.rs new file mode 100644 index 0000000..1e86471 --- /dev/null +++ b/src/internal_module/crypto/raw.rs @@ -0,0 +1,3860 @@ +#![allow(dead_code, unused_variables)] +use core::fmt; +use core::mem::MaybeUninit; +#[repr(transparent)] +#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct CryptoErrno(u16); +/// Operation succeeded. +pub const CRYPTO_ERRNO_SUCCESS: CryptoErrno = CryptoErrno(0); +/// An error occurred when trying to during a conversion from a host type to a +/// guest type. +/// +/// Only an internal bug can throw this error. +pub const CRYPTO_ERRNO_GUEST_ERROR: CryptoErrno = CryptoErrno(1); +/// The requested operation is valid, but not implemented by the host. +pub const CRYPTO_ERRNO_NOT_IMPLEMENTED: CryptoErrno = CryptoErrno(2); +/// The requested feature is not supported by the chosen algorithm. +pub const CRYPTO_ERRNO_UNSUPPORTED_FEATURE: CryptoErrno = CryptoErrno(3); +/// The requested operation is valid, but was administratively prohibited. +pub const CRYPTO_ERRNO_PROHIBITED_OPERATION: CryptoErrno = CryptoErrno(4); +/// Unsupported encoding for an import or export operation. +pub const CRYPTO_ERRNO_UNSUPPORTED_ENCODING: CryptoErrno = CryptoErrno(5); +/// The requested algorithm is not supported by the host. +pub const CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM: CryptoErrno = CryptoErrno(6); +/// The requested option is not supported by the currently selected algorithm. +pub const CRYPTO_ERRNO_UNSUPPORTED_OPTION: CryptoErrno = CryptoErrno(7); +/// An invalid or incompatible key was supplied. +/// +/// The key may not be valid, or was generated for a different algorithm or +/// parameters set. +pub const CRYPTO_ERRNO_INVALID_KEY: CryptoErrno = CryptoErrno(8); +/// The currently selected algorithm doesn't support the requested output +/// length. +/// +/// This error is thrown by non-extensible hash functions, when requesting an +/// output size larger than they produce out of a single block. +pub const CRYPTO_ERRNO_INVALID_LENGTH: CryptoErrno = CryptoErrno(9); +/// A signature or authentication tag verification failed. +pub const CRYPTO_ERRNO_VERIFICATION_FAILED: CryptoErrno = CryptoErrno(10); +/// A secure random numbers generator is not available. +/// +/// The requested operation requires random numbers, but the host cannot +/// securely generate them at the moment. +pub const CRYPTO_ERRNO_RNG_ERROR: CryptoErrno = CryptoErrno(11); +/// An error was returned by the underlying cryptography library. +/// +/// The host may be running out of memory, parameters may be incompatible with +/// the chosen implementation of an algorithm or another unexpected error may +/// have happened. +/// +/// Ideally, the specification should provide enough details and guidance to +/// make this error impossible to ever be thrown. +/// +/// Realistically, the WASI crypto module cannot possibly cover all possible +/// error types implementations can return, especially since some of these may +/// be language-specific. This error can thus be thrown when other error types +/// are not suitable, and when the original error comes from the cryptographic +/// primitives themselves and not from the WASI module. +pub const CRYPTO_ERRNO_ALGORITHM_FAILURE: CryptoErrno = CryptoErrno(12); +/// The supplied signature is invalid, or incompatible with the chosen +/// algorithm. +pub const CRYPTO_ERRNO_INVALID_SIGNATURE: CryptoErrno = CryptoErrno(13); +/// An attempt was made to close a handle that was already closed. +pub const CRYPTO_ERRNO_CLOSED: CryptoErrno = CryptoErrno(14); +/// A function was called with an unassigned handle, a closed handle, or handle +/// of an unexpected type. +pub const CRYPTO_ERRNO_INVALID_HANDLE: CryptoErrno = CryptoErrno(15); +/// The host needs to copy data to a guest-allocated buffer, but that buffer is +/// too small. +pub const CRYPTO_ERRNO_OVERFLOW: CryptoErrno = CryptoErrno(16); +/// An internal error occurred. +/// +/// This error is reserved to internal consistency checks, and must only be sent +/// if the internal state of the host remains safe after an inconsistency was +/// detected. +pub const CRYPTO_ERRNO_INTERNAL_ERROR: CryptoErrno = CryptoErrno(17); +/// Too many handles are currently open, and a new one cannot be created. +/// +/// Implementations are free to represent handles as they want, and to enforce +/// limits to limit resources usage. +pub const CRYPTO_ERRNO_TOO_MANY_HANDLES: CryptoErrno = CryptoErrno(18); +/// A key was provided, but the chosen algorithm doesn't support keys. +/// +/// This is returned by symmetric operations. +/// +/// Many hash functions, in particular, do not support keys without being used +/// in particular constructions. Blindly ignoring a key provided by mistake +/// while trying to open a context for such as function could cause serious +/// security vulnerabilities. +/// +/// These functions must refuse to create the context and return this error +/// instead. +pub const CRYPTO_ERRNO_KEY_NOT_SUPPORTED: CryptoErrno = CryptoErrno(19); +/// A key is required for the chosen algorithm, but none was given. +pub const CRYPTO_ERRNO_KEY_REQUIRED: CryptoErrno = CryptoErrno(20); +/// The provided authentication tag is invalid or incompatible with the current +/// algorithm. +/// +/// This error is returned by decryption functions and tag verification +/// functions. +/// +/// Unlike `verification_failed`, this error code is returned when the tag +/// cannot possibly verify for any input. +pub const CRYPTO_ERRNO_INVALID_TAG: CryptoErrno = CryptoErrno(21); +/// The requested operation is incompatible with the current scheme. +/// +/// For example, the `symmetric_state_encrypt()` function cannot complete if the +/// selected construction is a key derivation function. This error code will be +/// returned instead. +pub const CRYPTO_ERRNO_INVALID_OPERATION: CryptoErrno = CryptoErrno(22); +/// A nonce is required. +/// +/// Most encryption schemes require a nonce. +/// +/// In the absence of a nonce, the WASI cryptography module can automatically +/// generate one, if that can be done safely. The nonce can be retrieved later +/// with the `symmetric_state_option_get()` function using the `nonce` +/// parameter. If automatically generating a nonce cannot be done safely, the +/// module never falls back to an insecure option and requests an explicit nonce +/// by throwing that error. +pub const CRYPTO_ERRNO_NONCE_REQUIRED: CryptoErrno = CryptoErrno(23); +/// The provided nonce doesn't have a correct size for the given cipher. +pub const CRYPTO_ERRNO_INVALID_NONCE: CryptoErrno = CryptoErrno(24); +/// The named option was not set. +/// +/// The caller tried to read the value of an option that was not set. +/// This error is used to make the distinction between an empty option, and an +/// option that was not set and left to its default value. +pub const CRYPTO_ERRNO_OPTION_NOT_SET: CryptoErrno = CryptoErrno(25); +/// A key or key pair matching the requested identifier cannot be found using +/// the supplied information. +/// +/// This error is returned by a secrets manager via the `keypair_from_id()` +/// function. +pub const CRYPTO_ERRNO_NOT_FOUND: CryptoErrno = CryptoErrno(26); +/// The algorithm requires parameters that haven't been set. +/// +/// Non-generic options are required and must be given by building an `options` +/// set and giving that object to functions instantiating that algorithm. +pub const CRYPTO_ERRNO_PARAMETERS_MISSING: CryptoErrno = CryptoErrno(27); +/// A requested computation is not done yet, and additional calls to the +/// function are required. +/// +/// Some functions, such as functions generating key pairs and password +/// stretching functions, can take a long time to complete. +/// +/// In order to avoid a host call to be blocked for too long, these functions +/// can return prematurely, requiring additional calls with the same parameters +/// until they complete. +pub const CRYPTO_ERRNO_IN_PROGRESS: CryptoErrno = CryptoErrno(28); +/// Multiple keys have been provided, but they do not share the same type. +/// +/// This error is returned when trying to build a key pair from a public key and +/// a secret key that were created for different and incompatible algorithms. +pub const CRYPTO_ERRNO_INCOMPATIBLE_KEYS: CryptoErrno = CryptoErrno(29); +/// A managed key or secret expired and cannot be used any more. +pub const CRYPTO_ERRNO_EXPIRED: CryptoErrno = CryptoErrno(30); +impl CryptoErrno { + pub const fn raw(&self) -> u16 { + self.0 + } + + pub fn name(&self) -> &'static str { + match self.0 { + 0 => "SUCCESS", + 1 => "GUEST_ERROR", + 2 => "NOT_IMPLEMENTED", + 3 => "UNSUPPORTED_FEATURE", + 4 => "PROHIBITED_OPERATION", + 5 => "UNSUPPORTED_ENCODING", + 6 => "UNSUPPORTED_ALGORITHM", + 7 => "UNSUPPORTED_OPTION", + 8 => "INVALID_KEY", + 9 => "INVALID_LENGTH", + 10 => "VERIFICATION_FAILED", + 11 => "RNG_ERROR", + 12 => "ALGORITHM_FAILURE", + 13 => "INVALID_SIGNATURE", + 14 => "CLOSED", + 15 => "INVALID_HANDLE", + 16 => "OVERFLOW", + 17 => "INTERNAL_ERROR", + 18 => "TOO_MANY_HANDLES", + 19 => "KEY_NOT_SUPPORTED", + 20 => "KEY_REQUIRED", + 21 => "INVALID_TAG", + 22 => "INVALID_OPERATION", + 23 => "NONCE_REQUIRED", + 24 => "INVALID_NONCE", + 25 => "OPTION_NOT_SET", + 26 => "NOT_FOUND", + 27 => "PARAMETERS_MISSING", + 28 => "IN_PROGRESS", + 29 => "INCOMPATIBLE_KEYS", + 30 => "EXPIRED", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } + + pub fn message(&self) -> &'static str { + match self.0 { + 0 => "Operation succeeded.", + 1 => { + "An error occurred when trying to during a conversion from a host type to a guest \ + type. + +Only an internal bug can throw this error." + } + 2 => "The requested operation is valid, but not implemented by the host.", + 3 => "The requested feature is not supported by the chosen algorithm.", + 4 => "The requested operation is valid, but was administratively prohibited.", + 5 => "Unsupported encoding for an import or export operation.", + 6 => "The requested algorithm is not supported by the host.", + 7 => "The requested option is not supported by the currently selected algorithm.", + 8 => { + "An invalid or incompatible key was supplied. + +The key may not be valid, or was generated for a different algorithm or parameters set." + } + 9 => { + "The currently selected algorithm doesn't support the requested output length. + +This error is thrown by non-extensible hash functions, when requesting an output size larger than \ + they produce out of a single block." + } + 10 => "A signature or authentication tag verification failed.", + 11 => { + "A secure random numbers generator is not available. + +The requested operation requires random numbers, but the host cannot securely generate them at the \ + moment." + } + 12 => { + "An error was returned by the underlying cryptography library. + +The host may be running out of memory, parameters may be incompatible with the chosen \ + implementation of an algorithm or another unexpected error may have happened. + +Ideally, the specification should provide enough details and guidance to make this error \ + impossible to ever be thrown. + +Realistically, the WASI crypto module cannot possibly cover all possible error types \ + implementations can return, especially since some of these may be \ + language-specific. +This error can thus be thrown when other error types are not suitable, and when the original error \ + comes from the cryptographic primitives themselves and not from the WASI module." + } + 13 => "The supplied signature is invalid, or incompatible with the chosen algorithm.", + 14 => "An attempt was made to close a handle that was already closed.", + 15 => { + "A function was called with an unassigned handle, a closed handle, or handle of an \ + unexpected type." + } + 16 => { + "The host needs to copy data to a guest-allocated buffer, but that buffer is too \ + small." + } + 17 => { + "An internal error occurred. + +This error is reserved to internal consistency checks, and must only be sent if the internal state \ + of the host remains safe after an inconsistency was detected." + } + 18 => { + "Too many handles are currently open, and a new one cannot be created. + +Implementations are free to represent handles as they want, and to enforce limits to limit \ + resources usage." + } + 19 => { + "A key was provided, but the chosen algorithm doesn't support keys. + +This is returned by symmetric operations. + +Many hash functions, in particular, do not support keys without being used in particular \ + constructions. +Blindly ignoring a key provided by mistake while trying to open a context for such as function \ + could cause serious security vulnerabilities. + +These functions must refuse to create the context and return this error instead." + } + 20 => "A key is required for the chosen algorithm, but none was given.", + 21 => { + "The provided authentication tag is invalid or incompatible with the current \ + algorithm. + +This error is returned by decryption functions and tag verification functions. + +Unlike `verification_failed`, this error code is returned when the tag cannot possibly verify for \ + any input." + } + 22 => { + "The requested operation is incompatible with the current scheme. + +For example, the `symmetric_state_encrypt()` function cannot complete if the selected construction \ + is a key derivation function. +This error code will be returned instead." + } + 23 => { + "A nonce is required. + +Most encryption schemes require a nonce. + +In the absence of a nonce, the WASI cryptography module can automatically generate one, if that \ + can be done safely. The nonce can be retrieved later with the \ + `symmetric_state_option_get()` function using the `nonce` parameter. +If automatically generating a nonce cannot be done safely, the module never falls back to an \ + insecure option and requests an explicit nonce by throwing that error." + } + 24 => "The provided nonce doesn't have a correct size for the given cipher.", + 25 => { + "The named option was not set. + +The caller tried to read the value of an option that was not set. +This error is used to make the distinction between an empty option, and an option that was not set \ + and left to its default value." + } + 26 => { + "A key or key pair matching the requested identifier cannot be found using the \ + supplied information. + +This error is returned by a secrets manager via the `keypair_from_id()` function." + } + 27 => { + "The algorithm requires parameters that haven't been set. + +Non-generic options are required and must be given by building an `options` set and giving that \ + object to functions instantiating that algorithm." + } + 28 => { + "A requested computation is not done yet, and additional calls to the function are \ + required. + +Some functions, such as functions generating key pairs and password stretching functions, can take \ + a long time to complete. + +In order to avoid a host call to be blocked for too long, these functions can return prematurely, \ + requiring additional calls with the same parameters until they complete." + } + 29 => { + "Multiple keys have been provided, but they do not share the same type. + +This error is returned when trying to build a key pair from a public key and a secret key that \ + were created for different and incompatible algorithms." + } + 30 => "A managed key or secret expired and cannot be used any more.", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } +} +impl fmt::Debug for CryptoErrno { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CryptoErrno") + .field("code", &self.0) + .field("name", &self.name()) + .field("message", &self.message()) + .finish() + } +} +impl fmt::Display for CryptoErrno { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} (error {})", self.name(), self.0) + } +} + +#[cfg(feature = "std")] +extern crate std; +#[cfg(feature = "std")] +impl std::error::Error for CryptoErrno {} + +#[repr(transparent)] +#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct KeypairEncoding(u16); +/// Raw bytes. +pub const KEYPAIR_ENCODING_RAW: KeypairEncoding = KeypairEncoding(0); +/// PCSK8/DER encoding. +pub const KEYPAIR_ENCODING_PKCS8: KeypairEncoding = KeypairEncoding(1); +/// PEM encoding. +pub const KEYPAIR_ENCODING_PEM: KeypairEncoding = KeypairEncoding(2); +/// Implementation-defined encoding. +pub const KEYPAIR_ENCODING_LOCAL: KeypairEncoding = KeypairEncoding(3); +impl KeypairEncoding { + pub const fn raw(&self) -> u16 { + self.0 + } + + pub fn name(&self) -> &'static str { + match self.0 { + 0 => "RAW", + 1 => "PKCS8", + 2 => "PEM", + 3 => "LOCAL", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } + + pub fn message(&self) -> &'static str { + match self.0 { + 0 => "Raw bytes.", + 1 => "PCSK8/DER encoding.", + 2 => "PEM encoding.", + 3 => "Implementation-defined encoding.", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } +} +impl fmt::Debug for KeypairEncoding { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("KeypairEncoding") + .field("code", &self.0) + .field("name", &self.name()) + .field("message", &self.message()) + .finish() + } +} + +#[repr(transparent)] +#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct PublickeyEncoding(u16); +/// Raw bytes. +pub const PUBLICKEY_ENCODING_RAW: PublickeyEncoding = PublickeyEncoding(0); +/// PKCS8/DER encoding. +pub const PUBLICKEY_ENCODING_PKCS8: PublickeyEncoding = PublickeyEncoding(1); +/// PEM encoding. +pub const PUBLICKEY_ENCODING_PEM: PublickeyEncoding = PublickeyEncoding(2); +/// SEC-1 encoding. +pub const PUBLICKEY_ENCODING_SEC: PublickeyEncoding = PublickeyEncoding(3); +/// Implementation-defined encoding. +pub const PUBLICKEY_ENCODING_LOCAL: PublickeyEncoding = PublickeyEncoding(4); +impl PublickeyEncoding { + pub const fn raw(&self) -> u16 { + self.0 + } + + pub fn name(&self) -> &'static str { + match self.0 { + 0 => "RAW", + 1 => "PKCS8", + 2 => "PEM", + 3 => "SEC", + 4 => "LOCAL", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } + + pub fn message(&self) -> &'static str { + match self.0 { + 0 => "Raw bytes.", + 1 => "PKCS8/DER encoding.", + 2 => "PEM encoding.", + 3 => "SEC-1 encoding.", + 4 => "Implementation-defined encoding.", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } +} +impl fmt::Debug for PublickeyEncoding { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PublickeyEncoding") + .field("code", &self.0) + .field("name", &self.name()) + .field("message", &self.message()) + .finish() + } +} + +#[repr(transparent)] +#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct SecretkeyEncoding(u16); +/// Raw bytes. +pub const SECRETKEY_ENCODING_RAW: SecretkeyEncoding = SecretkeyEncoding(0); +/// PKCS8/DER encoding. +pub const SECRETKEY_ENCODING_PKCS8: SecretkeyEncoding = SecretkeyEncoding(1); +/// PEM encoding. +pub const SECRETKEY_ENCODING_PEM: SecretkeyEncoding = SecretkeyEncoding(2); +/// SEC-1 encoding. +pub const SECRETKEY_ENCODING_SEC: SecretkeyEncoding = SecretkeyEncoding(3); +/// Implementation-defined encoding. +pub const SECRETKEY_ENCODING_LOCAL: SecretkeyEncoding = SecretkeyEncoding(4); +impl SecretkeyEncoding { + pub const fn raw(&self) -> u16 { + self.0 + } + + pub fn name(&self) -> &'static str { + match self.0 { + 0 => "RAW", + 1 => "PKCS8", + 2 => "PEM", + 3 => "SEC", + 4 => "LOCAL", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } + + pub fn message(&self) -> &'static str { + match self.0 { + 0 => "Raw bytes.", + 1 => "PKCS8/DER encoding.", + 2 => "PEM encoding.", + 3 => "SEC-1 encoding.", + 4 => "Implementation-defined encoding.", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } +} +impl fmt::Debug for SecretkeyEncoding { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SecretkeyEncoding") + .field("code", &self.0) + .field("name", &self.name()) + .field("message", &self.message()) + .finish() + } +} + +#[repr(transparent)] +#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct SignatureEncoding(u16); +/// Raw bytes. +pub const SIGNATURE_ENCODING_RAW: SignatureEncoding = SignatureEncoding(0); +/// DER encoding. +pub const SIGNATURE_ENCODING_DER: SignatureEncoding = SignatureEncoding(1); +impl SignatureEncoding { + pub const fn raw(&self) -> u16 { + self.0 + } + + pub fn name(&self) -> &'static str { + match self.0 { + 0 => "RAW", + 1 => "DER", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } + + pub fn message(&self) -> &'static str { + match self.0 { + 0 => "Raw bytes.", + 1 => "DER encoding.", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } +} +impl fmt::Debug for SignatureEncoding { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SignatureEncoding") + .field("code", &self.0) + .field("name", &self.name()) + .field("message", &self.message()) + .finish() + } +} + +#[repr(transparent)] +#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct AlgorithmType(u16); +pub const ALGORITHM_TYPE_SIGNATURES: AlgorithmType = AlgorithmType(0); +pub const ALGORITHM_TYPE_SYMMETRIC: AlgorithmType = AlgorithmType(1); +pub const ALGORITHM_TYPE_KEY_EXCHANGE: AlgorithmType = AlgorithmType(2); +impl AlgorithmType { + pub const fn raw(&self) -> u16 { + self.0 + } + + pub fn name(&self) -> &'static str { + match self.0 { + 0 => "SIGNATURES", + 1 => "SYMMETRIC", + 2 => "KEY_EXCHANGE", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } + + pub fn message(&self) -> &'static str { + match self.0 { + 0 => "", + 1 => "", + 2 => "", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } +} +impl fmt::Debug for AlgorithmType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AlgorithmType") + .field("code", &self.0) + .field("name", &self.name()) + .field("message", &self.message()) + .finish() + } +} + +pub type Version = u64; +pub type Size = usize; +pub type Timestamp = u64; +pub type ArrayOutput = u32; +pub type Options = u32; +pub type SecretsManager = u32; +pub type Keypair = u32; +pub type SignatureState = u32; +pub type Signature = u32; +pub type Publickey = u32; +pub type Secretkey = u32; +pub type SignatureVerificationState = u32; +pub type SymmetricState = u32; +pub type SymmetricKey = u32; +pub type SymmetricTag = u32; +#[repr(transparent)] +#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct OptOptionsU(u8); +pub const OPT_OPTIONS_U_SOME: OptOptionsU = OptOptionsU(0); +pub const OPT_OPTIONS_U_NONE: OptOptionsU = OptOptionsU(1); +impl OptOptionsU { + pub const fn raw(&self) -> u8 { + self.0 + } + + pub fn name(&self) -> &'static str { + match self.0 { + 0 => "SOME", + 1 => "NONE", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } + + pub fn message(&self) -> &'static str { + match self.0 { + 0 => "", + 1 => "", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } +} +impl fmt::Debug for OptOptionsU { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OptOptionsU") + .field("code", &self.0) + .field("name", &self.name()) + .field("message", &self.message()) + .finish() + } +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub union OptOptionsUnion { + pub none: (), + pub some: Options, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct OptOptions { + pub tag: u8, + pub u: OptOptionsUnion, +} + +#[repr(transparent)] +#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct OptSymmetricKeyU(u8); +pub const OPT_SYMMETRIC_KEY_U_SOME: OptSymmetricKeyU = OptSymmetricKeyU(0); +pub const OPT_SYMMETRIC_KEY_U_NONE: OptSymmetricKeyU = OptSymmetricKeyU(1); +impl OptSymmetricKeyU { + pub const fn raw(&self) -> u8 { + self.0 + } + + pub fn name(&self) -> &'static str { + match self.0 { + 0 => "SOME", + 1 => "NONE", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } + + pub fn message(&self) -> &'static str { + match self.0 { + 0 => "", + 1 => "", + _ => unsafe { core::hint::unreachable_unchecked() }, + } + } +} +impl fmt::Debug for OptSymmetricKeyU { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OptSymmetricKeyU") + .field("code", &self.0) + .field("name", &self.name()) + .field("message", &self.message()) + .finish() + } +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub union OptSymmetricKeyUnion { + pub none: (), + pub some: SymmetricKey, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct OptSymmetricKey { + pub tag: u8, + pub u: OptSymmetricKeyUnion, +} + +pub type U64 = u64; +pub type SignatureKeypair = Keypair; +pub type SignaturePublickey = Publickey; +pub type SignatureSecretkey = Secretkey; +pub type KxKeypair = Keypair; +pub type KxPublickey = Publickey; +pub type KxSecretkey = Secretkey; +/// Create a new object to set non-default options. +/// +/// Example usage: +/// +/// ```rust +/// let options_handle = options_open(AlgorithmType::Symmetric)?; +/// options_set(options_handle, "context", context)?; +/// options_set_u64(options_handle, "threads", 4)?; +/// let state = symmetric_state_open("BLAKE3", None, Some(options_handle))?; +/// options_close(options_handle)?; +/// ``` +pub unsafe fn options_open(algorithm_type: AlgorithmType) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_common::options_open( + algorithm_type.0 as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Options)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Destroy an options object. +/// +/// Objects are reference counted. It is safe to close an object immediately +/// after the last function needing it is called. +pub unsafe fn options_close(handle: Options) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_common::options_close(handle as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Set or update an option. +/// +/// This is used to set algorithm-specific parameters, but also to provide +/// credentials for the secrets management facilities, if required. +/// +/// This function may return `unsupported_option` if an option that doesn't +/// exist for any implemented algorithms is specified. +pub unsafe fn options_set( + handle: Options, + name: &str, + value: *const u8, + value_len: Size, +) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_common::options_set( + handle as i32, + name.as_ptr() as i32, + name.len() as i32, + value as i32, + value_len as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Set or update an integer option. +/// +/// This is used to set algorithm-specific parameters. +/// +/// This function may return `unsupported_option` if an option that doesn't +/// exist for any implemented algorithms is specified. +pub unsafe fn options_set_u64(handle: Options, name: &str, value: u64) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_common::options_set_u64( + handle as i32, + name.as_ptr() as i32, + name.len() as i32, + value as i64, + ); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Set or update a guest-allocated memory that the host can use or return data +/// into. +/// +/// This is for example used to set the scratch buffer required by memory-hard +/// functions. +/// +/// This function may return `unsupported_option` if an option that doesn't +/// exist for any implemented algorithms is specified. +pub unsafe fn options_set_guest_buffer( + handle: Options, + name: &str, + buffer: *mut u8, + buffer_len: Size, +) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_common::options_set_guest_buffer( + handle as i32, + name.as_ptr() as i32, + name.len() as i32, + buffer as i32, + buffer_len as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Return the length of an `array_output` object. +/// +/// This allows a guest to allocate a buffer of the correct size in order to +/// copy the output of a function returning this object type. +pub unsafe fn array_output_len(array_output: ArrayOutput) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_common::array_output_len( + array_output as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Copy the content of an `array_output` object into an application-allocated +/// buffer. +/// +/// Multiple calls to that function can be made in order to consume the data in +/// a streaming fashion, if necessary. +/// +/// The function returns the number of bytes that were actually copied. `0` +/// means that the end of the stream has been reached. The total size always +/// matches the output of `array_output_len()`. +/// +/// The handle is automatically closed after all the data has been consumed. +/// +/// Example usage: +/// +/// ```rust +/// let len = array_output_len(output_handle)?; +/// let mut out = vec![0u8; len]; +/// array_output_pull(output_handle, &mut out)?; +/// ``` +pub unsafe fn array_output_pull( + array_output: ArrayOutput, + buf: *mut u8, + buf_len: Size, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_common::array_output_pull( + array_output as i32, + buf as i32, + buf_len as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Create a context to use a secrets manager. +/// +/// The set of required and supported options is defined by the host. +/// +/// The function returns the `unsupported_feature` error code if secrets +/// management facilities are not supported by the host. This is also an +/// optional import, meaning that the function may not even exist. +pub unsafe fn secrets_manager_open(options: OptOptions) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_common::secrets_manager_open( + &options as *const _ as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const SecretsManager + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Destroy a secrets manager context. +/// +/// The function returns the `unsupported_feature` error code if secrets +/// management facilities are not supported by the host. This is also an +/// optional import, meaning that the function may not even exist. +pub unsafe fn secrets_manager_close(secrets_manager: SecretsManager) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_common::secrets_manager_close(secrets_manager as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Invalidate a managed key or key pair given an identifier and a version. +/// +/// This asks the secrets manager to delete or revoke a stored key, a specific +/// version of a key. +/// +/// `key_version` can be set to a version number, to `version.latest` to +/// invalidate the current version, or to `version.all` to invalidate all +/// versions of a key. +/// +/// The function returns `unsupported_feature` if this operation is not +/// supported by the host, and `not_found` if the identifier and version don't +/// match any existing key. +/// +/// This is an optional import, meaning that the function may not even exist. +pub unsafe fn secrets_manager_invalidate( + secrets_manager: SecretsManager, + key_id: *const u8, + key_id_len: Size, + key_version: Version, +) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_common::secrets_manager_invalidate( + secrets_manager as i32, + key_id as i32, + key_id_len as i32, + key_version as i64, + ); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +pub mod wasi_ephemeral_crypto_common { + #[link(wasm_import_module = "wasi_ephemeral_crypto_common")] + extern "C" { + /// Create a new object to set non-default options. + /// + /// Example usage: + /// + /// ```rust + /// let options_handle = options_open(AlgorithmType::Symmetric)?; + /// options_set(options_handle, "context", context)?; + /// options_set_u64(options_handle, "threads", 4)?; + /// let state = symmetric_state_open("BLAKE3", None, Some(options_handle))?; + /// options_close(options_handle)?; + /// ``` + pub fn options_open(arg0: i32, arg1: i32) -> i32; + /// Destroy an options object. + /// + /// Objects are reference counted. It is safe to close an object + /// immediately after the last function needing it is called. + pub fn options_close(arg0: i32) -> i32; + /// Set or update an option. + /// + /// This is used to set algorithm-specific parameters, but also to + /// provide credentials for the secrets management facilities, if + /// required. + /// + /// This function may return `unsupported_option` if an option that + /// doesn't exist for any implemented algorithms is specified. + pub fn options_set(arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32) -> i32; + /// Set or update an integer option. + /// + /// This is used to set algorithm-specific parameters. + /// + /// This function may return `unsupported_option` if an option that + /// doesn't exist for any implemented algorithms is specified. + pub fn options_set_u64(arg0: i32, arg1: i32, arg2: i32, arg3: i64) -> i32; + /// Set or update a guest-allocated memory that the host can use or + /// return data into. + /// + /// This is for example used to set the scratch buffer required by + /// memory-hard functions. + /// + /// This function may return `unsupported_option` if an option that + /// doesn't exist for any implemented algorithms is specified. + pub fn options_set_guest_buffer( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + ) -> i32; + /// Return the length of an `array_output` object. + /// + /// This allows a guest to allocate a buffer of the correct size in + /// order to copy the output of a function returning this object type. + pub fn array_output_len(arg0: i32, arg1: i32) -> i32; + /// Copy the content of an `array_output` object into an + /// application-allocated buffer. + /// + /// Multiple calls to that function can be made in order to consume the + /// data in a streaming fashion, if necessary. + /// + /// The function returns the number of bytes that were actually copied. + /// `0` means that the end of the stream has been reached. The total + /// size always matches the output of `array_output_len()`. + /// + /// The handle is automatically closed after all the data has been + /// consumed. + /// + /// Example usage: + /// + /// ```rust + /// let len = array_output_len(output_handle)?; + /// let mut out = vec![0u8; len]; + /// array_output_pull(output_handle, &mut out)?; + /// ``` + pub fn array_output_pull(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; + /// __(optional)__ + /// Create a context to use a secrets manager. + /// + /// The set of required and supported options is defined by the host. + /// + /// The function returns the `unsupported_feature` error code if secrets + /// management facilities are not supported by the host. This is + /// also an optional import, meaning that the function may not even + /// exist. + pub fn secrets_manager_open(arg0: i32, arg1: i32) -> i32; + /// __(optional)__ + /// Destroy a secrets manager context. + /// + /// The function returns the `unsupported_feature` error code if secrets + /// management facilities are not supported by the host. This is + /// also an optional import, meaning that the function may not even + /// exist. + pub fn secrets_manager_close(arg0: i32) -> i32; + /// __(optional)__ + /// Invalidate a managed key or key pair given an identifier and a + /// version. + /// + /// This asks the secrets manager to delete or revoke a stored key, a + /// specific version of a key. + /// + /// `key_version` can be set to a version number, to `version.latest` to + /// invalidate the current version, or to `version.all` to invalidate + /// all versions of a key. + /// + /// The function returns `unsupported_feature` if this operation is not + /// supported by the host, and `not_found` if the identifier and version + /// don't match any existing key. + /// + /// This is an optional import, meaning that the function may not even + /// exist. + pub fn secrets_manager_invalidate(arg0: i32, arg1: i32, arg2: i32, arg3: i64) -> i32; + } +} +/// Generate a new key pair. +/// +/// Internally, a key pair stores the supplied algorithm and optional +/// parameters. +/// +/// Trying to use that key pair with different parameters will throw an +/// `invalid_key` error. +/// +/// This function may return `$crypto_errno.unsupported_feature` if key +/// generation is not supported by the host for the chosen algorithm. +/// +/// The function may also return `unsupported_algorithm` if the algorithm is not +/// supported by the host. +/// +/// Finally, if generating that type of key pair is an expensive operation, the +/// function may return `in_progress`. In that case, the guest should retry with +/// the same parameters until the function completes. +/// +/// Example usage: +/// +/// ```rust +/// let kp_handle = +/// ctx.keypair_generate(AlgorithmType::Signatures, "RSA_PKCS1_2048_SHA256", None)?; +/// ``` +pub unsafe fn keypair_generate( + algorithm_type: AlgorithmType, + algorithm: &str, + options: OptOptions, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_generate( + algorithm_type.0 as i32, + algorithm.as_ptr() as i32, + algorithm.len() as i32, + &options as *const _ as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Keypair)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Import a key pair. +/// +/// This function creates a `keypair` object from existing material. +/// +/// It may return `unsupported_algorithm` if the encoding scheme is not +/// supported, or `invalid_key` if the key cannot be decoded. +/// +/// The function may also return `unsupported_algorithm` if the algorithm is not +/// supported by the host. +/// +/// Example usage: +/// +/// ```rust +/// let kp_handle = ctx.keypair_import( +/// AlgorithmType::Signatures, +/// "RSA_PKCS1_2048_SHA256", +/// KeypairEncoding::PKCS8, +/// )?; +/// ``` +pub unsafe fn keypair_import( + algorithm_type: AlgorithmType, + algorithm: &str, + encoded: *const u8, + encoded_len: Size, + encoding: KeypairEncoding, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_import( + algorithm_type.0 as i32, + algorithm.as_ptr() as i32, + algorithm.len() as i32, + encoded as i32, + encoded_len as i32, + encoding.0 as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Keypair)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Generate a new managed key pair. +/// +/// The key pair is generated and stored by the secrets management facilities. +/// +/// It may be used through its identifier, but the host may not allow it to be +/// exported. +/// +/// The function returns the `unsupported_feature` error code if secrets +/// management facilities are not supported by the host, +/// or `unsupported_algorithm` if a key cannot be created for the chosen +/// algorithm. +/// +/// The function may also return `unsupported_algorithm` if the algorithm is not +/// supported by the host. +/// +/// This is also an optional import, meaning that the function may not even +/// exist. +pub unsafe fn keypair_generate_managed( + secrets_manager: SecretsManager, + algorithm_type: AlgorithmType, + algorithm: &str, + options: OptOptions, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_generate_managed( + secrets_manager as i32, + algorithm_type.0 as i32, + algorithm.as_ptr() as i32, + algorithm.len() as i32, + &options as *const _ as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Keypair)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Store a key pair into the secrets manager. +/// +/// On success, the function stores the key pair identifier into `$kp_id`, +/// into which up to `$kp_id_max_len` can be written. +/// +/// The function returns `overflow` if the supplied buffer is too small. +pub unsafe fn keypair_store_managed( + secrets_manager: SecretsManager, + kp: Keypair, + kp_id: *mut u8, + kp_id_max_len: Size, +) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_store_managed( + secrets_manager as i32, + kp as i32, + kp_id as i32, + kp_id_max_len as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Replace a managed key pair. +/// +/// This function crates a new version of a managed key pair, by replacing +/// `$kp_old` with `$kp_new`. +/// +/// It does several things: +/// +/// - The key identifier for `$kp_new` is set to the one of `$kp_old`. +/// - A new, unique version identifier is assigned to `$kp_new`. This version +/// will be equivalent to using `$version_latest` until the key is replaced. +/// - The `$kp_old` handle is closed. +/// +/// Both keys must share the same algorithm and have compatible parameters. If +/// this is not the case, `incompatible_keys` is returned. +/// +/// The function may also return the `unsupported_feature` error code if secrets +/// management facilities are not supported by the host, or if keys cannot be +/// rotated. +/// +/// Finally, `prohibited_operation` can be returned if `$kp_new` wasn't created +/// by the secrets manager, and the secrets manager prohibits imported keys. +/// +/// If the operation succeeded, the new version is returned. +/// +/// This is an optional import, meaning that the function may not even exist. +pub unsafe fn keypair_replace_managed( + secrets_manager: SecretsManager, + kp_old: Keypair, + kp_new: Keypair, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_replace_managed( + secrets_manager as i32, + kp_old as i32, + kp_new as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Version)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Return the key pair identifier and version of a managed key pair. +/// +/// If the key pair is not managed, `unsupported_feature` is returned instead. +/// +/// This is an optional import, meaning that the function may not even exist. +pub unsafe fn keypair_id( + kp: Keypair, + kp_id: *mut u8, + kp_id_max_len: Size, +) -> Result<(Size, Version), CryptoErrno> { + let mut rp0 = MaybeUninit::::uninit(); + let mut rp1 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_id( + kp as i32, + kp_id as i32, + kp_id_max_len as i32, + rp0.as_mut_ptr() as i32, + rp1.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(( + core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size), + core::ptr::read(rp1.as_mut_ptr() as i32 as *const Version), + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Return a managed key pair from a key identifier. +/// +/// `kp_version` can be set to `version_latest` to retrieve the most recent +/// version of a key pair. +/// +/// If no key pair matching the provided information is found, `not_found` is +/// returned instead. +/// +/// This is an optional import, meaning that the function may not even exist. +/// ``` +pub unsafe fn keypair_from_id( + secrets_manager: SecretsManager, + kp_id: *const u8, + kp_id_len: Size, + kp_version: Version, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_from_id( + secrets_manager as i32, + kp_id as i32, + kp_id_len as i32, + kp_version as i64, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Keypair)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Create a key pair from a public key and a secret key. +pub unsafe fn keypair_from_pk_and_sk( + publickey: Publickey, + secretkey: Secretkey, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_from_pk_and_sk( + publickey as i32, + secretkey as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Keypair)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Export a key pair as the given encoding format. +/// +/// May return `prohibited_operation` if this operation is denied or +/// `unsupported_encoding` if the encoding is not supported. +pub unsafe fn keypair_export( + kp: Keypair, + encoding: KeypairEncoding, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_export( + kp as i32, + encoding.0 as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const ArrayOutput + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Get the public key of a key pair. +pub unsafe fn keypair_publickey(kp: Keypair) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_publickey( + kp as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Publickey)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Get the secret key of a key pair. +pub unsafe fn keypair_secretkey(kp: Keypair) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_secretkey( + kp as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Secretkey)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Destroy a key pair. +/// +/// The host will automatically wipe traces of the secret key from memory. +/// +/// If this is a managed key, the key will not be removed from persistent +/// storage, and can be reconstructed later using the key identifier. +pub unsafe fn keypair_close(kp: Keypair) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_close(kp as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Import a public key. +/// +/// The function may return `unsupported_encoding` if importing from the given +/// format is not implemented or incompatible with the key type. +/// +/// It may also return `invalid_key` if the key doesn't appear to match the +/// supplied algorithm. +/// +/// Finally, the function may return `unsupported_algorithm` if the algorithm is +/// not supported by the host. +/// +/// Example usage: +/// +/// ```rust +/// let pk_handle = +/// ctx.publickey_import(AlgorithmType::Signatures, encoded, PublicKeyEncoding::Sec)?; +/// ``` +pub unsafe fn publickey_import( + algorithm_type: AlgorithmType, + algorithm: &str, + encoded: *const u8, + encoded_len: Size, + encoding: PublickeyEncoding, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::publickey_import( + algorithm_type.0 as i32, + algorithm.as_ptr() as i32, + algorithm.len() as i32, + encoded as i32, + encoded_len as i32, + encoding.0 as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Publickey)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Export a public key as the given encoding format. +/// +/// May return `unsupported_encoding` if the encoding is not supported. +pub unsafe fn publickey_export( + pk: Publickey, + encoding: PublickeyEncoding, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::publickey_export( + pk as i32, + encoding.0 as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const ArrayOutput + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Check that a public key is valid and in canonical form. +/// +/// This function may perform stricter checks than those made during importation +/// at the expense of additional CPU cycles. +/// +/// The function returns `invalid_key` if the public key didn't pass the checks. +pub unsafe fn publickey_verify(pk: Publickey) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_asymmetric_common::publickey_verify(pk as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Compute the public key for a secret key. +pub unsafe fn publickey_from_secretkey(sk: Secretkey) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::publickey_from_secretkey( + sk as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Publickey)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Destroy a public key. +/// +/// Objects are reference counted. It is safe to close an object immediately +/// after the last function needing it is called. +pub unsafe fn publickey_close(pk: Publickey) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_asymmetric_common::publickey_close(pk as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Import a secret key. +/// +/// The function may return `unsupported_encoding` if importing from the given +/// format is not implemented or incompatible with the key type. +/// +/// It may also return `invalid_key` if the key doesn't appear to match the +/// supplied algorithm. +/// +/// Finally, the function may return `unsupported_algorithm` if the algorithm is +/// not supported by the host. +/// +/// Example usage: +/// +/// ```rust +/// let pk_handle = ctx.secretkey_import(AlgorithmType::KX, encoded, SecretKeyEncoding::Raw)?; +/// ``` +pub unsafe fn secretkey_import( + algorithm_type: AlgorithmType, + algorithm: &str, + encoded: *const u8, + encoded_len: Size, + encoding: SecretkeyEncoding, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::secretkey_import( + algorithm_type.0 as i32, + algorithm.as_ptr() as i32, + algorithm.len() as i32, + encoded as i32, + encoded_len as i32, + encoding.0 as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Secretkey)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Export a secret key as the given encoding format. +/// +/// May return `unsupported_encoding` if the encoding is not supported. +pub unsafe fn secretkey_export( + sk: Secretkey, + encoding: SecretkeyEncoding, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_asymmetric_common::secretkey_export( + sk as i32, + encoding.0 as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const ArrayOutput + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Destroy a secret key. +/// +/// Objects are reference counted. It is safe to close an object immediately +/// after the last function needing it is called. +pub unsafe fn secretkey_close(sk: Secretkey) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_asymmetric_common::secretkey_close(sk as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +pub mod wasi_ephemeral_crypto_asymmetric_common { + #[link(wasm_import_module = "wasi_ephemeral_crypto_asymmetric_common")] + extern "C" { + /// Generate a new key pair. + /// + /// Internally, a key pair stores the supplied algorithm and optional + /// parameters. + /// + /// Trying to use that key pair with different parameters will throw an + /// `invalid_key` error. + /// + /// This function may return `$crypto_errno.unsupported_feature` if key + /// generation is not supported by the host for the chosen algorithm. + /// + /// The function may also return `unsupported_algorithm` if the + /// algorithm is not supported by the host. + /// + /// Finally, if generating that type of key pair is an expensive + /// operation, the function may return `in_progress`. + /// In that case, the guest should retry with the same parameters until + /// the function completes. + /// + /// Example usage: + /// + /// ```rust + /// let kp_handle = + /// ctx.keypair_generate(AlgorithmType::Signatures, "RSA_PKCS1_2048_SHA256", None)?; + /// ``` + pub fn keypair_generate(arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32) -> i32; + /// Import a key pair. + /// + /// This function creates a `keypair` object from existing material. + /// + /// It may return `unsupported_algorithm` if the encoding scheme is not + /// supported, or `invalid_key` if the key cannot be decoded. + /// + /// The function may also return `unsupported_algorithm` if the + /// algorithm is not supported by the host. + /// + /// Example usage: + /// + /// ```rust + /// let kp_handle = ctx.keypair_import( + /// AlgorithmType::Signatures, + /// "RSA_PKCS1_2048_SHA256", + /// KeypairEncoding::PKCS8, + /// )?; + /// ``` + pub fn keypair_import( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, + arg6: i32, + ) -> i32; + /// __(optional)__ + /// Generate a new managed key pair. + /// + /// The key pair is generated and stored by the secrets management + /// facilities. + /// + /// It may be used through its identifier, but the host may not allow it + /// to be exported. + /// + /// The function returns the `unsupported_feature` error code if secrets + /// management facilities are not supported by the host, + /// or `unsupported_algorithm` if a key cannot be created for the chosen + /// algorithm. + /// + /// The function may also return `unsupported_algorithm` if the + /// algorithm is not supported by the host. + /// + /// This is also an optional import, meaning that the function may not + /// even exist. + pub fn keypair_generate_managed( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, + ) -> i32; + /// __(optional)__ + /// Store a key pair into the secrets manager. + /// + /// On success, the function stores the key pair identifier into + /// `$kp_id`, into which up to `$kp_id_max_len` can be written. + /// + /// The function returns `overflow` if the supplied buffer is too small. + pub fn keypair_store_managed(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; + /// __(optional)__ + /// Replace a managed key pair. + /// + /// This function crates a new version of a managed key pair, by + /// replacing `$kp_old` with `$kp_new`. + /// + /// It does several things: + /// + /// - The key identifier for `$kp_new` is set to the one of `$kp_old`. + /// - A new, unique version identifier is assigned to `$kp_new`. This + /// version will be equivalent to using `$version_latest` until the + /// key is replaced. + /// - The `$kp_old` handle is closed. + /// + /// Both keys must share the same algorithm and have compatible + /// parameters. If this is not the case, `incompatible_keys` is + /// returned. + /// + /// The function may also return the `unsupported_feature` error code if + /// secrets management facilities are not supported by the host, + /// or if keys cannot be rotated. + /// + /// Finally, `prohibited_operation` can be returned if `$kp_new` wasn't + /// created by the secrets manager, and the secrets manager prohibits + /// imported keys. + /// + /// If the operation succeeded, the new version is returned. + /// + /// This is an optional import, meaning that the function may not even + /// exist. + pub fn keypair_replace_managed(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; + /// __(optional)__ + /// Return the key pair identifier and version of a managed key pair. + /// + /// If the key pair is not managed, `unsupported_feature` is returned + /// instead. + /// + /// This is an optional import, meaning that the function may not even + /// exist. + pub fn keypair_id(arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32) -> i32; + /// __(optional)__ + /// Return a managed key pair from a key identifier. + /// + /// `kp_version` can be set to `version_latest` to retrieve the most + /// recent version of a key pair. + /// + /// If no key pair matching the provided information is found, + /// `not_found` is returned instead. + /// + /// This is an optional import, meaning that the function may not even + /// exist. ``` + pub fn keypair_from_id(arg0: i32, arg1: i32, arg2: i32, arg3: i64, arg4: i32) -> i32; + /// Create a key pair from a public key and a secret key. + pub fn keypair_from_pk_and_sk(arg0: i32, arg1: i32, arg2: i32) -> i32; + /// Export a key pair as the given encoding format. + /// + /// May return `prohibited_operation` if this operation is denied or + /// `unsupported_encoding` if the encoding is not supported. + pub fn keypair_export(arg0: i32, arg1: i32, arg2: i32) -> i32; + /// Get the public key of a key pair. + pub fn keypair_publickey(arg0: i32, arg1: i32) -> i32; + /// Get the secret key of a key pair. + pub fn keypair_secretkey(arg0: i32, arg1: i32) -> i32; + /// Destroy a key pair. + /// + /// The host will automatically wipe traces of the secret key from + /// memory. + /// + /// If this is a managed key, the key will not be removed from + /// persistent storage, and can be reconstructed later using the key + /// identifier. + pub fn keypair_close(arg0: i32) -> i32; + /// Import a public key. + /// + /// The function may return `unsupported_encoding` if importing from the + /// given format is not implemented or incompatible with the key type. + /// + /// It may also return `invalid_key` if the key doesn't appear to match + /// the supplied algorithm. + /// + /// Finally, the function may return `unsupported_algorithm` if the + /// algorithm is not supported by the host. + /// + /// Example usage: + /// + /// ```rust + /// let pk_handle = + /// ctx.publickey_import(AlgorithmType::Signatures, encoded, PublicKeyEncoding::Sec)?; + /// ``` + pub fn publickey_import( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, + arg6: i32, + ) -> i32; + /// Export a public key as the given encoding format. + /// + /// May return `unsupported_encoding` if the encoding is not supported. + pub fn publickey_export(arg0: i32, arg1: i32, arg2: i32) -> i32; + /// Check that a public key is valid and in canonical form. + /// + /// This function may perform stricter checks than those made during + /// importation at the expense of additional CPU cycles. + /// + /// The function returns `invalid_key` if the public key didn't pass the + /// checks. + pub fn publickey_verify(arg0: i32) -> i32; + /// Compute the public key for a secret key. + pub fn publickey_from_secretkey(arg0: i32, arg1: i32) -> i32; + /// Destroy a public key. + /// + /// Objects are reference counted. It is safe to close an object + /// immediately after the last function needing it is called. + pub fn publickey_close(arg0: i32) -> i32; + /// Import a secret key. + /// + /// The function may return `unsupported_encoding` if importing from the + /// given format is not implemented or incompatible with the key type. + /// + /// It may also return `invalid_key` if the key doesn't appear to match + /// the supplied algorithm. + /// + /// Finally, the function may return `unsupported_algorithm` if the + /// algorithm is not supported by the host. + /// + /// Example usage: + /// + /// ```rust + /// let pk_handle = ctx.secretkey_import(AlgorithmType::KX, encoded, SecretKeyEncoding::Raw)?; + /// ``` + pub fn secretkey_import( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, + arg6: i32, + ) -> i32; + /// Export a secret key as the given encoding format. + /// + /// May return `unsupported_encoding` if the encoding is not supported. + pub fn secretkey_export(arg0: i32, arg1: i32, arg2: i32) -> i32; + /// Destroy a secret key. + /// + /// Objects are reference counted. It is safe to close an object + /// immediately after the last function needing it is called. + pub fn secretkey_close(arg0: i32) -> i32; + } +} +/// Export a signature. +/// +/// This function exports a signature object using the specified encoding. +/// +/// May return `unsupported_encoding` if the signature cannot be encoded into +/// the given format. +pub unsafe fn signature_export( + signature: Signature, + encoding: SignatureEncoding, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_signatures::signature_export( + signature as i32, + encoding.0 as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const ArrayOutput + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Create a signature object. +/// +/// This object can be used along with a public key to verify an existing +/// signature. +/// +/// It may return `invalid_signature` if the signature is invalid or +/// incompatible with the specified algorithm, as well as `unsupported_encoding` +/// if the encoding is not compatible with the signature type. +/// +/// The function may also return `unsupported_algorithm` if the algorithm is not +/// supported by the host. +/// +/// Example usage: +/// +/// ```rust +/// let signature_handle = +/// ctx.signature_import("ECDSA_P256_SHA256", SignatureEncoding::DER, encoded)?; +/// ``` +pub unsafe fn signature_import( + algorithm: &str, + encoded: *const u8, + encoded_len: Size, + encoding: SignatureEncoding, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_signatures::signature_import( + algorithm.as_ptr() as i32, + algorithm.len() as i32, + encoded as i32, + encoded_len as i32, + encoding.0 as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Signature)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Create a new state to collect data to compute a signature on. +/// +/// This function allows data to be signed to be supplied in a streaming +/// fashion. +/// +/// The state is not closed and can be used after a signature has been computed, +/// allowing incremental updates by calling `signature_state_update()` again +/// afterwards. +/// +/// Example usage - signature creation +/// +/// ```rust +/// let kp_handle = ctx.keypair_import( +/// AlgorithmType::Signatures, +/// "Ed25519ph", +/// keypair, +/// KeypairEncoding::Raw, +/// )?; +/// let state_handle = ctx.signature_state_open(kp_handle)?; +/// ctx.signature_state_update(state_handle, b"message part 1")?; +/// ctx.signature_state_update(state_handle, b"message part 2")?; +/// let sig_handle = ctx.signature_state_sign(state_handle)?; +/// let raw_sig = ctx.signature_export(sig_handle, SignatureEncoding::Raw)?; +/// ``` +pub unsafe fn signature_state_open(kp: SignatureKeypair) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = + wasi_ephemeral_crypto_signatures::signature_state_open(kp as i32, rp0.as_mut_ptr() as i32); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const SignatureState + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Absorb data into the signature state. +/// +/// This function may return `unsupported_feature` is the selected algorithm +/// doesn't support incremental updates. +pub unsafe fn signature_state_update( + state: SignatureState, + input: *const u8, + input_len: Size, +) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_signatures::signature_state_update( + state as i32, + input as i32, + input_len as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Compute a signature for all the data collected up to that point. +/// +/// The function can be called multiple times for incremental signing. +pub unsafe fn signature_state_sign(state: SignatureState) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_signatures::signature_state_sign( + state as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const ArrayOutput + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Destroy a signature state. +/// +/// Objects are reference counted. It is safe to close an object immediately +/// after the last function needing it is called. +/// +/// Note that closing a signature state doesn't close or invalidate the key pair +/// object, that be reused for further signatures. +pub unsafe fn signature_state_close(state: SignatureState) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_signatures::signature_state_close(state as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Create a new state to collect data to verify a signature on. +/// +/// This is the verification counterpart of `signature_state`. +/// +/// Data can be injected using `signature_verification_state_update()`, and the +/// state is not closed after a verification, allowing incremental verification. +/// +/// Example usage - signature verification: +/// +/// ```rust +/// let pk_handle = ctx.publickey_import( +/// AlgorithmType::Signatures, +/// "ECDSA_P256_SHA256", +/// encoded_pk, +/// PublicKeyEncoding::Sec, +/// )?; +/// let signature_handle = ctx.signature_import( +/// AlgorithmType::Signatures, +/// "ECDSA_P256_SHA256", +/// encoded_sig, +/// SignatureEncoding::Der, +/// )?; +/// let state_handle = ctx.signature_verification_state_open(pk_handle)?; +/// ctx.signature_verification_state_update(state_handle, "message")?; +/// ctx.signature_verification_state_verify(signature_handle)?; +/// ``` +pub unsafe fn signature_verification_state_open( + kp: SignaturePublickey, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_signatures::signature_verification_state_open( + kp as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const SignatureVerificationState + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Absorb data into the signature verification state. +/// +/// This function may return `unsupported_feature` is the selected algorithm +/// doesn't support incremental updates. +pub unsafe fn signature_verification_state_update( + state: SignatureVerificationState, + input: *const u8, + input_len: Size, +) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_signatures::signature_verification_state_update( + state as i32, + input as i32, + input_len as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Check that the given signature is verifies for the data collected up to that +/// point point. +/// +/// The state is not closed and can absorb more data to allow for incremental +/// verification. +/// +/// The function returns `invalid_signature` if the signature doesn't appear to +/// be valid. +pub unsafe fn signature_verification_state_verify( + state: SignatureVerificationState, + signature: Signature, +) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_signatures::signature_verification_state_verify( + state as i32, + signature as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Destroy a signature verification state. +/// +/// Objects are reference counted. It is safe to close an object immediately +/// after the last function needing it is called. +/// +/// Note that closing a signature state doesn't close or invalidate the public +/// key object, that be reused for further verifications. +pub unsafe fn signature_verification_state_close( + state: SignatureVerificationState, +) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_signatures::signature_verification_state_close(state as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Destroy a signature. +/// +/// Objects are reference counted. It is safe to close an object immediately +/// after the last function needing it is called. +pub unsafe fn signature_close(signature: Signature) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_signatures::signature_close(signature as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +pub mod wasi_ephemeral_crypto_signatures { + #[link(wasm_import_module = "wasi_ephemeral_crypto_signatures")] + extern "C" { + /// Export a signature. + /// + /// This function exports a signature object using the specified + /// encoding. + /// + /// May return `unsupported_encoding` if the signature cannot be encoded + /// into the given format. + pub fn signature_export(arg0: i32, arg1: i32, arg2: i32) -> i32; + /// Create a signature object. + /// + /// This object can be used along with a public key to verify an + /// existing signature. + /// + /// It may return `invalid_signature` if the signature is invalid or + /// incompatible with the specified algorithm, as well as + /// `unsupported_encoding` if the encoding is not compatible with the + /// signature type. + /// + /// The function may also return `unsupported_algorithm` if the + /// algorithm is not supported by the host. + /// + /// Example usage: + /// + /// ```rust + /// let signature_handle = + /// ctx.signature_import("ECDSA_P256_SHA256", SignatureEncoding::DER, encoded)?; + /// ``` + pub fn signature_import( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, + ) -> i32; + /// Create a new state to collect data to compute a signature on. + /// + /// This function allows data to be signed to be supplied in a streaming + /// fashion. + /// + /// The state is not closed and can be used after a signature has been + /// computed, allowing incremental updates by calling + /// `signature_state_update()` again afterwards. + /// + /// Example usage - signature creation + /// + /// ```rust + /// let kp_handle = ctx.keypair_import( + /// AlgorithmType::Signatures, + /// "Ed25519ph", + /// keypair, + /// KeypairEncoding::Raw, + /// )?; + /// let state_handle = ctx.signature_state_open(kp_handle)?; + /// ctx.signature_state_update(state_handle, b"message part 1")?; + /// ctx.signature_state_update(state_handle, b"message part 2")?; + /// let sig_handle = ctx.signature_state_sign(state_handle)?; + /// let raw_sig = ctx.signature_export(sig_handle, SignatureEncoding::Raw)?; + /// ``` + pub fn signature_state_open(arg0: i32, arg1: i32) -> i32; + /// Absorb data into the signature state. + /// + /// This function may return `unsupported_feature` is the selected + /// algorithm doesn't support incremental updates. + pub fn signature_state_update(arg0: i32, arg1: i32, arg2: i32) -> i32; + /// Compute a signature for all the data collected up to that point. + /// + /// The function can be called multiple times for incremental signing. + pub fn signature_state_sign(arg0: i32, arg1: i32) -> i32; + /// Destroy a signature state. + /// + /// Objects are reference counted. It is safe to close an object + /// immediately after the last function needing it is called. + /// + /// Note that closing a signature state doesn't close or invalidate the + /// key pair object, that be reused for further signatures. + pub fn signature_state_close(arg0: i32) -> i32; + /// Create a new state to collect data to verify a signature on. + /// + /// This is the verification counterpart of `signature_state`. + /// + /// Data can be injected using `signature_verification_state_update()`, + /// and the state is not closed after a verification, allowing + /// incremental verification. + /// + /// Example usage - signature verification: + /// + /// ```rust + /// let pk_handle = ctx.publickey_import( + /// AlgorithmType::Signatures, + /// "ECDSA_P256_SHA256", + /// encoded_pk, + /// PublicKeyEncoding::Sec, + /// )?; + /// let signature_handle = ctx.signature_import( + /// AlgorithmType::Signatures, + /// "ECDSA_P256_SHA256", + /// encoded_sig, + /// SignatureEncoding::Der, + /// )?; + /// let state_handle = ctx.signature_verification_state_open(pk_handle)?; + /// ctx.signature_verification_state_update(state_handle, "message")?; + /// ctx.signature_verification_state_verify(signature_handle)?; + /// ``` + pub fn signature_verification_state_open(arg0: i32, arg1: i32) -> i32; + /// Absorb data into the signature verification state. + /// + /// This function may return `unsupported_feature` is the selected + /// algorithm doesn't support incremental updates. + pub fn signature_verification_state_update(arg0: i32, arg1: i32, arg2: i32) -> i32; + /// Check that the given signature is verifies for the data collected up + /// to that point point. + /// + /// The state is not closed and can absorb more data to allow for + /// incremental verification. + /// + /// The function returns `invalid_signature` if the signature doesn't + /// appear to be valid. + pub fn signature_verification_state_verify(arg0: i32, arg1: i32) -> i32; + /// Destroy a signature verification state. + /// + /// Objects are reference counted. It is safe to close an object + /// immediately after the last function needing it is called. + /// + /// Note that closing a signature state doesn't close or invalidate the + /// public key object, that be reused for further verifications. + pub fn signature_verification_state_close(arg0: i32) -> i32; + /// Destroy a signature. + /// + /// Objects are reference counted. It is safe to close an object + /// immediately after the last function needing it is called. + pub fn signature_close(arg0: i32) -> i32; + } +} +/// Generate a new symmetric key for a given algorithm. +/// +/// `options` can be `None` to use the default parameters, or an +/// algoritm-specific set of parameters to override. +/// +/// This function may return `unsupported_feature` if key generation is not +/// supported by the host for the chosen algorithm, or `unsupported_algorithm` +/// if the algorithm is not supported by the host. +pub unsafe fn symmetric_key_generate( + algorithm: &str, + options: OptOptions, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_generate( + algorithm.as_ptr() as i32, + algorithm.len() as i32, + &options as *const _ as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const SymmetricKey + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Create a symmetric key from raw material. +/// +/// The algorithm is internally stored along with the key, and trying to use the +/// key with an operation expecting a different algorithm will return +/// `invalid_key`. +/// +/// The function may also return `unsupported_algorithm` if the algorithm is not +/// supported by the host. +pub unsafe fn symmetric_key_import( + algorithm: &str, + raw: *const u8, + raw_len: Size, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_import( + algorithm.as_ptr() as i32, + algorithm.len() as i32, + raw as i32, + raw_len as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const SymmetricKey + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Export a symmetric key as raw material. +/// +/// This is mainly useful to export a managed key. +/// +/// May return `prohibited_operation` if this operation is denied. +pub unsafe fn symmetric_key_export( + symmetric_key: SymmetricKey, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_export( + symmetric_key as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const ArrayOutput + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Destroy a symmetric key. +/// +/// Objects are reference counted. It is safe to close an object immediately +/// after the last function needing it is called. +pub unsafe fn symmetric_key_close(symmetric_key: SymmetricKey) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_close(symmetric_key as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Generate a new managed symmetric key. +/// +/// The key is generated and stored by the secrets management facilities. +/// +/// It may be used through its identifier, but the host may not allow it to be +/// exported. +/// +/// The function returns the `unsupported_feature` error code if secrets +/// management facilities are not supported by the host, +/// or `unsupported_algorithm` if a key cannot be created for the chosen +/// algorithm. +/// +/// The function may also return `unsupported_algorithm` if the algorithm is not +/// supported by the host. +/// +/// This is also an optional import, meaning that the function may not even +/// exist. +pub unsafe fn symmetric_key_generate_managed( + secrets_manager: SecretsManager, + algorithm: &str, + options: OptOptions, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_generate_managed( + secrets_manager as i32, + algorithm.as_ptr() as i32, + algorithm.len() as i32, + &options as *const _ as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const SymmetricKey + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Store a symmetric key into the secrets manager. +/// +/// On success, the function stores the key identifier into `$symmetric_key_id`, +/// into which up to `$symmetric_key_id_max_len` can be written. +/// +/// The function returns `overflow` if the supplied buffer is too small. +pub unsafe fn symmetric_key_store_managed( + secrets_manager: SecretsManager, + symmetric_key: SymmetricKey, + symmetric_key_id: *mut u8, + symmetric_key_id_max_len: Size, +) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_store_managed( + secrets_manager as i32, + symmetric_key as i32, + symmetric_key_id as i32, + symmetric_key_id_max_len as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Replace a managed symmetric key. +/// +/// This function crates a new version of a managed symmetric key, by replacing +/// `$kp_old` with `$kp_new`. +/// +/// It does several things: +/// +/// - The key identifier for `$symmetric_key_new` is set to the one of +/// `$symmetric_key_old`. +/// - A new, unique version identifier is assigned to `$kp_new`. This version +/// will be equivalent to using `$version_latest` until the key is replaced. +/// - The `$symmetric_key_old` handle is closed. +/// +/// Both keys must share the same algorithm and have compatible parameters. If +/// this is not the case, `incompatible_keys` is returned. +/// +/// The function may also return the `unsupported_feature` error code if secrets +/// management facilities are not supported by the host, or if keys cannot be +/// rotated. +/// +/// Finally, `prohibited_operation` can be returned if `$symmetric_key_new` +/// wasn't created by the secrets manager, and the secrets manager prohibits +/// imported keys. +/// +/// If the operation succeeded, the new version is returned. +/// +/// This is an optional import, meaning that the function may not even exist. +pub unsafe fn symmetric_key_replace_managed( + secrets_manager: SecretsManager, + symmetric_key_old: SymmetricKey, + symmetric_key_new: SymmetricKey, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_replace_managed( + secrets_manager as i32, + symmetric_key_old as i32, + symmetric_key_new as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Version)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Return the key identifier and version of a managed symmetric key. +/// +/// If the key is not managed, `unsupported_feature` is returned instead. +/// +/// This is an optional import, meaning that the function may not even exist. +pub unsafe fn symmetric_key_id( + symmetric_key: SymmetricKey, + symmetric_key_id: *mut u8, + symmetric_key_id_max_len: Size, +) -> Result<(Size, Version), CryptoErrno> { + let mut rp0 = MaybeUninit::::uninit(); + let mut rp1 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_id( + symmetric_key as i32, + symmetric_key_id as i32, + symmetric_key_id_max_len as i32, + rp0.as_mut_ptr() as i32, + rp1.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(( + core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size), + core::ptr::read(rp1.as_mut_ptr() as i32 as *const Version), + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// __(optional)__ +/// Return a managed symmetric key from a key identifier. +/// +/// `kp_version` can be set to `version_latest` to retrieve the most recent +/// version of a symmetric key. +/// +/// If no key matching the provided information is found, `not_found` is +/// returned instead. +/// +/// This is an optional import, meaning that the function may not even exist. +pub unsafe fn symmetric_key_from_id( + secrets_manager: SecretsManager, + symmetric_key_id: *const u8, + symmetric_key_id_len: Size, + symmetric_key_version: Version, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_from_id( + secrets_manager as i32, + symmetric_key_id as i32, + symmetric_key_id_len as i32, + symmetric_key_version as i64, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const SymmetricKey + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Create a new state to aborb and produce data using symmetric operations. +/// +/// The state remains valid after every operation in order to support +/// incremental updates. +/// +/// The function has two optional parameters: a key and an options set. +/// +/// It will fail with a `key_not_supported` error code if a key was provided but +/// the chosen algorithm doesn't natively support keying. +/// +/// On the other hand, if a key is required, but was not provided, a +/// `key_required` error will be thrown. +/// +/// Some algorithms may require additional parameters. They have to be supplied +/// as an options set: +/// +/// ```rust +/// let options_handle = ctx.options_open()?; +/// ctx.options_set("context", b"My application")?; +/// ctx.options_set_u64("fanout", 16)?; +/// let state_handle = ctx.symmetric_state_open("BLAKE2b-512", None, Some(options_handle))?; +/// ``` +/// +/// If some parameters are mandatory but were not set, the `parameters_missing` +/// error code will be returned. +/// +/// A notable exception is the `nonce` parameter, that is common to most AEAD +/// constructions. +/// +/// If a nonce is required but was not supplied: +/// +/// - If it is safe to do so, the host will automatically generate a nonce. This +/// is true for nonces that are large enough to be randomly generated, or if +/// the host is able to maintain a global counter. +/// - If not, the function will fail and return the dedicated `nonce_required` +/// error code. +/// +/// A nonce that was automatically generated can be retrieved after the function +/// returns with `symmetric_state_get(state_handle, "nonce")`. +/// +/// **Sample usage patterns:** +/// +/// - **Hashing** +/// +/// ```rust +/// let mut out = [0u8; 64]; +/// let state_handle = ctx.symmetric_state_open("SHAKE-128", None, None)?; +/// ctx.symmetric_state_absorb(state_handle, b"data")?; +/// ctx.symmetric_state_absorb(state_handle, b"more_data")?; +/// ctx.symmetric_state_squeeze(state_handle, &mut out)?; +/// ``` +/// +/// - **MAC** +/// +/// ```rust +/// let mut raw_tag = [0u8; 64]; +/// let key_handle = ctx.symmetric_key_import("HMAC/SHA-512", b"key")?; +/// let state_handle = ctx.symmetric_state_open("HMAC/SHA-512", Some(key_handle), None)?; +/// ctx.symmetric_state_absorb(state_handle, b"data")?; +/// ctx.symmetric_state_absorb(state_handle, b"more_data")?; +/// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; +/// ctx.symmetric_tag_pull(computed_tag_handle, &mut raw_tag)?; +/// ``` +/// +/// Verification: +/// +/// ```rust +/// let state_handle = ctx.symmetric_state_open("HMAC/SHA-512", Some(key_handle), None)?; +/// ctx.symmetric_state_absorb(state_handle, b"data")?; +/// ctx.symmetric_state_absorb(state_handle, b"more_data")?; +/// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; +/// ctx.symmetric_tag_verify(computed_tag_handle, expected_raw_tag)?; +/// ``` +/// +/// - **Tuple hashing** +/// +/// ```rust +/// let mut out = [0u8; 64]; +/// let state_handle = ctx.symmetric_state_open("TupleHashXOF256", None, None)?; +/// ctx.symmetric_state_absorb(state_handle, b"value 1")?; +/// ctx.symmetric_state_absorb(state_handle, b"value 2")?; +/// ctx.symmetric_state_absorb(state_handle, b"value 3")?; +/// ctx.symmetric_state_squeeze(state_handle, &mut out)?; +/// ``` +/// Unlike MACs and regular hash functions, inputs are domain separated instead +/// of being concatenated. +/// +/// - **Key derivation using extract-and-expand** +/// +/// Extract: +/// +/// ```rust +/// let mut prk = vec![0u8; 64]; +/// let key_handle = ctx.symmetric_key_import("HKDF-EXTRACT/SHA-512", b"key")?; +/// let state_handle = ctx.symmetric_state_open("HKDF-EXTRACT/SHA-512", Some(key_handle), None)?; +/// ctx.symmetric_state_absorb(state_handle, b"salt")?; +/// let prk_handle = ctx.symmetric_state_squeeze_key(state_handle, "HKDF-EXPAND/SHA-512")?; +/// ``` +/// +/// Expand: +/// +/// ```rust +/// let mut subkey = vec![0u8; 32]; +/// let state_handle = ctx.symmetric_state_open("HKDF-EXPAND/SHA-512", Some(prk_handle), None)?; +/// ctx.symmetric_state_absorb(state_handle, b"info")?; +/// ctx.symmetric_state_squeeze(state_handle, &mut subkey)?; +/// ``` +/// +/// - **Key derivation using a XOF** +/// +/// ```rust +/// let mut subkey1 = vec![0u8; 32]; +/// let mut subkey2 = vec![0u8; 32]; +/// let key_handle = ctx.symmetric_key_import("BLAKE3", b"key")?; +/// let state_handle = ctx.symmetric_state_open("BLAKE3", Some(key_handle), None)?; +/// ctx.symmetric_absorb(state_handle, b"context")?; +/// ctx.squeeze(state_handle, &mut subkey1)?; +/// ctx.squeeze(state_handle, &mut subkey2)?; +/// ``` +/// +/// - **Password hashing** +/// +/// ```rust +/// let mut memory = vec![0u8; 1_000_000_000]; +/// let options_handle = ctx.symmetric_options_open()?; +/// ctx.symmetric_options_set_guest_buffer(options_handle, "memory", &mut memory)?; +/// ctx.symmetric_options_set_u64(options_handle, "opslimit", 5)?; +/// ctx.symmetric_options_set_u64(options_handle, "parallelism", 8)?; +/// +/// let state_handle = ctx.symmetric_state_open("ARGON2-ID-13", None, Some(options))?; +/// ctx.symmtric_state_absorb(state_handle, b"password")?; +/// +/// let pw_str_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; +/// let mut pw_str = vec![0u8; ctx.symmetric_tag_len(pw_str_handle)?]; +/// ctx.symmetric_tag_pull(pw_str_handle, &mut pw_str)?; +/// ``` +/// +/// - **AEAD encryption with an explicit nonce** +/// +/// ```rust +/// let key_handle = ctx.symmetric_key_generate("AES-256-GCM", None)?; +/// let message = b"test"; +/// +/// let options_handle = ctx.symmetric_options_open()?; +/// ctx.symmetric_options_set(options_handle, "nonce", nonce)?; +/// +/// let state_handle = +/// ctx.symmetric_state_open("AES-256-GCM", Some(key_handle), Some(options_handle))?; +/// let mut ciphertext = vec![0u8; message.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; +/// ctx.symmetric_state_absorb(state_handle, "additional data")?; +/// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, message)?; +/// ``` +/// +/// - **AEAD encryption with automatic nonce generation** +/// +/// ```rust +/// let key_handle = ctx.symmetric_key_generate("AES-256-GCM-SIV", None)?; +/// let message = b"test"; +/// let mut nonce = [0u8; 24]; +/// +/// let state_handle = ctx.symmetric_state_open("AES-256-GCM-SIV", Some(key_handle), None)?; +/// +/// let nonce = ctx.symmetric_state_options_get(state_handle, "nonce")?; +/// +/// let mut ciphertext = vec![0u8; message.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; +/// ctx.symmetric_state_absorb(state_handle, "additional data")?; +/// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, message)?; +/// ``` +/// +/// - **Session authenticated modes** +/// +/// ```rust +/// let mut out = [0u8; 16]; +/// let mut out2 = [0u8; 16]; +/// let mut ciphertext = [0u8; 20]; +/// let key_handle = ctx.symmetric_key_generate("Xoodyak-128", None)?; +/// let state_handle = ctx.symmetric_state_open("Xoodyak-128", Some(key_handle), None)?; +/// ctx.symmetric_state_absorb(state_handle, b"data")?; +/// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, b"abcd")?; +/// ctx.symmetric_state_absorb(state_handle, b"more data")?; +/// ctx.symmetric_state_squeeze(state_handle, &mut out)?; +/// ctx.symmetric_state_squeeze(state_handle, &mut out2)?; +/// ctx.symmetric_state_ratchet(state_handle)?; +/// ctx.symmetric_state_absorb(state_handle, b"more data")?; +/// let next_key_handle = ctx.symmetric_state_squeeze_key(state_handle, "Xoodyak-128")?; +/// // ... +/// ``` +pub unsafe fn symmetric_state_open( + algorithm: &str, + key: OptSymmetricKey, + options: OptOptions, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_open( + algorithm.as_ptr() as i32, + algorithm.len() as i32, + &key as *const _ as i32, + &options as *const _ as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const SymmetricState + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Retrieve a parameter from the current state. +/// +/// In particular, `symmetric_state_options_get("nonce")` can be used to get a +/// nonce that as automatically generated. +/// +/// The function may return `options_not_set` if an option was not set, which is +/// different from an empty value. +/// +/// It may also return `unsupported_option` if the option doesn't exist for the +/// chosen algorithm. +pub unsafe fn symmetric_state_options_get( + handle: SymmetricState, + name: &str, + value: *mut u8, + value_max_len: Size, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_options_get( + handle as i32, + name.as_ptr() as i32, + name.len() as i32, + value as i32, + value_max_len as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Retrieve an integer parameter from the current state. +/// +/// The function may return `options_not_set` if an option was not set. +/// +/// It may also return `unsupported_option` if the option doesn't exist for the +/// chosen algorithm. +pub unsafe fn symmetric_state_options_get_u64( + handle: SymmetricState, + name: &str, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_options_get_u64( + handle as i32, + name.as_ptr() as i32, + name.len() as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const U64)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Clone a symmetric state. +/// +/// The function clones the internal state, assigns a new handle to it and +/// returns the new handle. +pub unsafe fn symmetric_state_clone(handle: SymmetricState) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_clone( + handle as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const SymmetricState + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Destroy a symmetric state. +/// +/// Objects are reference counted. It is safe to close an object immediately +/// after the last function needing it is called. +pub unsafe fn symmetric_state_close(handle: SymmetricState) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_close(handle as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Absorb data into the state. +/// +/// - **Hash functions:** adds data to be hashed. +/// - **MAC functions:** adds data to be authenticated. +/// - **Tuplehash-like constructions:** adds a new tuple to the state. +/// - **Key derivation functions:** adds to the IKM or to the subkey +/// information. +/// - **AEAD constructions:** adds additional data to be authenticated. +/// - **Stateful hash objects, permutation-based constructions:** absorbs. +/// +/// If the chosen algorithm doesn't accept input data, the `invalid_operation` +/// error code is returned. +/// +/// If too much data has been fed for the algorithm, `overflow` may be thrown. +pub unsafe fn symmetric_state_absorb( + handle: SymmetricState, + data: *const u8, + data_len: Size, +) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_absorb( + handle as i32, + data as i32, + data_len as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Squeeze bytes from the state. +/// +/// - **Hash functions:** this tries to output an `out_len` bytes digest from +/// the absorbed data. The hash function output will be truncated if +/// necessary. If the requested size is too large, the `invalid_len` error +/// code is returned. +/// - **Key derivation functions:** : outputs an arbitrary-long derived key. +/// - **RNGs, DRBGs, stream ciphers:**: outputs arbitrary-long data. +/// - **Stateful hash objects, permutation-based constructions:** squeeze. +/// +/// Other kinds of algorithms may return `invalid_operation` instead. +/// +/// For password-stretching functions, the function may return `in_progress`. +/// In that case, the guest should retry with the same parameters until the +/// function completes. +pub unsafe fn symmetric_state_squeeze( + handle: SymmetricState, + out: *mut u8, + out_len: Size, +) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_squeeze( + handle as i32, + out as i32, + out_len as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Compute and return a tag for all the data injected into the state so far. +/// +/// - **MAC functions**: returns a tag authenticating the absorbed data. +/// - **Tuplehash-like constructions:** returns a tag authenticating all the +/// absorbed tuples. +/// - **Password-hashing functions:** returns a standard string containing all +/// the required parameters for password verification. +/// +/// Other kinds of algorithms may return `invalid_operation` instead. +/// +/// For password-stretching functions, the function may return `in_progress`. +/// In that case, the guest should retry with the same parameters until the +/// function completes. +pub unsafe fn symmetric_state_squeeze_tag( + handle: SymmetricState, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_squeeze_tag( + handle as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const SymmetricTag + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Use the current state to produce a key for a target algorithm. +/// +/// For extract-then-expand constructions, this returns the PRK. +/// For session-base authentication encryption, this returns a key that can be +/// used to resume a session without storing a nonce. +/// +/// `invalid_operation` is returned for algorithms not supporting this +/// operation. +pub unsafe fn symmetric_state_squeeze_key( + handle: SymmetricState, + alg_str: &str, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_squeeze_key( + handle as i32, + alg_str.as_ptr() as i32, + alg_str.len() as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const SymmetricKey + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Return the maximum length of an authentication tag for the current +/// algorithm. +/// +/// This allows guests to compute the size required to store a ciphertext along +/// with its authentication tag. +/// +/// The returned length may include the encryption mode's padding requirements +/// in addition to the actual tag. +/// +/// For an encryption operation, the size of the output buffer should be +/// `input_len + symmetric_state_max_tag_len()`. +/// +/// For a decryption operation, the size of the buffer that will store the +/// decrypted data must be `ciphertext_len - symmetric_state_max_tag_len()`. +pub unsafe fn symmetric_state_max_tag_len(handle: SymmetricState) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_max_tag_len( + handle as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Encrypt data with an attached tag. +/// +/// - **Stream cipher:** adds the input to the stream cipher output. `out_len` +/// and `data_len` can be equal, as no authentication tags will be added. +/// - **AEAD:** encrypts `data` into `out`, including the authentication tag to +/// the output. Additional data must have been previously absorbed using +/// `symmetric_state_absorb()`. The `symmetric_state_max_tag_len()` function +/// can be used to retrieve the overhead of adding the tag, as well as padding +/// if necessary. +/// - **SHOE, Xoodyak, Strobe:** encrypts data, squeezes a tag and appends it to +/// the output. +/// +/// If `out` and `data` are the same address, encryption may happen in-place. +/// +/// The function returns the actual size of the ciphertext along with the tag. +/// +/// `invalid_operation` is returned for algorithms not supporting encryption. +pub unsafe fn symmetric_state_encrypt( + handle: SymmetricState, + out: *mut u8, + out_len: Size, + data: *const u8, + data_len: Size, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_encrypt( + handle as i32, + out as i32, + out_len as i32, + data as i32, + data_len as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Encrypt data, with a detached tag. +/// +/// - **Stream cipher:** returns `invalid_operation` since stream ciphers do not +/// include authentication tags. +/// - **AEAD:** encrypts `data` into `out` and returns the tag separately. +/// Additional data must have been previously absorbed using +/// `symmetric_state_absorb()`. The output and input buffers must be of the +/// same length. +/// - **SHOE, Xoodyak, Strobe:** encrypts data and squeezes a tag. +/// +/// If `out` and `data` are the same address, encryption may happen in-place. +/// +/// The function returns the tag. +/// +/// `invalid_operation` is returned for algorithms not supporting encryption. +pub unsafe fn symmetric_state_encrypt_detached( + handle: SymmetricState, + out: *mut u8, + out_len: Size, + data: *const u8, + data_len: Size, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_encrypt_detached( + handle as i32, + out as i32, + out_len as i32, + data as i32, + data_len as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const SymmetricTag + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// - **Stream cipher:** adds the input to the stream cipher output. `out_len` +/// and `data_len` can be equal, as no authentication tags will be added. +/// - **AEAD:** decrypts `data` into `out`. Additional data must have been +/// previously absorbed using `symmetric_state_absorb()`. +/// - **SHOE, Xoodyak, Strobe:** decrypts data, squeezes a tag and verify that +/// it matches the one that was appended to the ciphertext. +/// +/// If `out` and `data` are the same address, decryption may happen in-place. +/// +/// `out_len` must be exactly `data_len` + `max_tag_len` bytes. +/// +/// The function returns the actual size of the decrypted message, which can be +/// smaller than `out_len` for modes that requires padding. +/// +/// `invalid_tag` is returned if the tag didn't verify. +/// +/// `invalid_operation` is returned for algorithms not supporting encryption. +pub unsafe fn symmetric_state_decrypt( + handle: SymmetricState, + out: *mut u8, + out_len: Size, + data: *const u8, + data_len: Size, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_decrypt( + handle as i32, + out as i32, + out_len as i32, + data as i32, + data_len as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// - **Stream cipher:** returns `invalid_operation` since stream ciphers do not +/// include authentication tags. +/// - **AEAD:** decrypts `data` into `out`. Additional data must have been +/// previously absorbed using `symmetric_state_absorb()`. +/// - **SHOE, Xoodyak, Strobe:** decrypts data, squeezes a tag and verify that +/// it matches the expected one. +/// +/// `raw_tag` is the expected tag, as raw bytes. +/// +/// `out` and `data` be must have the same length. +/// If they also share the same address, decryption may happen in-place. +/// +/// The function returns the actual size of the decrypted message. +/// +/// `invalid_tag` is returned if the tag verification failed. +/// +/// `invalid_operation` is returned for algorithms not supporting encryption. +pub unsafe fn symmetric_state_decrypt_detached( + handle: SymmetricState, + out: *mut u8, + out_len: Size, + data: *const u8, + data_len: Size, + raw_tag: *const u8, + raw_tag_len: Size, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_decrypt_detached( + handle as i32, + out as i32, + out_len as i32, + data as i32, + data_len as i32, + raw_tag as i32, + raw_tag_len as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Make it impossible to recover the previous state. +/// +/// This operation is supported by some systems keeping a rolling state over an +/// entire session, for forward security. +/// +/// `invalid_operation` is returned for algorithms not supporting ratcheting. +pub unsafe fn symmetric_state_ratchet(handle: SymmetricState) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_ratchet(handle as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Return the length of an authentication tag. +/// +/// This function can be used by a guest to allocate the correct buffer size to +/// copy a computed authentication tag. +pub unsafe fn symmetric_tag_len(symmetric_tag: SymmetricTag) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_tag_len( + symmetric_tag as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Copy an authentication tag into a guest-allocated buffer. +/// +/// The handle automatically becomes invalid after this operation. Manually +/// closing it is not required. +/// +/// Example usage: +/// +/// ```rust +/// let mut raw_tag = [0u8; 16]; +/// ctx.symmetric_tag_pull(raw_tag_handle, &mut raw_tag)?; +/// ``` +/// +/// The function returns `overflow` if the supplied buffer is too small to copy +/// the tag. +/// +/// Otherwise, it returns the number of bytes that have been copied. +pub unsafe fn symmetric_tag_pull( + symmetric_tag: SymmetricTag, + buf: *mut u8, + buf_len: Size, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_symmetric::symmetric_tag_pull( + symmetric_tag as i32, + buf as i32, + buf_len as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Verify that a computed authentication tag matches the expected value, in +/// constant-time. +/// +/// The expected tag must be provided as a raw byte string. +/// +/// The function returns `invalid_tag` if the tags don't match. +/// +/// Example usage: +/// +/// ```rust +/// let key_handle = ctx.symmetric_key_import("HMAC/SHA-256", b"key")?; +/// let state_handle = ctx.symmetric_state_open("HMAC/SHA-256", Some(key_handle), None)?; +/// ctx.symmetric_state_absorb(state_handle, b"data")?; +/// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; +/// ctx.symmetric_tag_verify(computed_tag_handle, expected_raw_tag)?; +/// ``` +pub unsafe fn symmetric_tag_verify( + symmetric_tag: SymmetricTag, + expected_raw_tag_ptr: *const u8, + expected_raw_tag_len: Size, +) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_symmetric::symmetric_tag_verify( + symmetric_tag as i32, + expected_raw_tag_ptr as i32, + expected_raw_tag_len as i32, + ); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Explicitly destroy an unused authentication tag. +/// +/// This is usually not necessary, as `symmetric_tag_pull()` automatically +/// closes a tag after it has been copied. +/// +/// Objects are reference counted. It is safe to close an object immediately +/// after the last function needing it is called. +pub unsafe fn symmetric_tag_close(symmetric_tag: SymmetricTag) -> Result<(), CryptoErrno> { + let ret = wasi_ephemeral_crypto_symmetric::symmetric_tag_close(symmetric_tag as i32); + match ret { + 0 => Ok(()), + _ => Err(CryptoErrno(ret as u16)), + } +} + +pub mod wasi_ephemeral_crypto_symmetric { + #[link(wasm_import_module = "wasi_ephemeral_crypto_symmetric")] + extern "C" { + /// Generate a new symmetric key for a given algorithm. + /// + /// `options` can be `None` to use the default parameters, or an + /// algoritm-specific set of parameters to override. + /// + /// This function may return `unsupported_feature` if key generation is + /// not supported by the host for the chosen algorithm, or + /// `unsupported_algorithm` if the algorithm is not supported by the + /// host. + pub fn symmetric_key_generate(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; + /// Create a symmetric key from raw material. + /// + /// The algorithm is internally stored along with the key, and trying to + /// use the key with an operation expecting a different algorithm will + /// return `invalid_key`. + /// + /// The function may also return `unsupported_algorithm` if the + /// algorithm is not supported by the host. + pub fn symmetric_key_import(arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32) -> i32; + /// Export a symmetric key as raw material. + /// + /// This is mainly useful to export a managed key. + /// + /// May return `prohibited_operation` if this operation is denied. + pub fn symmetric_key_export(arg0: i32, arg1: i32) -> i32; + /// Destroy a symmetric key. + /// + /// Objects are reference counted. It is safe to close an object + /// immediately after the last function needing it is called. + pub fn symmetric_key_close(arg0: i32) -> i32; + /// __(optional)__ + /// Generate a new managed symmetric key. + /// + /// The key is generated and stored by the secrets management + /// facilities. + /// + /// It may be used through its identifier, but the host may not allow it + /// to be exported. + /// + /// The function returns the `unsupported_feature` error code if secrets + /// management facilities are not supported by the host, + /// or `unsupported_algorithm` if a key cannot be created for the chosen + /// algorithm. + /// + /// The function may also return `unsupported_algorithm` if the + /// algorithm is not supported by the host. + /// + /// This is also an optional import, meaning that the function may not + /// even exist. + pub fn symmetric_key_generate_managed( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + ) -> i32; + /// __(optional)__ + /// Store a symmetric key into the secrets manager. + /// + /// On success, the function stores the key identifier into + /// `$symmetric_key_id`, into which up to + /// `$symmetric_key_id_max_len` can be written. + /// + /// The function returns `overflow` if the supplied buffer is too small. + pub fn symmetric_key_store_managed(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; + /// __(optional)__ + /// Replace a managed symmetric key. + /// + /// This function crates a new version of a managed symmetric key, by + /// replacing `$kp_old` with `$kp_new`. + /// + /// It does several things: + /// + /// - The key identifier for `$symmetric_key_new` is set to the one of + /// `$symmetric_key_old`. + /// - A new, unique version identifier is assigned to `$kp_new`. This + /// version will be equivalent to using `$version_latest` until the + /// key is replaced. + /// - The `$symmetric_key_old` handle is closed. + /// + /// Both keys must share the same algorithm and have compatible + /// parameters. If this is not the case, `incompatible_keys` is + /// returned. + /// + /// The function may also return the `unsupported_feature` error code if + /// secrets management facilities are not supported by the host, + /// or if keys cannot be rotated. + /// + /// Finally, `prohibited_operation` can be returned if + /// `$symmetric_key_new` wasn't created by the secrets manager, and the + /// secrets manager prohibits imported keys. + /// + /// If the operation succeeded, the new version is returned. + /// + /// This is an optional import, meaning that the function may not even + /// exist. + pub fn symmetric_key_replace_managed(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; + /// __(optional)__ + /// Return the key identifier and version of a managed symmetric key. + /// + /// If the key is not managed, `unsupported_feature` is returned + /// instead. + /// + /// This is an optional import, meaning that the function may not even + /// exist. + pub fn symmetric_key_id(arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32) -> i32; + /// __(optional)__ + /// Return a managed symmetric key from a key identifier. + /// + /// `kp_version` can be set to `version_latest` to retrieve the most + /// recent version of a symmetric key. + /// + /// If no key matching the provided information is found, `not_found` is + /// returned instead. + /// + /// This is an optional import, meaning that the function may not even + /// exist. + pub fn symmetric_key_from_id(arg0: i32, arg1: i32, arg2: i32, arg3: i64, arg4: i32) -> i32; + /// Create a new state to aborb and produce data using symmetric + /// operations. + /// + /// The state remains valid after every operation in order to support + /// incremental updates. + /// + /// The function has two optional parameters: a key and an options set. + /// + /// It will fail with a `key_not_supported` error code if a key was + /// provided but the chosen algorithm doesn't natively support keying. + /// + /// On the other hand, if a key is required, but was not provided, a + /// `key_required` error will be thrown. + /// + /// Some algorithms may require additional parameters. They have to be + /// supplied as an options set: + /// + /// ```rust + /// let options_handle = ctx.options_open()?; + /// ctx.options_set("context", b"My application")?; + /// ctx.options_set_u64("fanout", 16)?; + /// let state_handle = ctx.symmetric_state_open("BLAKE2b-512", None, Some(options_handle))?; + /// ``` + /// + /// If some parameters are mandatory but were not set, the + /// `parameters_missing` error code will be returned. + /// + /// A notable exception is the `nonce` parameter, that is common to most + /// AEAD constructions. + /// + /// If a nonce is required but was not supplied: + /// + /// - If it is safe to do so, the host will automatically generate a + /// nonce. This is true for nonces that are large enough to be + /// randomly generated, or if the host is able to maintain a global + /// counter. + /// - If not, the function will fail and return the dedicated + /// `nonce_required` error code. + /// + /// A nonce that was automatically generated can be retrieved after the + /// function returns with `symmetric_state_get(state_handle, "nonce")`. + /// + /// **Sample usage patterns:** + /// + /// - **Hashing** + /// + /// ```rust + /// let mut out = [0u8; 64]; + /// let state_handle = ctx.symmetric_state_open("SHAKE-128", None, None)?; + /// ctx.symmetric_state_absorb(state_handle, b"data")?; + /// ctx.symmetric_state_absorb(state_handle, b"more_data")?; + /// ctx.symmetric_state_squeeze(state_handle, &mut out)?; + /// ``` + /// + /// - **MAC** + /// + /// ```rust + /// let mut raw_tag = [0u8; 64]; + /// let key_handle = ctx.symmetric_key_import("HMAC/SHA-512", b"key")?; + /// let state_handle = ctx.symmetric_state_open("HMAC/SHA-512", Some(key_handle), None)?; + /// ctx.symmetric_state_absorb(state_handle, b"data")?; + /// ctx.symmetric_state_absorb(state_handle, b"more_data")?; + /// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; + /// ctx.symmetric_tag_pull(computed_tag_handle, &mut raw_tag)?; + /// ``` + /// + /// Verification: + /// + /// ```rust + /// let state_handle = ctx.symmetric_state_open("HMAC/SHA-512", Some(key_handle), None)?; + /// ctx.symmetric_state_absorb(state_handle, b"data")?; + /// ctx.symmetric_state_absorb(state_handle, b"more_data")?; + /// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; + /// ctx.symmetric_tag_verify(computed_tag_handle, expected_raw_tag)?; + /// ``` + /// + /// - **Tuple hashing** + /// + /// ```rust + /// let mut out = [0u8; 64]; + /// let state_handle = ctx.symmetric_state_open("TupleHashXOF256", None, None)?; + /// ctx.symmetric_state_absorb(state_handle, b"value 1")?; + /// ctx.symmetric_state_absorb(state_handle, b"value 2")?; + /// ctx.symmetric_state_absorb(state_handle, b"value 3")?; + /// ctx.symmetric_state_squeeze(state_handle, &mut out)?; + /// ``` + /// Unlike MACs and regular hash functions, inputs are domain separated + /// instead of being concatenated. + /// + /// - **Key derivation using extract-and-expand** + /// + /// Extract: + /// + /// ```rust + /// let mut prk = vec![0u8; 64]; + /// let key_handle = ctx.symmetric_key_import("HKDF-EXTRACT/SHA-512", b"key")?; + /// let state_handle = ctx.symmetric_state_open("HKDF-EXTRACT/SHA-512", Some(key_handle), None)?; + /// ctx.symmetric_state_absorb(state_handle, b"salt")?; + /// let prk_handle = ctx.symmetric_state_squeeze_key(state_handle, "HKDF-EXPAND/SHA-512")?; + /// ``` + /// + /// Expand: + /// + /// ```rust + /// let mut subkey = vec![0u8; 32]; + /// let state_handle = ctx.symmetric_state_open("HKDF-EXPAND/SHA-512", Some(prk_handle), None)?; + /// ctx.symmetric_state_absorb(state_handle, b"info")?; + /// ctx.symmetric_state_squeeze(state_handle, &mut subkey)?; + /// ``` + /// + /// - **Key derivation using a XOF** + /// + /// ```rust + /// let mut subkey1 = vec![0u8; 32]; + /// let mut subkey2 = vec![0u8; 32]; + /// let key_handle = ctx.symmetric_key_import("BLAKE3", b"key")?; + /// let state_handle = ctx.symmetric_state_open("BLAKE3", Some(key_handle), None)?; + /// ctx.symmetric_absorb(state_handle, b"context")?; + /// ctx.squeeze(state_handle, &mut subkey1)?; + /// ctx.squeeze(state_handle, &mut subkey2)?; + /// ``` + /// + /// - **Password hashing** + /// + /// ```rust + /// let mut memory = vec![0u8; 1_000_000_000]; + /// let options_handle = ctx.symmetric_options_open()?; + /// ctx.symmetric_options_set_guest_buffer(options_handle, "memory", &mut memory)?; + /// ctx.symmetric_options_set_u64(options_handle, "opslimit", 5)?; + /// ctx.symmetric_options_set_u64(options_handle, "parallelism", 8)?; + /// + /// let state_handle = ctx.symmetric_state_open("ARGON2-ID-13", None, Some(options))?; + /// ctx.symmtric_state_absorb(state_handle, b"password")?; + /// + /// let pw_str_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; + /// let mut pw_str = vec![0u8; ctx.symmetric_tag_len(pw_str_handle)?]; + /// ctx.symmetric_tag_pull(pw_str_handle, &mut pw_str)?; + /// ``` + /// + /// - **AEAD encryption with an explicit nonce** + /// + /// ```rust + /// let key_handle = ctx.symmetric_key_generate("AES-256-GCM", None)?; + /// let message = b"test"; + /// + /// let options_handle = ctx.symmetric_options_open()?; + /// ctx.symmetric_options_set(options_handle, "nonce", nonce)?; + /// + /// let state_handle = + /// ctx.symmetric_state_open("AES-256-GCM", Some(key_handle), Some(options_handle))?; + /// let mut ciphertext = vec![0u8; message.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; + /// ctx.symmetric_state_absorb(state_handle, "additional data")?; + /// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, message)?; + /// ``` + /// + /// - **AEAD encryption with automatic nonce generation** + /// + /// ```rust + /// let key_handle = ctx.symmetric_key_generate("AES-256-GCM-SIV", None)?; + /// let message = b"test"; + /// let mut nonce = [0u8; 24]; + /// + /// let state_handle = ctx.symmetric_state_open("AES-256-GCM-SIV", Some(key_handle), None)?; + /// + /// let nonce = ctx.symmetric_state_options_get(state_handle, "nonce")?; + /// + /// let mut ciphertext = vec![0u8; message.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; + /// ctx.symmetric_state_absorb(state_handle, "additional data")?; + /// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, message)?; + /// ``` + /// + /// - **Session authenticated modes** + /// + /// ```rust + /// let mut out = [0u8; 16]; + /// let mut out2 = [0u8; 16]; + /// let mut ciphertext = [0u8; 20]; + /// let key_handle = ctx.symmetric_key_generate("Xoodyak-128", None)?; + /// let state_handle = ctx.symmetric_state_open("Xoodyak-128", Some(key_handle), None)?; + /// ctx.symmetric_state_absorb(state_handle, b"data")?; + /// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, b"abcd")?; + /// ctx.symmetric_state_absorb(state_handle, b"more data")?; + /// ctx.symmetric_state_squeeze(state_handle, &mut out)?; + /// ctx.symmetric_state_squeeze(state_handle, &mut out2)?; + /// ctx.symmetric_state_ratchet(state_handle)?; + /// ctx.symmetric_state_absorb(state_handle, b"more data")?; + /// let next_key_handle = ctx.symmetric_state_squeeze_key(state_handle, "Xoodyak-128")?; + /// // ... + /// ``` + pub fn symmetric_state_open(arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32) -> i32; + /// Retrieve a parameter from the current state. + /// + /// In particular, `symmetric_state_options_get("nonce")` can be used to + /// get a nonce that as automatically generated. + /// + /// The function may return `options_not_set` if an option was not set, + /// which is different from an empty value. + /// + /// It may also return `unsupported_option` if the option doesn't exist + /// for the chosen algorithm. + pub fn symmetric_state_options_get( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, + ) -> i32; + /// Retrieve an integer parameter from the current state. + /// + /// The function may return `options_not_set` if an option was not set. + /// + /// It may also return `unsupported_option` if the option doesn't exist + /// for the chosen algorithm. + pub fn symmetric_state_options_get_u64(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; + /// Clone a symmetric state. + /// + /// The function clones the internal state, assigns a new handle to it + /// and returns the new handle. + pub fn symmetric_state_clone(arg0: i32, arg1: i32) -> i32; + /// Destroy a symmetric state. + /// + /// Objects are reference counted. It is safe to close an object + /// immediately after the last function needing it is called. + pub fn symmetric_state_close(arg0: i32) -> i32; + /// Absorb data into the state. + /// + /// - **Hash functions:** adds data to be hashed. + /// - **MAC functions:** adds data to be authenticated. + /// - **Tuplehash-like constructions:** adds a new tuple to the state. + /// - **Key derivation functions:** adds to the IKM or to the subkey + /// information. + /// - **AEAD constructions:** adds additional data to be authenticated. + /// - **Stateful hash objects, permutation-based constructions:** + /// absorbs. + /// + /// If the chosen algorithm doesn't accept input data, the + /// `invalid_operation` error code is returned. + /// + /// If too much data has been fed for the algorithm, `overflow` may be + /// thrown. + pub fn symmetric_state_absorb(arg0: i32, arg1: i32, arg2: i32) -> i32; + /// Squeeze bytes from the state. + /// + /// - **Hash functions:** this tries to output an `out_len` bytes digest + /// from the absorbed data. The hash function output will be truncated + /// if necessary. If the requested size is too large, the + /// `invalid_len` error code is returned. + /// - **Key derivation functions:** : outputs an arbitrary-long derived + /// key. + /// - **RNGs, DRBGs, stream ciphers:**: outputs arbitrary-long data. + /// - **Stateful hash objects, permutation-based constructions:** + /// squeeze. + /// + /// Other kinds of algorithms may return `invalid_operation` instead. + /// + /// For password-stretching functions, the function may return + /// `in_progress`. In that case, the guest should retry with the + /// same parameters until the function completes. + pub fn symmetric_state_squeeze(arg0: i32, arg1: i32, arg2: i32) -> i32; + /// Compute and return a tag for all the data injected into the state so + /// far. + /// + /// - **MAC functions**: returns a tag authenticating the absorbed data. + /// - **Tuplehash-like constructions:** returns a tag authenticating all + /// the absorbed tuples. + /// - **Password-hashing functions:** returns a standard string + /// containing all the required parameters for password verification. + /// + /// Other kinds of algorithms may return `invalid_operation` instead. + /// + /// For password-stretching functions, the function may return + /// `in_progress`. In that case, the guest should retry with the + /// same parameters until the function completes. + pub fn symmetric_state_squeeze_tag(arg0: i32, arg1: i32) -> i32; + /// Use the current state to produce a key for a target algorithm. + /// + /// For extract-then-expand constructions, this returns the PRK. + /// For session-base authentication encryption, this returns a key that + /// can be used to resume a session without storing a nonce. + /// + /// `invalid_operation` is returned for algorithms not supporting this + /// operation. + pub fn symmetric_state_squeeze_key(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; + /// Return the maximum length of an authentication tag for the current + /// algorithm. + /// + /// This allows guests to compute the size required to store a + /// ciphertext along with its authentication tag. + /// + /// The returned length may include the encryption mode's padding + /// requirements in addition to the actual tag. + /// + /// For an encryption operation, the size of the output buffer should be + /// `input_len + symmetric_state_max_tag_len()`. + /// + /// For a decryption operation, the size of the buffer that will store + /// the decrypted data must be `ciphertext_len - + /// symmetric_state_max_tag_len()`. + pub fn symmetric_state_max_tag_len(arg0: i32, arg1: i32) -> i32; + /// Encrypt data with an attached tag. + /// + /// - **Stream cipher:** adds the input to the stream cipher output. + /// `out_len` and `data_len` can be equal, as no authentication tags + /// will be added. + /// - **AEAD:** encrypts `data` into `out`, including the authentication + /// tag to the output. Additional data must have been previously + /// absorbed using `symmetric_state_absorb()`. The + /// `symmetric_state_max_tag_len()` function can be used to retrieve + /// the overhead of adding the tag, as well as padding if necessary. + /// - **SHOE, Xoodyak, Strobe:** encrypts data, squeezes a tag and + /// appends it to the output. + /// + /// If `out` and `data` are the same address, encryption may happen + /// in-place. + /// + /// The function returns the actual size of the ciphertext along with + /// the tag. + /// + /// `invalid_operation` is returned for algorithms not supporting + /// encryption. + pub fn symmetric_state_encrypt( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, + ) -> i32; + /// Encrypt data, with a detached tag. + /// + /// - **Stream cipher:** returns `invalid_operation` since stream + /// ciphers do not include authentication tags. + /// - **AEAD:** encrypts `data` into `out` and returns the tag + /// separately. Additional data must have been previously absorbed + /// using `symmetric_state_absorb()`. The output and input buffers + /// must be of the same length. + /// - **SHOE, Xoodyak, Strobe:** encrypts data and squeezes a tag. + /// + /// If `out` and `data` are the same address, encryption may happen + /// in-place. + /// + /// The function returns the tag. + /// + /// `invalid_operation` is returned for algorithms not supporting + /// encryption. + pub fn symmetric_state_encrypt_detached( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, + ) -> i32; + /// - **Stream cipher:** adds the input to the stream cipher output. + /// `out_len` and `data_len` can be equal, as no authentication tags + /// will be added. + /// - **AEAD:** decrypts `data` into `out`. Additional data must have + /// been previously absorbed using `symmetric_state_absorb()`. + /// - **SHOE, Xoodyak, Strobe:** decrypts data, squeezes a tag and + /// verify that it matches the one that was appended to the + /// ciphertext. + /// + /// If `out` and `data` are the same address, decryption may happen + /// in-place. + /// + /// `out_len` must be exactly `data_len` + `max_tag_len` bytes. + /// + /// The function returns the actual size of the decrypted message, which + /// can be smaller than `out_len` for modes that requires padding. + /// + /// `invalid_tag` is returned if the tag didn't verify. + /// + /// `invalid_operation` is returned for algorithms not supporting + /// encryption. + pub fn symmetric_state_decrypt( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, + ) -> i32; + /// - **Stream cipher:** returns `invalid_operation` since stream + /// ciphers do not include authentication tags. + /// - **AEAD:** decrypts `data` into `out`. Additional data must have + /// been previously absorbed using `symmetric_state_absorb()`. + /// - **SHOE, Xoodyak, Strobe:** decrypts data, squeezes a tag and + /// verify that it matches the expected one. + /// + /// `raw_tag` is the expected tag, as raw bytes. + /// + /// `out` and `data` be must have the same length. + /// If they also share the same address, decryption may happen in-place. + /// + /// The function returns the actual size of the decrypted message. + /// + /// `invalid_tag` is returned if the tag verification failed. + /// + /// `invalid_operation` is returned for algorithms not supporting + /// encryption. + pub fn symmetric_state_decrypt_detached( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, + arg6: i32, + arg7: i32, + ) -> i32; + /// Make it impossible to recover the previous state. + /// + /// This operation is supported by some systems keeping a rolling state + /// over an entire session, for forward security. + /// + /// `invalid_operation` is returned for algorithms not supporting + /// ratcheting. + pub fn symmetric_state_ratchet(arg0: i32) -> i32; + /// Return the length of an authentication tag. + /// + /// This function can be used by a guest to allocate the correct buffer + /// size to copy a computed authentication tag. + pub fn symmetric_tag_len(arg0: i32, arg1: i32) -> i32; + /// Copy an authentication tag into a guest-allocated buffer. + /// + /// The handle automatically becomes invalid after this operation. + /// Manually closing it is not required. + /// + /// Example usage: + /// + /// ```rust + /// let mut raw_tag = [0u8; 16]; + /// ctx.symmetric_tag_pull(raw_tag_handle, &mut raw_tag)?; + /// ``` + /// + /// The function returns `overflow` if the supplied buffer is too small + /// to copy the tag. + /// + /// Otherwise, it returns the number of bytes that have been copied. + pub fn symmetric_tag_pull(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; + /// Verify that a computed authentication tag matches the expected + /// value, in constant-time. + /// + /// The expected tag must be provided as a raw byte string. + /// + /// The function returns `invalid_tag` if the tags don't match. + /// + /// Example usage: + /// + /// ```rust + /// let key_handle = ctx.symmetric_key_import("HMAC/SHA-256", b"key")?; + /// let state_handle = ctx.symmetric_state_open("HMAC/SHA-256", Some(key_handle), None)?; + /// ctx.symmetric_state_absorb(state_handle, b"data")?; + /// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; + /// ctx.symmetric_tag_verify(computed_tag_handle, expected_raw_tag)?; + /// ``` + pub fn symmetric_tag_verify(arg0: i32, arg1: i32, arg2: i32) -> i32; + /// Explicitly destroy an unused authentication tag. + /// + /// This is usually not necessary, as `symmetric_tag_pull()` + /// automatically closes a tag after it has been copied. + /// + /// Objects are reference counted. It is safe to close an object + /// immediately after the last function needing it is called. + pub fn symmetric_tag_close(arg0: i32) -> i32; + } +} +/// Perform a simple Diffie-Hellman key exchange. +/// +/// Both keys must be of the same type, or else the +/// `$crypto_errno.incompatible_keys` error is returned. The algorithm also has +/// to support this kind of key exchange. If this is not the case, the +/// `$crypto_errno.invalid_operation` error is returned. +/// +/// Otherwide, a raw shared key is returned, and can be imported as a symmetric +/// key. ``` +pub unsafe fn kx_dh(pk: Publickey, sk: Secretkey) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_kx::kx_dh(pk as i32, sk as i32, rp0.as_mut_ptr() as i32); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const ArrayOutput + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Create a shared secret and encrypt it for the given public key. +/// +/// This operation is only compatible with specific algorithms. +/// If a selected algorithm doesn't support it, +/// `$crypto_errno.invalid_operation` is returned. +/// +/// On success, both the shared secret and its encrypted version are returned. +pub unsafe fn kx_encapsulate(pk: Publickey) -> Result<(ArrayOutput, ArrayOutput), CryptoErrno> { + let mut rp0 = MaybeUninit::::uninit(); + let mut rp1 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_kx::kx_encapsulate( + pk as i32, + rp0.as_mut_ptr() as i32, + rp1.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(( + core::ptr::read(rp0.as_mut_ptr() as i32 as *const ArrayOutput), + core::ptr::read(rp1.as_mut_ptr() as i32 as *const ArrayOutput), + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +/// Decapsulate an encapsulated secret crated with `kx_encapsulate` +/// +/// Return the secret, or `$crypto_errno.verification_failed` on error. +pub unsafe fn kx_decapsulate( + sk: Secretkey, + encapsulated_secret: *const u8, + encapsulated_secret_len: Size, +) -> Result { + let mut rp0 = MaybeUninit::::uninit(); + let ret = wasi_ephemeral_crypto_kx::kx_decapsulate( + sk as i32, + encapsulated_secret as i32, + encapsulated_secret_len as i32, + rp0.as_mut_ptr() as i32, + ); + match ret { + 0 => Ok(core::ptr::read( + rp0.as_mut_ptr() as i32 as *const ArrayOutput + )), + _ => Err(CryptoErrno(ret as u16)), + } +} + +pub mod wasi_ephemeral_crypto_kx { + #[link(wasm_import_module = "wasi_ephemeral_crypto_kx")] + extern "C" { + /// Perform a simple Diffie-Hellman key exchange. + /// + /// Both keys must be of the same type, or else the + /// `$crypto_errno.incompatible_keys` error is returned. + /// The algorithm also has to support this kind of key exchange. If this + /// is not the case, the `$crypto_errno.invalid_operation` error is + /// returned. + /// + /// Otherwide, a raw shared key is returned, and can be imported as a + /// symmetric key. ``` + pub fn kx_dh(arg0: i32, arg1: i32, arg2: i32) -> i32; + /// Create a shared secret and encrypt it for the given public key. + /// + /// This operation is only compatible with specific algorithms. + /// If a selected algorithm doesn't support it, + /// `$crypto_errno.invalid_operation` is returned. + /// + /// On success, both the shared secret and its encrypted version are + /// returned. + pub fn kx_encapsulate(arg0: i32, arg1: i32, arg2: i32) -> i32; + /// Decapsulate an encapsulated secret crated with `kx_encapsulate` + /// + /// Return the secret, or `$crypto_errno.verification_failed` on error. + pub fn kx_decapsulate(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; + } +} +pub const VERSION_UNSPECIFIED: Version = 18374686479671623680; +pub const VERSION_LATEST: Version = 18374686479671623681; +pub const VERSION_ALL: Version = 18374686479671623682; diff --git a/test/common.js b/test/common.js index 5cf58c5..d18e2fe 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 = false; +const hasOpenSSL3 = true; const mustCallChecks = []; function runCallChecks() { diff --git a/test/crypto/test-crypto-hkdf.js b/test/crypto/test-crypto-hkdf.js index f9ee5d2..37ed84f 100644 --- a/test/crypto/test-crypto-hkdf.js +++ b/test/crypto/test-crypto-hkdf.js @@ -10,7 +10,7 @@ if (!common.hasCrypto) import { kMaxLength } from 'buffer'; import assert from 'assert'; import { - createSecretKey, + /*createSecretKey,*/ hkdf, hkdfSync, getHashes @@ -157,7 +157,8 @@ algorithms.forEach(([ hash, secret, salt, info, length ]) => { })); } - { + // unimplemented now + /*{ const key_secret = createSecretKey(Buffer.from(secret)); const buf_salt = Buffer.from(salt); const buf_info = Buffer.from(info); @@ -167,7 +168,7 @@ algorithms.forEach(([ hash, secret, salt, info, length ]) => { common.mustSucceed((asyncResult) => { assert.deepStrictEqual(syncResult, asyncResult); })); - } + }*/ { const ta_secret = new Uint8Array(Buffer.from(secret)); From 4889ad3ad59faf74aa8677cd136a9509f7940da8 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Mon, 26 Dec 2022 22:45:00 +0800 Subject: [PATCH 11/25] impl: hash-internal --- modules/internal/crypto/hash.js | 192 +++++++++++++++++ modules/internal/streams/lazy_transform.js | 58 ++++++ src/internal_module/crypto/lib.rs | 227 +++++++++++++++------ tests/test-crypto.rs | 1 - 4 files changed, 416 insertions(+), 62 deletions(-) create mode 100644 modules/internal/crypto/hash.js create mode 100644 modules/internal/streams/lazy_transform.js diff --git a/modules/internal/crypto/hash.js b/modules/internal/crypto/hash.js new file mode 100644 index 0000000..a01fa7f --- /dev/null +++ b/modules/internal/crypto/hash.js @@ -0,0 +1,192 @@ +'use strict'; + +const { + Hash: _Hash, + HashJob, + Hmac: _Hmac, + kCryptoJobAsync, +} = internalBinding('crypto'); + +import { + getArrayBufferOrView, + getDefaultEncoding, + getStringOption, + jobPromise, + normalizeAlgorithm, + normalizeHashName, + validateMaxBufferLength, + kHandle, +} 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'); + +function Hash(algorithm, options) { + if (!(this instanceof Hash)) + return new Hash(algorithm, options); + if (!(algorithm instanceof _Hash)) + validateString(algorithm, 'algorithm'); + 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[kHandle].update(chunk, encoding); + callback(); +}; + +Hash.prototype._flush = function _flush(callback) { + this.push(this[kHandle].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); + } + + if (!this[kHandle].update(data, encoding)) + 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 = this[kHandle].digest(`${outputEncoding}`); + state[kFinalized] = true; + return ret; +}; + +function Hmac(hmac, key, options) { + if (!(this instanceof Hmac)) + return new Hmac(hmac, key, options); + validateString(hmac, 'hmac'); + const encoding = getStringOption(options, 'encoding'); + key = prepareSecretKey(key, encoding); + this[kHandle] = new _Hmac(); + this[kHandle].init(hmac, 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 = this[kHandle].digest(`${outputEncoding}`); + state[kFinalized] = true; + return ret; +}; + +Hmac.prototype._flush = Hash.prototype._flush; +Hmac.prototype._transform = Hash.prototype._transform; + +// Implementation for WebCrypto subtle.digest() + +async function asyncDigest(algorithm, data) { + algorithm = normalizeAlgorithm(algorithm); + data = getArrayBufferOrView(data, 'data'); + validateMaxBufferLength(data, 'data'); + + if (algorithm.length !== undefined) + validateUint32(algorithm.length, 'algorithm.length'); + + switch (algorithm.name) { + case 'SHA-1': + // Fall through + case 'SHA-256': + // Fall through + case 'SHA-384': + // Fall through + case 'SHA-512': + return jobPromise(new HashJob( + kCryptoJobAsync, + normalizeHashName(algorithm.name), + data, + algorithm.length)); + } + + throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); +} + +export { + Hash, + Hmac, + asyncDigest, +}; diff --git a/modules/internal/streams/lazy_transform.js b/modules/internal/streams/lazy_transform.js new file mode 100644 index 0000000..45d14d5 --- /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 stream from '../../stream'; + +import { + getDefaultEncoding +} from '../crypto/util'; + +export function LazyTransform(options) { + this._options = options; +} +Object.setPrototypeOf(LazyTransform.prototype, stream.Transform.prototype); +Object.setPrototypeOf(LazyTransform, stream.Transform); + +function makeGetter(name) { + return function() { + stream.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/src/internal_module/crypto/lib.rs b/src/internal_module/crypto/lib.rs index 1bf0af1..f326219 100644 --- a/src/internal_module/crypto/lib.rs +++ b/src/internal_module/crypto/lib.rs @@ -5,6 +5,120 @@ const NONE_OPTS: raw::OptOptions = raw::OptOptions { u: raw::OptOptionsUnion { none: () }, }; +const NONE_KEY: raw::OptSymmetricKey = raw::OptSymmetricKey { + tag: raw::OPT_SYMMETRIC_KEY_U_NONE.raw(), + u: raw::OptSymmetricKeyUnion { none: () }, +}; + +pub struct Hmac { + handle: raw::SymmetricState, +} + +impl Hmac { + pub fn create(alg: &str, key: T) -> Result + where + T: AsRef<[u8]>, + { + let alg = match alg { + "sha256" | "SHA256" | "HMAC/SHA-256" => "HMAC/SHA-256", + "sha512" | "SHA512" | "HMAC/SHA-512" => "HMAC/SHA-512", + _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), + }; + let handle = { + let key = key.as_ref(); + unsafe { + let key = raw::symmetric_key_import(alg, key.as_ptr(), key.len())?; + let opt = raw::OptSymmetricKey { + tag: raw::OPT_SYMMETRIC_KEY_U_SOME.raw(), + u: raw::OptSymmetricKeyUnion { some: key }, + }; + let state = raw::symmetric_state_open(alg, opt, NONE_OPTS)?; + raw::symmetric_key_close(key)?; + state + } + }; + Ok(Self { handle }) + } + + pub fn update(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { + let data = data.as_ref(); + unsafe { raw::symmetric_state_absorb(self.handle, data.as_ptr(), data.len()) } + } + + pub fn digest(&mut self) -> Result, raw::CryptoErrno> { + unsafe { + let tag = raw::symmetric_state_squeeze_tag(self.handle)?; + let len = raw::symmetric_tag_len(tag)?; + let mut out = vec![0; len]; + raw::symmetric_tag_pull(tag, out.as_mut_ptr(), out.len())?; + raw::symmetric_tag_close(tag)?; + Ok(out) + } + } + + pub fn digest_into(&mut self, mut buf: impl AsMut<[u8]>) -> Result<(), raw::CryptoErrno> { + let buf = buf.as_mut(); + unsafe { + let tag = raw::symmetric_state_squeeze_tag(self.handle)?; + raw::symmetric_tag_pull(tag, buf.as_mut_ptr(), buf.len())?; + raw::symmetric_tag_close(tag)?; + } + Ok(()) + } +} + +impl Drop for Hmac { + fn drop(&mut self) { + unsafe { + raw::symmetric_state_close(self.handle).unwrap(); + } + } +} + +pub struct Hash { + handle: raw::SymmetricState, + hash_len: usize +} + +impl Hash { + pub fn create(alg: &str) -> Result { + let (alg, hash_len) = match alg { + "sha256" | "SHA256" | "SHA-256" => ("SHA-256", 32), + "sha512" | "SHA512" | "SHA-512" => ("SHA-512", 64), + _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), + }; + let handle = { unsafe { raw::symmetric_state_open(alg, NONE_KEY, NONE_OPTS)? } }; + Ok(Self { handle, hash_len }) + } + + pub fn update(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { + let data = data.as_ref(); + unsafe { raw::symmetric_state_absorb(self.handle, data.as_ptr(), data.len()) } + } + + pub fn digest(&mut self) -> Result, raw::CryptoErrno> { + let mut out = vec![0; self.hash_len]; + self.digest_into(&mut out)?; + Ok(out) + } + + pub fn digest_into(&mut self, mut buf: impl AsMut<[u8]>) -> Result<(), raw::CryptoErrno> { + let buf = buf.as_mut(); + unsafe { + raw::symmetric_state_squeeze(self.handle, buf.as_mut_ptr(), buf.len())?; + } + Ok(()) + } +} + +impl Drop for Hash { + fn drop(&mut self) { + unsafe { + raw::symmetric_state_close(self.handle).unwrap(); + } + } +} + /// Behaviour like /// /// ```js @@ -17,35 +131,26 @@ pub fn hmac( key: impl AsRef<[u8]>, infos: &[impl AsRef<[u8]>], ) -> Result, raw::CryptoErrno> { - let key = key.as_ref(); - let hmac_alg = match alg { - "sha256" | "SHA256" | "HMAC/SHA-256" => "HMAC/SHA-256", - "sha512" | "SHA512" | "HMAC/SHA-512" => "HMAC/SHA-512", - _ => unreachable!(), - }; - unsafe { - let hmac_key = raw::symmetric_key_import(hmac_alg, key.as_ptr(), key.len())?; - let hmac_handle = raw::symmetric_state_open( - hmac_alg, - raw::OptSymmetricKey { - tag: raw::OPT_SYMMETRIC_KEY_U_SOME.raw(), - u: raw::OptSymmetricKeyUnion { some: hmac_key }, - }, - NONE_OPTS, - )?; - for info in infos { - let info = info.as_ref(); - raw::symmetric_state_absorb(hmac_handle, info.as_ptr(), info.len())?; - } - let tag = raw::symmetric_state_squeeze_tag(hmac_handle)?; - raw::symmetric_state_close(hmac_handle)?; - raw::symmetric_key_close(hmac_key)?; - let len = raw::symmetric_tag_len(tag)?; - let mut out = vec![0; len]; - raw::symmetric_tag_pull(tag, out.as_mut_ptr(), out.len())?; - raw::symmetric_tag_close(tag)?; - Ok(out) + let mut hash = Hmac::create(alg, key)?; + for info in infos { + hash.update(info)?; + } + hash.digest() +} + +/// Behaviour like +/// +/// ```js +/// let hash = createHash(alg); +/// infos.forEach(info => hash.update(info)); +/// let return = hash.digest(); +/// ``` +pub fn hash(alg: &str, infos: &[impl AsRef<[u8]>]) -> Result, raw::CryptoErrno> { + let mut hash = Hash::create(alg)?; + for info in infos { + hash.update(info)?; } + hash.digest() } fn hkdf_extract( @@ -267,42 +372,42 @@ impl ScryptRom { self.x.copy_from_slice(&self.b32); for _ in 0..4 { - self.x[4] ^= R(self.x[0] + self.x[12], 7); - self.x[8] ^= R(self.x[4] + self.x[0], 9); - self.x[12] ^= R(self.x[8] + self.x[4], 13); - self.x[0] ^= R(self.x[12] + self.x[8], 18); - self.x[9] ^= R(self.x[5] + self.x[1], 7); - self.x[13] ^= R(self.x[9] + self.x[5], 9); - self.x[1] ^= R(self.x[13] + self.x[9], 13); - self.x[5] ^= R(self.x[1] + self.x[13], 18); - self.x[14] ^= R(self.x[10] + self.x[6], 7); - self.x[2] ^= R(self.x[14] + self.x[10], 9); - self.x[6] ^= R(self.x[2] + self.x[14], 13); - self.x[10] ^= R(self.x[6] + self.x[2], 18); - self.x[3] ^= R(self.x[15] + self.x[11], 7); - self.x[7] ^= R(self.x[3] + self.x[15], 9); - self.x[11] ^= R(self.x[7] + self.x[3], 13); - self.x[15] ^= R(self.x[11] + self.x[7], 18); - self.x[1] ^= R(self.x[0] + self.x[3], 7); - self.x[2] ^= R(self.x[1] + self.x[0], 9); - self.x[3] ^= R(self.x[2] + self.x[1], 13); - self.x[0] ^= R(self.x[3] + self.x[2], 18); - self.x[6] ^= R(self.x[5] + self.x[4], 7); - self.x[7] ^= R(self.x[6] + self.x[5], 9); - self.x[4] ^= R(self.x[7] + self.x[6], 13); - self.x[5] ^= R(self.x[4] + self.x[7], 18); - self.x[11] ^= R(self.x[10] + self.x[9], 7); - self.x[8] ^= R(self.x[11] + self.x[10], 9); - self.x[9] ^= R(self.x[8] + self.x[11], 13); - self.x[10] ^= R(self.x[9] + self.x[8], 18); - self.x[12] ^= R(self.x[15] + self.x[14], 7); - self.x[13] ^= R(self.x[12] + self.x[15], 9); - self.x[14] ^= R(self.x[13] + self.x[12], 13); - self.x[15] ^= R(self.x[14] + self.x[13], 18); + self.x[4] ^= R(self.x[0].wrapping_add(self.x[12]), 7); + self.x[8] ^= R(self.x[4].wrapping_add(self.x[0]), 9); + self.x[12] ^= R(self.x[8].wrapping_add(self.x[4]), 13); + self.x[0] ^= R(self.x[12].wrapping_add(self.x[8]), 18); + self.x[9] ^= R(self.x[5].wrapping_add(self.x[1]), 7); + self.x[13] ^= R(self.x[9].wrapping_add(self.x[5]), 9); + self.x[1] ^= R(self.x[13].wrapping_add(self.x[9]), 13); + self.x[5] ^= R(self.x[1].wrapping_add(self.x[13]), 18); + self.x[14] ^= R(self.x[10].wrapping_add(self.x[6]), 7); + self.x[2] ^= R(self.x[14].wrapping_add(self.x[10]), 9); + self.x[6] ^= R(self.x[2].wrapping_add(self.x[14]), 13); + self.x[10] ^= R(self.x[6].wrapping_add(self.x[2]), 18); + self.x[3] ^= R(self.x[15].wrapping_add(self.x[11]), 7); + self.x[7] ^= R(self.x[3].wrapping_add(self.x[15]), 9); + self.x[11] ^= R(self.x[7].wrapping_add(self.x[3]), 13); + self.x[15] ^= R(self.x[11].wrapping_add(self.x[7]), 18); + self.x[1] ^= R(self.x[0].wrapping_add(self.x[3]), 7); + self.x[2] ^= R(self.x[1].wrapping_add(self.x[0]), 9); + self.x[3] ^= R(self.x[2].wrapping_add(self.x[1]), 13); + self.x[0] ^= R(self.x[3].wrapping_add(self.x[2]), 18); + self.x[6] ^= R(self.x[5].wrapping_add(self.x[4]), 7); + self.x[7] ^= R(self.x[6].wrapping_add(self.x[5]), 9); + self.x[4] ^= R(self.x[7].wrapping_add(self.x[6]), 13); + self.x[5] ^= R(self.x[4].wrapping_add(self.x[7]), 18); + self.x[11] ^= R(self.x[10].wrapping_add(self.x[9]), 7); + self.x[8] ^= R(self.x[11].wrapping_add(self.x[10]), 9); + self.x[9] ^= R(self.x[8].wrapping_add(self.x[11]), 13); + self.x[10] ^= R(self.x[9].wrapping_add(self.x[8]), 18); + self.x[12] ^= R(self.x[15].wrapping_add(self.x[14]), 7); + self.x[13] ^= R(self.x[12].wrapping_add(self.x[15]), 9); + self.x[14] ^= R(self.x[13].wrapping_add(self.x[12]), 13); + self.x[15] ^= R(self.x[14].wrapping_add(self.x[13]), 18); } for i in 0..16 { - self.b32[i] += self.x[i]; + self.b32[i] = self.b32[i].wrapping_add(self.x[i]); } for i in 0..16 { diff --git a/tests/test-crypto.rs b/tests/test-crypto.rs index f957dd0..b6a4c61 100644 --- a/tests/test-crypto.rs +++ b/tests/test-crypto.rs @@ -179,7 +179,6 @@ fn test_crypto_hash_stream_pipe() { test_js_file("test/crypto/test-crypto-hash-stream-pipe.js"); } #[test] -#[ignore = "working"] fn test_crypto_hkdf() { test_js_file("test/crypto/test-crypto-hkdf.js"); } From d2cd6129d41bf372bfc105ea9025474e983de5a7 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Wed, 28 Dec 2022 22:17:09 +0800 Subject: [PATCH 12/25] test: crypto-hash, crypto-hmac --- .github/workflows/examples.yml | 2 +- modules/crypto.js | 34 +++-- modules/internal/crypto/hash.js | 68 +++------ modules/internal/errors.js | 14 ++ modules/internal/streams/lazy_transform.js | 8 +- modules/internal/validators.js | 80 +++++++++- src/internal_module/crypto/lib.rs | 140 +++++++++++++++++- src/internal_module/crypto/mod.rs | 154 ++++++++++++++++++++ test/common.js | 3 +- test/crypto/test-crypto-hash-stream-pipe.js | 8 +- test/crypto/test-crypto-hash.js | 36 ++--- test/crypto/test-crypto-hmac.js | 25 ++-- tests/test-crypto.rs | 4 +- 13 files changed, 474 insertions(+), 102 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index c579d28..951a817 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -33,7 +33,7 @@ jobs: - name: Install WasmEdge run: | VERSION=0.10.0 - curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | sudo bash -s -- -e all --version=$VERSION --tf-version=$VERSION --tf-deps-version=$VERSION --tf-tools-version=$VERSION --image-version=$VERSION --image-deps-version=$VERSION -p /usr/local + curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | sudo bash -s -- -e all --version=$VERSION --tf-version=$VERSION --tf-deps-version=$VERSION --tf-tools-version=$VERSION --image-version=$VERSION --image-deps-version=$VERSION --plugins wasi_crypto -p /usr/local - uses: actions/setup-node@v2 with: diff --git a/modules/crypto.js b/modules/crypto.js index 06ffe8c..3787a74 100644 --- a/modules/crypto.js +++ b/modules/crypto.js @@ -68,8 +68,8 @@ import { signOneShot, Verify, verifyOneShot, -} from "./internal/crypto/sig"; -import { createHash, Hash, Hmac } from "./internal/crypto/hash"; +} from "./internal/crypto/sig";*/ +import { Hash, Hmac } from "./internal/crypto/hash";/* import { X509Certificate } from "./internal/crypto/x509"; */import { getCiphers, @@ -107,11 +107,15 @@ function createDiffieHellmanGroup(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); } @@ -160,9 +164,9 @@ export default { createDecipheriv, createDiffieHellman, createDiffieHellmanGroup, - createECDH, + createECDH,*/ createHash, - createHmac, + createHmac,/* createPrivateKey, createPublicKey, createSecretKey, @@ -184,11 +188,11 @@ export default { getCurves,/* getDiffieHellman,*/ getFips, - getHashes,/* - Hash,*/ + getHashes, + Hash, hkdf, - hkdfSync,/* - Hmac, + hkdfSync, + Hmac,/* KeyObject,*/ pbkdf2, pbkdf2Sync,/* @@ -225,9 +229,9 @@ export { createDecipheriv, createDiffieHellman, createDiffieHellmanGroup, - createECDH, + createECDH,*/ createHash, - createHmac, + createHmac,/* createPrivateKey, createPublicKey, createSecretKey, @@ -249,11 +253,11 @@ export { getCurves,/* getDiffieHellman,*/ getFips, - getHashes,/* - Hash,*/ + getHashes, + Hash, hkdf, - hkdfSync,/* - Hmac, + hkdfSync, + Hmac,/* KeyObject,*/ pbkdf2, pbkdf2Sync,/* diff --git a/modules/internal/crypto/hash.js b/modules/internal/crypto/hash.js index a01fa7f..72ceb07 100644 --- a/modules/internal/crypto/hash.js +++ b/modules/internal/crypto/hash.js @@ -1,12 +1,5 @@ 'use strict'; -const { - Hash: _Hash, - HashJob, - Hmac: _Hmac, - kCryptoJobAsync, -} = internalBinding('crypto'); - import { getArrayBufferOrView, getDefaultEncoding, @@ -16,6 +9,7 @@ import { normalizeHashName, validateMaxBufferLength, kHandle, + getHashes, } from '../crypto/util'; import { @@ -51,11 +45,20 @@ 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)) + 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) @@ -79,12 +82,12 @@ Hash.prototype.copy = function copy(options) { }; Hash.prototype._transform = function _transform(chunk, encoding, callback) { - this[kHandle].update(chunk, encoding); + this.update(chunk, encoding); callback(); }; Hash.prototype._flush = function _flush(callback) { - this.push(this[kHandle].digest()); + this.push(this.digest()); callback(); }; @@ -101,8 +104,8 @@ Hash.prototype.update = function update(data, encoding) { throw new ERR_INVALID_ARG_TYPE( 'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data); } - - if (!this[kHandle].update(data, encoding)) + let buffer = getArrayBufferOrView(data, "data", encoding); + if (!this[kHandle].update(buffer.buffer ?? buffer)) throw new ERR_CRYPTO_HASH_UPDATE_FAILED(); return this; }; @@ -115,19 +118,21 @@ Hash.prototype.digest = function digest(outputEncoding) { outputEncoding = outputEncoding || getDefaultEncoding(); // Explicit conversion for backward compatibility. - const ret = this[kHandle].digest(`${outputEncoding}`); + const ret = Buffer.from(this[kHandle].digest()); state[kFinalized] = true; - return ret; + 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); - this[kHandle] = new _Hmac(); - this[kHandle].init(hmac, key); + this[kHandle] = new _Hmac(hmac, key.buffer ?? key); this[kState] = { [kFinalized]: false }; @@ -149,9 +154,9 @@ Hmac.prototype.digest = function digest(outputEncoding) { } // Explicit conversion for backward compatibility. - const ret = this[kHandle].digest(`${outputEncoding}`); + const ret = Buffer.from(this[kHandle].digest()); state[kFinalized] = true; - return ret; + return outputEncoding === 'buffer' ? ret : ret.toString(outputEncoding);; }; Hmac.prototype._flush = Hash.prototype._flush; @@ -159,34 +164,7 @@ Hmac.prototype._transform = Hash.prototype._transform; // Implementation for WebCrypto subtle.digest() -async function asyncDigest(algorithm, data) { - algorithm = normalizeAlgorithm(algorithm); - data = getArrayBufferOrView(data, 'data'); - validateMaxBufferLength(data, 'data'); - - if (algorithm.length !== undefined) - validateUint32(algorithm.length, 'algorithm.length'); - - switch (algorithm.name) { - case 'SHA-1': - // Fall through - case 'SHA-256': - // Fall through - case 'SHA-384': - // Fall through - case 'SHA-512': - return jobPromise(new HashJob( - kCryptoJobAsync, - normalizeHashName(algorithm.name), - data, - algorithm.length)); - } - - throw lazyDOMException('Unrecognized name.', 'NotSupportedError'); -} - export { Hash, Hmac, - asyncDigest, }; diff --git a/modules/internal/errors.js b/modules/internal/errors.js index 24fc459..3ae33a3 100644 --- a/modules/internal/errors.js +++ b/modules/internal/errors.js @@ -908,3 +908,17 @@ export class ERR_CRYPTO_INVALID_KEYLEN extends RangeError { 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"; + } +} diff --git a/modules/internal/streams/lazy_transform.js b/modules/internal/streams/lazy_transform.js index 45d14d5..8c10a78 100644 --- a/modules/internal/streams/lazy_transform.js +++ b/modules/internal/streams/lazy_transform.js @@ -3,7 +3,7 @@ // for the stream, one conventional and one non-conventional. 'use strict'; -import stream from '../../stream'; +import Transform from './transform'; import { getDefaultEncoding @@ -12,12 +12,12 @@ import { export function LazyTransform(options) { this._options = options; } -Object.setPrototypeOf(LazyTransform.prototype, stream.Transform.prototype); -Object.setPrototypeOf(LazyTransform, stream.Transform); +Object.setPrototypeOf(LazyTransform.prototype, Transform.prototype); +Object.setPrototypeOf(LazyTransform, Transform); function makeGetter(name) { return function() { - stream.Transform.call(this, this._options); + Transform.call(this, this._options); this._writableState.decodeStrings = false; if (!this._options || !this._options.defaultEncoding) { diff --git a/modules/internal/validators.js b/modules/internal/validators.js index 5080f62..35ca8c2 100644 --- a/modules/internal/validators.js +++ b/modules/internal/validators.js @@ -254,6 +254,83 @@ export const validateOneOf = hideStackFrames((value, name, oneOf) => { } }); +// 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, @@ -266,5 +343,6 @@ export default { validateNumber, validateArray, getValidMode, - validateOneOf + validateOneOf, + validateEncoding } \ No newline at end of file diff --git a/src/internal_module/crypto/lib.rs b/src/internal_module/crypto/lib.rs index f326219..418250a 100644 --- a/src/internal_module/crypto/lib.rs +++ b/src/internal_module/crypto/lib.rs @@ -1,5 +1,7 @@ use super::raw; +pub type CryptoErrno = raw::CryptoErrno; + const NONE_OPTS: raw::OptOptions = raw::OptOptions { tag: raw::OPT_OPTIONS_U_NONE.raw(), u: raw::OptOptionsUnion { none: () }, @@ -77,7 +79,7 @@ impl Drop for Hmac { pub struct Hash { handle: raw::SymmetricState, - hash_len: usize + hash_len: usize, } impl Hash { @@ -109,6 +111,14 @@ impl Hash { } Ok(()) } + + pub fn copy(&self) -> Result { + let h = unsafe { raw::symmetric_state_clone(self.handle) }?; + Ok(Self { + handle: h, + hash_len: self.hash_len, + }) + } } impl Drop for Hash { @@ -469,3 +479,131 @@ pub fn u8array_to_hex(arr: impl AsRef<[u8]>) -> String { .collect::>() .join("") } + +pub struct Cipher { + handle: raw::SymmetricState, + message: Vec, + tag: Option>, +} + +impl Cipher { + pub fn create(alg: &str, key: T, iv: T) -> Result + where + T: AsRef<[u8]>, + { + let alg = match alg { + "aes-128-gcm" | "AES-128-GCM" => "AES-128-GCM", + "aes-256-gcm" | "AES-256-GCM" => "AES-256-GCM", + _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), + }; + let handle = { + let key = key.as_ref(); + let iv = iv.as_ref(); + unsafe { + let raw_key = raw::symmetric_key_import(alg, key.as_ptr(), key.len())?; + let key = raw::OptSymmetricKey { + tag: raw::OPT_SYMMETRIC_KEY_U_SOME.raw(), + u: raw::OptSymmetricKeyUnion { some: raw_key }, + }; + let opt = raw::options_open(raw::ALGORITHM_TYPE_SYMMETRIC)?; + raw::options_set(opt, "nonce", iv.as_ptr(), iv.len())?; + let opts = raw::OptOptions { + tag: raw::OPT_OPTIONS_U_SOME.raw(), + u: raw::OptOptionsUnion { some: opt }, + }; + let state = raw::symmetric_state_open(alg, key, opts).unwrap(); + raw::symmetric_key_close(raw_key).unwrap(); + state + } + }; + Ok(Self { + handle, + message: vec![], + tag: None, + }) + } + + pub fn set_aad(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { + let data = data.as_ref(); + unsafe { raw::symmetric_state_absorb(self.handle, data.as_ptr(), data.len()) } + } + + /// in WasmEdge implement of wasi-crypto, `encrypt` can't be called multiple times, + /// multiple call `encrypt` is also not equivalent to multiple call `update`. + /// so we store all message and concat it, then encrypt one-time on `final` + pub fn update(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { + self.message.extend_from_slice(data.as_ref()); + Ok(()) + } + + /// `final` is reserved keyword, `fin` looks better than `r#final` + pub fn fin(&mut self) -> Result, raw::CryptoErrno> { + let mut out = vec![0; self.message.len()]; + unsafe { + let tag = raw::symmetric_state_encrypt_detached( + self.handle, + out.as_mut_ptr(), + out.len(), + self.message.as_ptr(), + self.message.len(), + )?; + let len = raw::symmetric_tag_len(tag)?; + let mut buf = vec![0; len]; + raw::symmetric_tag_pull(tag, buf.as_mut_ptr(), buf.len())?; + raw::symmetric_tag_close(tag)?; + self.tag = Some(buf); + } + Ok(out) + } + + /// equivalent to `update(data)` then `final` + pub fn encrypt(&mut self, data: impl AsRef<[u8]>) -> Result, raw::CryptoErrno> { + let data = data.as_ref(); + let mut out = vec![0; data.len()]; + unsafe { + let tag = raw::symmetric_state_encrypt_detached( + self.handle, + out.as_mut_ptr(), + out.len(), + data.as_ptr(), + data.len(), + )?; + let len = raw::symmetric_tag_len(tag)?; + let mut buf = vec![0; len]; + raw::symmetric_tag_pull(tag, buf.as_mut_ptr(), buf.len())?; + raw::symmetric_tag_close(tag)?; + self.tag = Some(buf); + } + Ok(out) + } + + pub fn get_auth_tag(&self) -> Result<&Vec, raw::CryptoErrno> { + self.tag.as_ref().ok_or(raw::CRYPTO_ERRNO_INVALID_OPERATION) + } + + pub fn take_auth_tag(&mut self) -> Result, raw::CryptoErrno> { + self.tag.take().ok_or(raw::CRYPTO_ERRNO_INVALID_OPERATION) + } +} + +impl Drop for Cipher { + fn drop(&mut self) { + unsafe { + raw::symmetric_state_close(self.handle).unwrap(); + } + } +} + +pub fn encrypt>( + alg: &str, + key: T, + iv: T, + aad: T, + msg: T, +) -> Result<(Vec, Vec), raw::CryptoErrno> { + let mut c = Cipher::create(alg, key, iv)?; + c.set_aad(aad)?; + let out = c.encrypt(msg)?; + let tag = c.take_auth_tag()?; + Ok((out, tag)) +} diff --git a/src/internal_module/crypto/mod.rs b/src/internal_module/crypto/mod.rs index 15d571b..590e406 100644 --- a/src/internal_module/crypto/mod.rs +++ b/src/internal_module/crypto/mod.rs @@ -163,6 +163,156 @@ fn hkdf_sync(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue } } +struct JsHash { + handle: lib::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)) => lib::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), + } + } + + fn finalizer(data: &mut Self::RefType, _event_loop: Option<&mut EventLoop>) { + std::mem::drop(data) + } +} + +struct JsHmac { + handle: lib::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))) => { + lib::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), + } + } + + fn finalizer(data: &mut Self::RefType, _event_loop: Option<&mut EventLoop>) { + std::mem::drop(data) + } +} + struct Crypto; impl ModuleInit for Crypto { @@ -188,6 +338,8 @@ impl ModuleInit for Crypto { "hkdf_sync\0", ctx.wrap_function("hkdf_sync", hkdf_sync).into(), ); + m.add_export(JsHash::CLASS_NAME, register_class::(ctx)); + m.add_export(JsHmac::CLASS_NAME, register_class::(ctx)); } } @@ -201,6 +353,8 @@ pub fn init_module(ctx: &mut Context) { "pbkdf2_sync\0", "scrypt_sync\0", "hkdf_sync\0", + JsHash::CLASS_NAME, + JsHmac::CLASS_NAME, ], ) } diff --git a/test/common.js b/test/common.js index d18e2fe..7664a89 100644 --- a/test/common.js +++ b/test/common.js @@ -284,7 +284,7 @@ export function getArrayBufferViews(buf) { } export const hasCrypto = true; - +export const hasFipsCrypto = true const common = { isDumbTerminal, isFreeBSD, @@ -298,6 +298,7 @@ const common = { isMainThread, hasCrypto, hasOpenSSL3, + hasFipsCrypto, mustCall, mustCallAtLeast, mustNotCall, diff --git a/test/crypto/test-crypto-hash-stream-pipe.js b/test/crypto/test-crypto-hash-stream-pipe.js index 6588509..950261d 100644 --- a/test/crypto/test-crypto-hash-stream-pipe.js +++ b/test/crypto/test-crypto-hash-stream-pipe.js @@ -20,15 +20,15 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. 'use strict'; -const common = require('../common'); +import common from '../common'; if (!common.hasCrypto) common.skip('missing crypto'); -const assert = require('assert'); -const crypto = require('crypto'); +import assert from 'assert'; +import crypto from 'crypto'; -const stream = require('stream'); +import stream from 'stream'; const s = new stream.PassThrough(); const h = crypto.createHash('sha3-512'); const expect = '36a38a2a35e698974d4e5791a3f05b05' + diff --git a/test/crypto/test-crypto-hash.js b/test/crypto/test-crypto-hash.js index c8320bd..f9bef94 100644 --- a/test/crypto/test-crypto-hash.js +++ b/test/crypto/test-crypto-hash.js @@ -1,24 +1,24 @@ // Copyright Joyent and Node contributors. All rights reserved. MIT license. 'use strict'; -const common = require('../common'); +import common from'../common'; if (!common.hasCrypto) common.skip('missing crypto'); -const assert = require('assert'); -const crypto = require('crypto'); -const fs = require('fs'); +import assert from 'assert'; +import crypto from 'crypto'; +import fs from 'fs'; -const fixtures = require('../common/fixtures'); +import fixtures from '../common/fixtures'; let cryptoType; let digest; // Test hashing -const a1 = crypto.createHash('sha1').update('Test123').digest('hex'); +//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'); +//const a4 = crypto.createHash('sha1').update('Test123').digest('buffer'); // stream interface let a5 = crypto.createHash('sha512'); @@ -40,7 +40,7 @@ let a8 = crypto.createHash('sha512'); a8.write(''); a8.end(); a8 = a8.read(); - +/* if (!common.hasFipsCrypto) { cryptoType = 'md5'; digest = 'latin1'; @@ -56,7 +56,7 @@ digest = 'hex'; assert.strictEqual( a1, '8308651804facb7b9af8ffc53a33a22d6a1c8ac2', - `${cryptoType} with ${digest} digest failed to evaluate to expected hash`); + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`);*/ cryptoType = 'sha256'; digest = 'base64'; assert.strictEqual( @@ -75,13 +75,13 @@ assert.deepStrictEqual( '\u00c2\u0006\u00da0\u00a1\u00879(G\u00ed\'', 'latin1'), `${cryptoType} with ${digest} digest failed to evaluate to expected hash`); -cryptoType = 'sha1'; +/*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); @@ -90,12 +90,12 @@ assert.notStrictEqual(a7, undefined); assert.notStrictEqual(a8, undefined); // Test multiple updates to same hash -const h1 = crypto.createHash('sha1').update('Test123').digest('hex'); +/*const h1 = crypto.createHash('sha1').update('Test123').digest('hex'); const h2 = crypto.createHash('sha1').update('Test').update('123').digest('hex'); -assert.strictEqual(h1, h2); +assert.strictEqual(h1, h2);*/ // Test hashing for binary files -const fn = fixtures.path('sample.png'); +/*const fn = fixtures.path('sample.png'); const sha1Hash = crypto.createHash('sha1'); const fileStream = fs.createReadStream(fn); fileStream.on('data', function(data) { @@ -105,7 +105,7 @@ 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. @@ -185,7 +185,7 @@ assert.throws( // Test XOF hash functions and the outputLength option. { // Default outputLengths. - assert.strictEqual(crypto.createHash('shake128').digest('hex'), + /*assert.strictEqual(crypto.createHash('shake128').digest('hex'), '7f9c2ba4e88f827d616045507605853e'); assert.strictEqual(crypto.createHash('shake128', null).digest('hex'), '7f9c2ba4e88f827d616045507605853e'); @@ -244,13 +244,13 @@ assert.throws( .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 }), diff --git a/test/crypto/test-crypto-hmac.js b/test/crypto/test-crypto-hmac.js index 1ba682f..9cf0c17 100644 --- a/test/crypto/test-crypto-hmac.js +++ b/test/crypto/test-crypto-hmac.js @@ -1,12 +1,13 @@ // Copyright Joyent and Node contributors. All rights reserved. MIT license. 'use strict'; -const common = require('../common'); +import common from '../common'; if (!common.hasCrypto) common.skip('missing crypto'); -const assert = require('assert'); -const crypto = require('crypto'); +import assert from 'assert'; +import crypto from 'crypto'; +import { getHashes } from '../../modules/crypto'; { const Hmac = crypto.Hmac; @@ -33,18 +34,22 @@ assert.throws( message: 'boom' }); -assert.throws( +/*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]; @@ -267,6 +272,8 @@ const rfc4231 = [ 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'); @@ -413,7 +420,7 @@ assert.strictEqual( 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'; @@ -451,13 +458,13 @@ assert.strictEqual( 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/tests/test-crypto.rs b/tests/test-crypto.rs index b6a4c61..52a6780 100644 --- a/tests/test-crypto.rs +++ b/tests/test-crypto.rs @@ -169,12 +169,10 @@ fn test_crypto_getcipherinfo() { test_js_file("test/crypto/test-crypto-getcipherinfo.js"); } #[test] -#[ignore = "working"] fn test_crypto_hash() { test_js_file("test/crypto/test-crypto-hash.js"); } -#[test] -#[ignore = "working"] +#[ignore = "unsupported, sha3-512"] fn test_crypto_hash_stream_pipe() { test_js_file("test/crypto/test-crypto-hash-stream-pipe.js"); } From 2619b7ba9b190a3789c2eb33f870d1f185fcf0db Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Fri, 30 Dec 2022 00:12:49 +0800 Subject: [PATCH 13/25] impl: cipheriv, decipheriv --- .github/workflows/examples.yml | 8 +- modules/crypto.js | 44 +-- modules/internal/crypto/cipher.js | 325 ++++++++++++++++++ modules/internal/crypto/hash.js | 1 + src/internal_module/crypto/lib.rs | 180 +++++++++- src/internal_module/crypto/mod.rs | 153 +++++++++ .../crypto/test-crypto-cipheriv-decipheriv.js | 33 +- tests/test-crypto.rs | 10 +- 8 files changed, 698 insertions(+), 56 deletions(-) create mode 100644 modules/internal/crypto/cipher.js diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 951a817..db302ed 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -32,8 +32,12 @@ jobs: - name: Install WasmEdge run: | - VERSION=0.10.0 - curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | sudo bash -s -- -e all --version=$VERSION --tf-version=$VERSION --tf-deps-version=$VERSION --tf-tools-version=$VERSION --image-version=$VERSION --image-deps-version=$VERSION --plugins wasi_crypto -p /usr/local + VERSION=0.11.2 + curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | sudo bash -s -- -e all --version=$VERSION --tf-version=$VERSION --tf-deps-version=$VERSION --tf-tools-version=$VERSION --image-version=$VERSION --image-deps-version=$VERSION -p /usr/local + curl -sLO https://github.com/WasmEdge/WasmEdge/releases/download/0.11.2/WasmEdge-plugin-wasi_crypto-0.11.2-manylinux2014_x86_64.tar.gz + tar -zxf WasmEdge-plugin-wasi_crypto-0.11.2-manylinux2014_x86_64.tar.gz + rm -f WasmEdge-plugin-wasi_crypto-0.11.2-manylinux2014_x86_64.tar.gz + mv libwasmedgePluginWasiCrypto.so /usr/local/lib/wasmedge/ - uses: actions/setup-node@v2 with: diff --git a/modules/crypto.js b/modules/crypto.js index 3787a74..8f770b8 100644 --- a/modules/crypto.js +++ b/modules/crypto.js @@ -52,7 +52,7 @@ import { diffieHellman, DiffieHellmanGroup, ECDH, -} from "./internal/crypto/diffiehellman"; +} from "./internal/crypto/diffiehellman";*/ import { Cipheriv, Decipheriv, @@ -62,7 +62,7 @@ import { publicDecrypt, publicEncrypt, } from "./internal/crypto/cipher"; - +/* import { Sign, signOneShot, @@ -82,7 +82,7 @@ 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); } @@ -90,7 +90,7 @@ function createCipheriv(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, @@ -157,11 +157,11 @@ const verify = verifyOneShot; export default { /*Certificate,*/ checkPrime, - checkPrimeSync,/* + checkPrimeSync, Cipheriv, constants, createCipheriv, - createDecipheriv, + createDecipheriv,/* createDiffieHellman, createDiffieHellmanGroup, createECDH,*/ @@ -171,8 +171,8 @@ export default { createPublicKey, createSecretKey, createSign, - createVerify, - Decipheriv, + createVerify,*/ + Decipheriv,/* DiffieHellman, diffieHellman, DiffieHellmanGroup, @@ -182,8 +182,8 @@ export default { generateKeyPairSync, generateKeySync,*/ generatePrime, - generatePrimeSync,/* - getCipherInfo,*/ + generatePrimeSync, + getCipherInfo, getCiphers, getCurves,/* getDiffieHellman,*/ @@ -195,11 +195,11 @@ export default { Hmac,/* KeyObject,*/ pbkdf2, - pbkdf2Sync,/* + pbkdf2Sync, privateDecrypt, privateEncrypt, publicDecrypt, - publicEncrypt,*/ + publicEncrypt, randomBytes, randomFill, randomFillSync, @@ -222,12 +222,12 @@ export default { export { /*Certificate,*/ checkPrime, - checkPrimeSync,/* - Cipheriv,*/ + checkPrimeSync, + Cipheriv, constants, - /*createCipheriv, + createCipheriv, createDecipheriv, - createDiffieHellman, + /*createDiffieHellman, createDiffieHellmanGroup, createECDH,*/ createHash, @@ -236,8 +236,8 @@ export { createPublicKey, createSecretKey, createSign, - createVerify, - Decipheriv, + createVerify,*/ + Decipheriv,/* DiffieHellman, diffieHellman, DiffieHellmanGroup, @@ -247,8 +247,8 @@ export { generateKeyPairSync, generateKeySync,*/ generatePrime, - generatePrimeSync,/* - getCipherInfo,*/ + generatePrimeSync, + getCipherInfo, getCiphers, getCurves,/* getDiffieHellman,*/ @@ -260,11 +260,11 @@ export { Hmac,/* KeyObject,*/ pbkdf2, - pbkdf2Sync,/* + pbkdf2Sync, privateDecrypt, privateEncrypt, publicDecrypt, - publicEncrypt,*/ + publicEncrypt, randomBytes, randomFill, randomFillSync, diff --git a/modules/internal/crypto/cipher.js b/modules/internal/crypto/cipher.js new file mode 100644 index 0000000..c4c76e1 --- /dev/null +++ b/modules/internal/crypto/cipher.js @@ -0,0 +1,325 @@ +// 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_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, +} from '../errors'; + +import { + validateEncoding, + validateInt32, + validateObject, + validateString, +} from '../validators'; + +import { + preparePrivateKey, + preparePublicOrPrivateKey, + prepareSecretKey, +} from './keys'; + +import { + getDefaultEncoding, + getArrayBufferOrView, + getStringOption, + kHandle, +} 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'; + +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, 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); + iv = iv === null ? null : getArrayBufferOrView(iv, 'iv'); + 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) { + let buf = getArrayBufferOrView(chunk, "chunk", encoding); + this.push(this[kHandle].update(buf.buffer ?? buf)); + callback(); +}; + +Cipher.prototype._flush = function _flush(callback) { + try { + this.push(this[kHandle].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') { + 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') { + 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/hash.js b/modules/internal/crypto/hash.js index 72ceb07..f572244 100644 --- a/modules/internal/crypto/hash.js +++ b/modules/internal/crypto/hash.js @@ -1,3 +1,4 @@ +// Copyright Joyent and Node contributors. All rights reserved. MIT license. 'use strict'; import { diff --git a/src/internal_module/crypto/lib.rs b/src/internal_module/crypto/lib.rs index 418250a..5e41e9b 100644 --- a/src/internal_module/crypto/lib.rs +++ b/src/internal_module/crypto/lib.rs @@ -480,6 +480,39 @@ pub fn u8array_to_hex(arr: impl AsRef<[u8]>) -> String { .join("") } +/// Convert hex string to u8 array +/// +/// # Examples +/// +/// ``` +/// use crypto_wasi::hex_to_u8array; +/// +/// assert_eq!(hex_to_u8array("01172d"), Some(vec![01, 23, 45])); +/// ``` +pub fn hex_to_u8array(arr: &str) -> Option> { + if arr.len() % 2 != 0 || arr.chars().any(|v| !v.is_ascii_hexdigit()) { + return None; + } + + fn hex_byte_to_u8(h: u8) -> u8 { + match h { + b'0'..=b'9' => h - b'0', + b'a'..=b'f' => 10 + h - b'a', + b'A'..=b'F' => 10 + h - b'A', + _ => unreachable!() + } + } + + Some( + arr.as_bytes() + .chunks(2) + .map(|v| { + (hex_byte_to_u8(v[0]) << 4) + hex_byte_to_u8(v[1]) + }) + .collect(), + ) +} + pub struct Cipher { handle: raw::SymmetricState, message: Vec, @@ -487,10 +520,11 @@ pub struct Cipher { } impl Cipher { - pub fn create(alg: &str, key: T, iv: T) -> Result - where - T: AsRef<[u8]>, - { + pub fn create( + alg: &str, + key: impl AsRef<[u8]>, + iv: impl AsRef<[u8]>, + ) -> Result { let alg = match alg { "aes-128-gcm" | "AES-128-GCM" => "AES-128-GCM", "aes-256-gcm" | "AES-256-GCM" => "AES-256-GCM", @@ -594,12 +628,12 @@ impl Drop for Cipher { } } -pub fn encrypt>( +pub fn encrypt( alg: &str, - key: T, - iv: T, - aad: T, - msg: T, + key: impl AsRef<[u8]>, + iv: impl AsRef<[u8]>, + aad: impl AsRef<[u8]>, + msg: impl AsRef<[u8]>, ) -> Result<(Vec, Vec), raw::CryptoErrno> { let mut c = Cipher::create(alg, key, iv)?; c.set_aad(aad)?; @@ -607,3 +641,131 @@ pub fn encrypt>( let tag = c.take_auth_tag()?; Ok((out, tag)) } + +pub struct Decipher { + handle: raw::SymmetricState, + message: Vec, + tag: Option>, +} + +impl Decipher { + pub fn create( + alg: &str, + key: impl AsRef<[u8]>, + iv: impl AsRef<[u8]>, + ) -> Result { + let alg = match alg { + "aes-128-gcm" | "AES-128-GCM" => "AES-128-GCM", + "aes-256-gcm" | "AES-256-GCM" => "AES-256-GCM", + _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), + }; + let handle = { + let key = key.as_ref(); + let iv = iv.as_ref(); + unsafe { + let raw_key = raw::symmetric_key_import(alg, key.as_ptr(), key.len())?; + let key = raw::OptSymmetricKey { + tag: raw::OPT_SYMMETRIC_KEY_U_SOME.raw(), + u: raw::OptSymmetricKeyUnion { some: raw_key }, + }; + let opt = raw::options_open(raw::ALGORITHM_TYPE_SYMMETRIC)?; + raw::options_set(opt, "nonce", iv.as_ptr(), iv.len())?; + let opts = raw::OptOptions { + tag: raw::OPT_OPTIONS_U_SOME.raw(), + u: raw::OptOptionsUnion { some: opt }, + }; + let state = raw::symmetric_state_open(alg, key, opts).unwrap(); + raw::symmetric_key_close(raw_key).unwrap(); + state + } + }; + Ok(Self { + handle, + message: vec![], + tag: None, + }) + } + + pub fn set_aad(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { + let data = data.as_ref(); + unsafe { raw::symmetric_state_absorb(self.handle, data.as_ptr(), data.len()) } + } + + /// in WasmEdge implement of wasi-crypto, `decrypt` can't be called multiple times, + /// multiple call `decrypt` is also not equivalent to multiple call `update`. + /// so we store all message and concat it, then decrypt one-time on `final` + pub fn update(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { + self.message.extend_from_slice(data.as_ref()); + Ok(()) + } + + /// `final` is reserved keyword, `fin` looks better than `r#final` + pub fn fin(&mut self) -> Result, raw::CryptoErrno> { + if let Some(tag) = &self.tag { + let mut out = vec![0; self.message.len()]; + unsafe { + raw::symmetric_state_decrypt_detached( + self.handle, + out.as_mut_ptr(), + out.len(), + self.message.as_ptr(), + self.message.len(), + tag.as_ptr(), + tag.len(), + )?; + } + Ok(out) + } else { + Err(raw::CRYPTO_ERRNO_INVALID_OPERATION) + } + } + + /// equivalent to `update(data)` then `final` + pub fn decrypt(&mut self, data: impl AsRef<[u8]>) -> Result, raw::CryptoErrno> { + let data = data.as_ref(); + if let Some(tag) = &self.tag { + let mut out = vec![0; data.len()]; + unsafe { + raw::symmetric_state_decrypt_detached( + self.handle, + out.as_mut_ptr(), + out.len(), + data.as_ptr(), + data.len(), + tag.as_ptr(), + tag.len(), + )?; + } + Ok(out) + } else { + Err(raw::CRYPTO_ERRNO_INVALID_OPERATION) + } + } + + pub fn set_auth_tag(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { + self.tag = Some(data.as_ref().to_vec()); + Ok(()) + } +} + +impl Drop for Decipher { + fn drop(&mut self) { + unsafe { + raw::symmetric_state_close(self.handle).unwrap(); + } + } +} + +pub fn decrypt( + alg: &str, + key: impl AsRef<[u8]>, + iv: impl AsRef<[u8]>, + aad: impl AsRef<[u8]>, + auth_tag: impl AsRef<[u8]>, + msg: impl AsRef<[u8]>, +) -> Result, raw::CryptoErrno> { + let mut c = Decipher::create(alg, key, iv)?; + c.set_aad(aad)?; + c.set_auth_tag(auth_tag)?; + c.decrypt(msg) +} diff --git a/src/internal_module/crypto/mod.rs b/src/internal_module/crypto/mod.rs index 590e406..4baf83a 100644 --- a/src/internal_module/crypto/mod.rs +++ b/src/internal_module/crypto/mod.rs @@ -313,6 +313,157 @@ impl JsClassDef for JsHmac { } } +enum JsCipher { + Cipher(lib::Cipher), + Decipher(lib::Decipher), +} + +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 { + lib::Cipher::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 { + lib::Decipher::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) + } + } + + fn finalizer(data: &mut Self::RefType, _event_loop: Option<&mut EventLoop>) { + std::mem::drop(data) + } +} + struct Crypto; impl ModuleInit for Crypto { @@ -340,6 +491,7 @@ impl ModuleInit for Crypto { ); 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)); } } @@ -355,6 +507,7 @@ pub fn init_module(ctx: &mut Context) { "hkdf_sync\0", JsHash::CLASS_NAME, JsHmac::CLASS_NAME, + JsCipher::CLASS_NAME, ], ) } diff --git a/test/crypto/test-crypto-cipheriv-decipheriv.js b/test/crypto/test-crypto-cipheriv-decipheriv.js index 89d0e49..cea3896 100644 --- a/test/crypto/test-crypto-cipheriv-decipheriv.js +++ b/test/crypto/test-crypto-cipheriv-decipheriv.js @@ -1,14 +1,15 @@ // Copyright Joyent and Node contributors. All rights reserved. MIT license. 'use strict'; -const common = require('../common'); +import common from '../common'; if (!common.hasCrypto) common.skip('missing crypto'); -const assert = require('assert'); -const crypto = require('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' + @@ -43,6 +44,7 @@ function testCipher1(key, iv) { function testCipher2(key, iv) { + return; // unsupport des-ebe3-cbc // Test encryption and decryption with explicit key and iv const plaintext = '32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBGWWELw' + @@ -62,6 +64,7 @@ function testCipher2(key, 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'); @@ -85,7 +88,7 @@ function testCipher3(key, iv) { const key = '123456789012345678901234'; const iv = '12345678'; - const instance = Cipheriv('des-ede3-cbc', key, iv); + const instance = Cipheriv('aes-128-gcm', key, iv); assert(instance instanceof Cipheriv, 'Cipheriv is expected to return a new ' + 'instance when called without `new`'); @@ -99,14 +102,14 @@ function testCipher3(key, iv) { }); assert.throws( - () => crypto.createCipheriv('des-ede3-cbc', null), + () => crypto.createCipheriv('aes-128-gcm', null), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', }); assert.throws( - () => crypto.createCipheriv('des-ede3-cbc', key, 10), + () => crypto.createCipheriv('aes-128-gcm', key, 10), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', @@ -118,7 +121,7 @@ function testCipher3(key, iv) { const key = '123456789012345678901234'; const iv = '12345678'; - const instance = Decipheriv('des-ede3-cbc', key, iv); + const instance = Decipheriv('aes-128-gcm', key, iv); assert(instance instanceof Decipheriv, 'Decipheriv expected to return a new' + ' instance when called without `new`'); @@ -132,20 +135,20 @@ function testCipher3(key, iv) { }); assert.throws( - () => crypto.createDecipheriv('des-ede3-cbc', null), + () => crypto.createDecipheriv('aes-128-gcm', null), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', }); assert.throws( - () => crypto.createDecipheriv('des-ede3-cbc', key, 10), + () => crypto.createDecipheriv('aes-128-gcm', key, 10), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', }); } - +/* testCipher1('0123456789abcd0123456789', '12345678'); testCipher1('0123456789abcd0123456789', Buffer.from('12345678')); testCipher1(Buffer.from('0123456789abcd0123456789'), '12345678'); @@ -160,9 +163,9 @@ if (!common.hasFipsCrypto) { // 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( @@ -187,7 +190,7 @@ for (let n = 0; n < 256; n += 1) { 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), @@ -213,7 +216,7 @@ for (let n = minIvLength; n < maxIvLength; n += 1) { }); // Passing a key with an invalid length should throw. - assert.throws( + /*assert.throws( () => crypto.createCipheriv('aes-128-ecb', Buffer.alloc(17), null), - /Invalid key length/); + /Invalid key length/);*/ } diff --git a/tests/test-crypto.rs b/tests/test-crypto.rs index 52a6780..419eb87 100644 --- a/tests/test-crypto.rs +++ b/tests/test-crypto.rs @@ -33,8 +33,7 @@ fn test_js_file(file_path: &str) { }); } -#[test] -#[ignore = "working"] +#[ignore = "unsupported, aes-wrap"] fn test_crypto_aes_wrap() { test_js_file("test/crypto/test-crypto-aes-wrap.js"); } @@ -63,8 +62,7 @@ fn test_crypto_binary_default() { fn test_crypto_certificate() { test_js_file("test/crypto/test-crypto-certificate.js"); } -#[test] -#[ignore = "working"] +#[ignore = "unsupported, md5"] fn test_crypto_cipher_decipher() { test_js_file("test/crypto/test-crypto-cipher-decipher.js"); } @@ -133,12 +131,10 @@ fn test_crypto_dh_shared() { fn test_crypto_dh_stateless() { test_js_file("test/crypto/test-crypto-dh-stateless.js"); } -#[test] #[ignore = "unsupported, domain"] fn test_crypto_domain() { test_js_file("test/crypto/test-crypto-domain.js"); } -#[test] #[ignore = "unsupported, domain"] fn test_crypto_domains() { test_js_file("test/crypto/test-crypto-domains.js"); @@ -153,7 +149,6 @@ fn test_crypto_ecb() { fn test_crypto_ecdh_convert_key() { test_js_file("test/crypto/test-crypto-ecdh-convert-key.js"); } -#[test] #[ignore = "unsupported"] fn test_crypto_fips() { test_js_file("test/crypto/test-crypto-fips.js"); @@ -181,7 +176,6 @@ fn test_crypto_hkdf() { test_js_file("test/crypto/test-crypto-hkdf.js"); } #[test] -#[ignore = "working"] fn test_crypto_hmac() { test_js_file("test/crypto/test-crypto-hmac.js"); } From 349f66be8f2ba7f67bef95bf4dc9c1c60f54235b Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Tue, 3 Jan 2023 21:58:34 +0800 Subject: [PATCH 14/25] test: crypto-cipheriv-decipheriv --- .github/workflows/examples.yml | 2 +- modules/internal/crypto/cipher.js | 31 ++++++++++----- modules/internal/errors.js | 14 +++++++ .../crypto/test-crypto-cipheriv-decipheriv.js | 39 ++++++++++++++++--- tests/test-crypto.rs | 1 - 5 files changed, 71 insertions(+), 16 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index db302ed..cff87c7 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -37,7 +37,7 @@ jobs: curl -sLO https://github.com/WasmEdge/WasmEdge/releases/download/0.11.2/WasmEdge-plugin-wasi_crypto-0.11.2-manylinux2014_x86_64.tar.gz tar -zxf WasmEdge-plugin-wasi_crypto-0.11.2-manylinux2014_x86_64.tar.gz rm -f WasmEdge-plugin-wasi_crypto-0.11.2-manylinux2014_x86_64.tar.gz - mv libwasmedgePluginWasiCrypto.so /usr/local/lib/wasmedge/ + sudo mv libwasmedgePluginWasiCrypto.so /usr/local/lib/wasmedge/ - uses: actions/setup-node@v2 with: diff --git a/modules/internal/crypto/cipher.js b/modules/internal/crypto/cipher.js index c4c76e1..ca8e375 100644 --- a/modules/internal/crypto/cipher.js +++ b/modules/internal/crypto/cipher.js @@ -17,6 +17,7 @@ const { import { ERR_CRYPTO_INVALID_STATE, + ERR_CRYPTO_UNKNOWN_CIPHER, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, } from '../errors'; @@ -39,6 +40,7 @@ import { getArrayBufferOrView, getStringOption, kHandle, + getCiphers, } from './util'; import { @@ -53,6 +55,8 @@ 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 } = @@ -103,7 +107,7 @@ function createCipherBase(cipher, credential, options, decipher, iv) { if (iv === undefined) { // this[kHandle].init(cipher, credential, authTagLength); } else { - this[kHandle] = new CipherBase(cipher, credential, iv, authTagLength, decipher); + this[kHandle] = new CipherBase(cipher, credential.buffer ?? credential, iv.buffer ?? iv, authTagLength, decipher); } this._decoder = null; @@ -122,6 +126,15 @@ function createCipherWithIV(cipher, key, options, decipher, iv) { const encoding = getStringOption(options, 'encoding'); key = prepareSecretKey(key, encoding); 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]); } @@ -141,14 +154,13 @@ Object.setPrototypeOf(Cipher.prototype, LazyTransform.prototype); Object.setPrototypeOf(Cipher, LazyTransform); Cipher.prototype._transform = function _transform(chunk, encoding, callback) { - let buf = getArrayBufferOrView(chunk, "chunk", encoding); - this.push(this[kHandle].update(buf.buffer ?? buf)); + this.push(this.update(chunk, encoding)); callback(); }; Cipher.prototype._flush = function _flush(callback) { try { - this.push(this[kHandle].final()); + this.push(this.final()); } catch (e) { callback(e); return; @@ -172,8 +184,9 @@ Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) { const ret = this[kHandle].update(buf.buffer ?? buf); if (outputEncoding && outputEncoding !== 'buffer') { - this._decoder = getDecoder(this._decoder, outputEncoding); - return this._decoder.write(ret); + return ""; // current implemented doesn't return anything from update + // this._decoder = getDecoder(this._decoder, outputEncoding); + // return this._decoder.write(ret); } return ret; @@ -183,10 +196,10 @@ Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) { Cipher.prototype.final = function final(outputEncoding) { outputEncoding = outputEncoding || getDefaultEncoding(); const ret = this[kHandle].final(); - if (outputEncoding && outputEncoding !== 'buffer') { - this._decoder = getDecoder(this._decoder, outputEncoding); - return this._decoder.end(ret); + return Buffer.from(ret).toString(outputEncoding); + // this._decoder = getDecoder(this._decoder, outputEncoding); + // return this._decoder.end(ret); } return ret; diff --git a/modules/internal/errors.js b/modules/internal/errors.js index 3ae33a3..be833cd 100644 --- a/modules/internal/errors.js +++ b/modules/internal/errors.js @@ -922,3 +922,17 @@ export class ERR_CRYPTO_HASH_UPDATE_FAILED extends Error { 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/test/crypto/test-crypto-cipheriv-decipheriv.js b/test/crypto/test-crypto-cipheriv-decipheriv.js index cea3896..6979ff6 100644 --- a/test/crypto/test-crypto-cipheriv-decipheriv.js +++ b/test/crypto/test-crypto-cipheriv-decipheriv.js @@ -83,10 +83,32 @@ function testCipher3(key, iv) { `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 = '123456789012345678901234'; - const iv = '12345678'; + 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 ' + @@ -118,8 +140,8 @@ function testCipher3(key, iv) { { const Decipheriv = crypto.Decipheriv; - const key = '123456789012345678901234'; - const iv = '12345678'; + const key = '1234567890123456'; + const iv = '123456789012'; const instance = Decipheriv('aes-128-gcm', key, iv); assert(instance instanceof Decipheriv, 'Decipheriv expected to return a new' + @@ -148,6 +170,12 @@ function testCipher3(key, iv) { 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')); @@ -198,12 +226,13 @@ assert.throws( 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. diff --git a/tests/test-crypto.rs b/tests/test-crypto.rs index 419eb87..5c54d22 100644 --- a/tests/test-crypto.rs +++ b/tests/test-crypto.rs @@ -67,7 +67,6 @@ fn test_crypto_cipher_decipher() { test_js_file("test/crypto/test-crypto-cipher-decipher.js"); } #[test] -#[ignore = "working"] fn test_crypto_cipheriv_decipheriv() { test_js_file("test/crypto/test-crypto-cipheriv-decipheriv.js"); } From 98903ad56af482d7df8cd87807bf15e18c701c42 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Tue, 10 Jan 2023 21:09:01 +0800 Subject: [PATCH 15/25] ci: use ubuntu20.04 wasi_crypto plugin --- .github/workflows/examples.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index cff87c7..39515ec 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -34,9 +34,9 @@ jobs: run: | VERSION=0.11.2 curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | sudo bash -s -- -e all --version=$VERSION --tf-version=$VERSION --tf-deps-version=$VERSION --tf-tools-version=$VERSION --image-version=$VERSION --image-deps-version=$VERSION -p /usr/local - curl -sLO https://github.com/WasmEdge/WasmEdge/releases/download/0.11.2/WasmEdge-plugin-wasi_crypto-0.11.2-manylinux2014_x86_64.tar.gz - tar -zxf WasmEdge-plugin-wasi_crypto-0.11.2-manylinux2014_x86_64.tar.gz - rm -f WasmEdge-plugin-wasi_crypto-0.11.2-manylinux2014_x86_64.tar.gz + curl -sLO https://github.com/WasmEdge/WasmEdge/releases/download/0.11.2/WasmEdge-plugin-wasi_crypto-0.11.2-ubuntu20.04_x86_64.tar.gz + tar -zxf WasmEdge-plugin-wasi_crypto-0.11.2-ubuntu20.04_x86_64.tar.gz + rm -f WasmEdge-plugin-wasi_crypto-0.11.2-ubuntu20.04_x86_64.tar.gz sudo mv libwasmedgePluginWasiCrypto.so /usr/local/lib/wasmedge/ - uses: actions/setup-node@v2 From 7d3d5b14d24313bceceaa30562e2e9f6730ebe7f Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Tue, 10 Jan 2023 21:23:27 +0800 Subject: [PATCH 16/25] ci: upstream install.sh changed --- .github/workflows/examples.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 39515ec..685e804 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -33,7 +33,7 @@ jobs: - name: Install WasmEdge run: | VERSION=0.11.2 - curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | sudo bash -s -- -e all --version=$VERSION --tf-version=$VERSION --tf-deps-version=$VERSION --tf-tools-version=$VERSION --image-version=$VERSION --image-deps-version=$VERSION -p /usr/local + curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | sudo bash -s -- -e all --version=$VERSION --tf-version=$VERSION --tf-deps-version=$VERSION --tf-tools-version=$VERSION --image-version=$VERSION -p /usr/local curl -sLO https://github.com/WasmEdge/WasmEdge/releases/download/0.11.2/WasmEdge-plugin-wasi_crypto-0.11.2-ubuntu20.04_x86_64.tar.gz tar -zxf WasmEdge-plugin-wasi_crypto-0.11.2-ubuntu20.04_x86_64.tar.gz rm -f WasmEdge-plugin-wasi_crypto-0.11.2-ubuntu20.04_x86_64.tar.gz From b901ff6b731f7ed036fefdc414010530dd3dbb33 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Fri, 13 Jan 2023 16:04:03 +0800 Subject: [PATCH 17/25] add feature to enable crypto --- .github/workflows/examples.yml | 9 ++++----- Cargo.toml | 1 + src/internal_module/mod.rs | 1 + src/quickjs_sys/mod.rs | 6 +++++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 685e804..59b2a9c 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -185,8 +185,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.toml b/Cargo.toml index 4482cca..229c400 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,4 @@ default = [] img = ["image", "imageproc"] tensorflow = ["img"] cjs = [] +nodejs_crypto = [] diff --git a/src/internal_module/mod.rs b/src/internal_module/mod.rs index 48b8f49..2c3af76 100644 --- a/src/internal_module/mod.rs +++ b/src/internal_module/mod.rs @@ -1,4 +1,5 @@ pub mod core; +#[cfg(feature = "nodejs_crypto")] pub mod crypto; pub mod encoding; pub mod fs; diff --git a/src/quickjs_sys/mod.rs b/src/quickjs_sys/mod.rs index 5abbbdd..9610337 100644 --- a/src/quickjs_sys/mod.rs +++ b/src/quickjs_sys/mod.rs @@ -344,7 +344,11 @@ impl Context { super::internal_module::httpx::init_module(&mut ctx); super::internal_module::os::init_module(&mut ctx); super::internal_module::fs::init_module(&mut ctx); - super::internal_module::crypto::init_module(&mut ctx); + + #[cfg(feature = "nodejs_crypto")] + { + super::internal_module::crypto::init_module(&mut ctx); + } ctx } From 585f2d3c5effc22371f969525c493920e1d2a4c0 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Mon, 16 Jan 2023 17:13:34 +0800 Subject: [PATCH 18/25] test: add fixtures --- Cargo.lock | 9 +- modules/internal/crypto/certificate.js | 53 ++ modules/internal/crypto/diffiehellman.js | 412 +++++++++ modules/internal/crypto/keygen.js | 413 +++++++++ modules/internal/crypto/sig.js | 307 +++++++ modules/internal/crypto/x509.js | 368 ++++++++ src/internal_module/crypto/lib.rs | 6 +- test/fixtures/keys/.gitattributes | 4 + ...56_signature_signedby_rsa_private_b.sha256 | Bin 0 -> 256 bytes test/fixtures/keys/Makefile | 821 ++++++++++++++++++ test/fixtures/keys/agent1-cert.pem | 18 + test/fixtures/keys/agent1-csr.pem | 13 + test/fixtures/keys/agent1-key.pem | 15 + test/fixtures/keys/agent1.cnf | 26 + test/fixtures/keys/agent1.pfx | Bin 0 -> 2445 bytes test/fixtures/keys/agent10-cert.pem | 32 + test/fixtures/keys/agent10-csr.pem | 13 + test/fixtures/keys/agent10-key.pem | 15 + test/fixtures/keys/agent10.cnf | 17 + test/fixtures/keys/agent10.pfx | Bin 0 -> 3061 bytes test/fixtures/keys/agent2-cert.pem | 16 + test/fixtures/keys/agent2-csr.pem | 13 + test/fixtures/keys/agent2-key.pem | 15 + test/fixtures/keys/agent2.cnf | 19 + test/fixtures/keys/agent3-cert.pem | 16 + test/fixtures/keys/agent3-csr.pem | 13 + test/fixtures/keys/agent3-key.pem | 15 + test/fixtures/keys/agent3.cnf | 19 + test/fixtures/keys/agent4-cert.pem | 16 + test/fixtures/keys/agent4-csr.pem | 13 + test/fixtures/keys/agent4-key.pem | 15 + test/fixtures/keys/agent4.cnf | 21 + test/fixtures/keys/agent5-cert.pem | 16 + test/fixtures/keys/agent5-csr.pem | 12 + test/fixtures/keys/agent5-key.pem | 15 + test/fixtures/keys/agent5.cnf | 21 + test/fixtures/keys/agent6-cert.pem | 31 + test/fixtures/keys/agent6-csr.pem | 12 + test/fixtures/keys/agent6-key.pem | 15 + test/fixtures/keys/agent6.cnf | 18 + test/fixtures/keys/agent6.pfx | Bin 0 -> 3029 bytes test/fixtures/keys/agent7-cert.pem | 19 + test/fixtures/keys/agent7-csr.pem | 17 + test/fixtures/keys/agent7-key.pem | 27 + test/fixtures/keys/agent7.cnf | 17 + test/fixtures/keys/agent8-cert.pem | 20 + test/fixtures/keys/agent8-csr.pem | 17 + test/fixtures/keys/agent8-key.pem | 27 + test/fixtures/keys/agent8.cnf | 17 + test/fixtures/keys/agent9-cert.pem | 20 + test/fixtures/keys/agent9-csr.pem | 17 + test/fixtures/keys/agent9-key.pem | 27 + test/fixtures/keys/agent9.cnf | 17 + test/fixtures/keys/ca1-cert.pem | 16 + test/fixtures/keys/ca1-cert.srl | 1 + test/fixtures/keys/ca1-key.pem | 18 + test/fixtures/keys/ca1.cnf | 23 + test/fixtures/keys/ca2-cert.pem | 16 + test/fixtures/keys/ca2-cert.srl | 1 + test/fixtures/keys/ca2-crl.pem | 10 + test/fixtures/keys/ca2-database.txt | 1 + test/fixtures/keys/ca2-database.txt.attr | 1 + test/fixtures/keys/ca2-database.txt.attr.old | 1 + test/fixtures/keys/ca2-database.txt.old | 0 test/fixtures/keys/ca2-key.pem | 18 + test/fixtures/keys/ca2-serial | 1 + test/fixtures/keys/ca2.cnf | 33 + test/fixtures/keys/ca3-cert.pem | 16 + test/fixtures/keys/ca3-cert.srl | 1 + test/fixtures/keys/ca3-csr.pem | 13 + test/fixtures/keys/ca3-key.pem | 15 + test/fixtures/keys/ca3.cnf | 23 + test/fixtures/keys/ca4-cert.pem | 16 + test/fixtures/keys/ca4-cert.srl | 1 + test/fixtures/keys/ca4-csr.pem | 13 + test/fixtures/keys/ca4-key.pem | 15 + test/fixtures/keys/ca4.cnf | 23 + test/fixtures/keys/ca5-cert.pem | 14 + test/fixtures/keys/ca5-cert.srl | 1 + test/fixtures/keys/ca5-csr.pem | 10 + test/fixtures/keys/ca5-key.pem | 8 + test/fixtures/keys/ca5.cnf | 35 + test/fixtures/keys/ca6-cert.pem | 14 + test/fixtures/keys/ca6-cert.srl | 1 + test/fixtures/keys/ca6-csr.pem | 10 + test/fixtures/keys/ca6-key.pem | 8 + test/fixtures/keys/ca6.cnf | 22 + test/fixtures/keys/dh1024.pem | 5 + test/fixtures/keys/dh2048.pem | 8 + test/fixtures/keys/dh512.pem | 4 + test/fixtures/keys/dherror.pem | 4 + test/fixtures/keys/dns-cert1.cnf | Bin 0 -> 565 bytes test/fixtures/keys/dsa1025.pem | 9 + test/fixtures/keys/dsa_params.pem | 14 + test/fixtures/keys/dsa_private.pem | 20 + test/fixtures/keys/dsa_private_1025.pem | 12 + test/fixtures/keys/dsa_private_encrypted.pem | 23 + .../keys/dsa_private_encrypted_1025.pem | 12 + test/fixtures/keys/dsa_private_pkcs8.pem | 15 + test/fixtures/keys/dsa_public.pem | 20 + test/fixtures/keys/dsa_public_1025.pem | 12 + test/fixtures/keys/ec-cert.pem | 13 + test/fixtures/keys/ec-csr.pem | 9 + test/fixtures/keys/ec-key.pem | 8 + test/fixtures/keys/ec.cnf | 17 + test/fixtures/keys/ec.pfx | Bin 0 -> 1006 bytes test/fixtures/keys/ec10-cert.pem | 27 + test/fixtures/keys/ec10-csr.pem | 10 + test/fixtures/keys/ec10-key.pem | 8 + test/fixtures/keys/ec10.pfx | Bin 0 -> 2198 bytes test/fixtures/keys/ec_p256_private.pem | 5 + test/fixtures/keys/ec_p256_public.pem | 4 + test/fixtures/keys/ec_p384_private.pem | 6 + test/fixtures/keys/ec_p384_public.pem | 5 + test/fixtures/keys/ec_p521_private.pem | 8 + test/fixtures/keys/ec_p521_public.pem | 6 + test/fixtures/keys/ec_secp256k1_private.pem | 5 + test/fixtures/keys/ec_secp256k1_public.pem | 4 + test/fixtures/keys/ed25519_private.pem | 3 + test/fixtures/keys/ed25519_public.pem | 3 + test/fixtures/keys/ed448_private.pem | 4 + test/fixtures/keys/ed448_public.pem | 4 + test/fixtures/keys/fake-cnnic-root-cert.pem | 18 + test/fixtures/keys/fake-cnnic-root-cert.srl | 1 + test/fixtures/keys/fake-cnnic-root-key.pem | 27 + test/fixtures/keys/fake-cnnic-root.cnf | 19 + .../fixtures/keys/fake-startcom-root-cert.pem | 22 + .../keys/fake-startcom-root-database.txt | 2 + .../keys/fake-startcom-root-database.txt.attr | 1 + .../fake-startcom-root-database.txt.attr.old | 1 + .../keys/fake-startcom-root-database.txt.old | 1 + .../fake-startcom-root-issued-certs/01.pem | 20 + .../fake-startcom-root-issued-certs/02.pem | 20 + test/fixtures/keys/fake-startcom-root-key.pem | 27 + test/fixtures/keys/fake-startcom-root-serial | 1 + .../keys/fake-startcom-root-serial.old | 1 + test/fixtures/keys/fake-startcom-root.cnf | 46 + .../incorrect_san_correct_subject-cert.pem | 11 + .../incorrect_san_correct_subject-key.pem | 5 + .../irrelevant_san_correct_subject-cert.pem | 11 + .../irrelevant_san_correct_subject-key.pem | 5 + test/fixtures/keys/rsa_ca.crt | 24 + test/fixtures/keys/rsa_cert.cnf | 22 + test/fixtures/keys/rsa_cert.crt | 24 + test/fixtures/keys/rsa_cert.pfx | Bin 0 -> 2629 bytes test/fixtures/keys/rsa_cert_foafssl_b.cnf | 28 + test/fixtures/keys/rsa_cert_foafssl_b.crt | 24 + .../fixtures/keys/rsa_cert_foafssl_b.exponent | 1 + test/fixtures/keys/rsa_cert_foafssl_b.modulus | 1 + test/fixtures/keys/rsa_private.pem | 27 + test/fixtures/keys/rsa_private_1024.pem | 15 + test/fixtures/keys/rsa_private_2048.pem | 27 + test/fixtures/keys/rsa_private_4096.pem | 51 ++ test/fixtures/keys/rsa_private_b.pem | 27 + test/fixtures/keys/rsa_private_encrypted.pem | 30 + test/fixtures/keys/rsa_private_pkcs8.pem | 28 + test/fixtures/keys/rsa_private_pkcs8_bad.pem | 28 + test/fixtures/keys/rsa_pss_private_2048.pem | 28 + .../rsa_pss_private_2048_sha1_sha1_20.pem | 28 + .../rsa_pss_private_2048_sha256_sha256_16.pem | 29 + .../rsa_pss_private_2048_sha512_sha256_20.pem | 29 + test/fixtures/keys/rsa_pss_public_2048.pem | 9 + .../keys/rsa_pss_public_2048_sha1_sha1_20.pem | 9 + .../rsa_pss_public_2048_sha256_sha256_16.pem | 10 + .../rsa_pss_public_2048_sha512_sha256_20.pem | 10 + test/fixtures/keys/rsa_public.pem | 9 + test/fixtures/keys/rsa_public_1024.pem | 6 + test/fixtures/keys/rsa_public_2048.pem | 9 + test/fixtures/keys/rsa_public_4096.pem | 14 + test/fixtures/keys/rsa_public_b.pem | 9 + ...c_sha1_signature_signedby_rsa_private.sha1 | 2 + ..._signature_signedby_rsa_private_pkcs8.sha1 | 2 + test/fixtures/keys/rsa_spkac.spkac | 1 + test/fixtures/keys/rsa_spkac_invalid.spkac | 1 + .../keys/selfsigned-no-keycertsign/README.md | 2 + .../keys/selfsigned-no-keycertsign/cert.conf | 17 + .../keys/selfsigned-no-keycertsign/cert.pem | 19 + .../https_renew_cert.sh | 13 + .../keys/selfsigned-no-keycertsign/key.pem | 27 + test/fixtures/keys/x25519_private.pem | 3 + test/fixtures/keys/x25519_public.pem | 3 + test/fixtures/keys/x448_private.pem | 4 + test/fixtures/keys/x448_public.pem | 4 + 183 files changed, 4702 insertions(+), 12 deletions(-) create mode 100644 modules/internal/crypto/certificate.js create mode 100644 modules/internal/crypto/diffiehellman.js create mode 100644 modules/internal/crypto/keygen.js create mode 100644 modules/internal/crypto/sig.js create mode 100644 modules/internal/crypto/x509.js create mode 100644 test/fixtures/keys/.gitattributes create mode 100644 test/fixtures/keys/I_AM_THE_WALRUS_sha256_signature_signedby_rsa_private_b.sha256 create mode 100644 test/fixtures/keys/Makefile create mode 100644 test/fixtures/keys/agent1-cert.pem create mode 100644 test/fixtures/keys/agent1-csr.pem create mode 100644 test/fixtures/keys/agent1-key.pem create mode 100644 test/fixtures/keys/agent1.cnf create mode 100644 test/fixtures/keys/agent1.pfx create mode 100644 test/fixtures/keys/agent10-cert.pem create mode 100644 test/fixtures/keys/agent10-csr.pem create mode 100644 test/fixtures/keys/agent10-key.pem create mode 100644 test/fixtures/keys/agent10.cnf create mode 100644 test/fixtures/keys/agent10.pfx create mode 100644 test/fixtures/keys/agent2-cert.pem create mode 100644 test/fixtures/keys/agent2-csr.pem create mode 100644 test/fixtures/keys/agent2-key.pem create mode 100644 test/fixtures/keys/agent2.cnf create mode 100644 test/fixtures/keys/agent3-cert.pem create mode 100644 test/fixtures/keys/agent3-csr.pem create mode 100644 test/fixtures/keys/agent3-key.pem create mode 100644 test/fixtures/keys/agent3.cnf create mode 100644 test/fixtures/keys/agent4-cert.pem create mode 100644 test/fixtures/keys/agent4-csr.pem create mode 100644 test/fixtures/keys/agent4-key.pem create mode 100644 test/fixtures/keys/agent4.cnf create mode 100644 test/fixtures/keys/agent5-cert.pem create mode 100644 test/fixtures/keys/agent5-csr.pem create mode 100644 test/fixtures/keys/agent5-key.pem create mode 100644 test/fixtures/keys/agent5.cnf create mode 100644 test/fixtures/keys/agent6-cert.pem create mode 100644 test/fixtures/keys/agent6-csr.pem create mode 100644 test/fixtures/keys/agent6-key.pem create mode 100644 test/fixtures/keys/agent6.cnf create mode 100644 test/fixtures/keys/agent6.pfx create mode 100644 test/fixtures/keys/agent7-cert.pem create mode 100644 test/fixtures/keys/agent7-csr.pem create mode 100644 test/fixtures/keys/agent7-key.pem create mode 100644 test/fixtures/keys/agent7.cnf create mode 100644 test/fixtures/keys/agent8-cert.pem create mode 100644 test/fixtures/keys/agent8-csr.pem create mode 100644 test/fixtures/keys/agent8-key.pem create mode 100644 test/fixtures/keys/agent8.cnf create mode 100644 test/fixtures/keys/agent9-cert.pem create mode 100644 test/fixtures/keys/agent9-csr.pem create mode 100644 test/fixtures/keys/agent9-key.pem create mode 100644 test/fixtures/keys/agent9.cnf create mode 100644 test/fixtures/keys/ca1-cert.pem create mode 100644 test/fixtures/keys/ca1-cert.srl create mode 100644 test/fixtures/keys/ca1-key.pem create mode 100644 test/fixtures/keys/ca1.cnf create mode 100644 test/fixtures/keys/ca2-cert.pem create mode 100644 test/fixtures/keys/ca2-cert.srl create mode 100644 test/fixtures/keys/ca2-crl.pem create mode 100644 test/fixtures/keys/ca2-database.txt create mode 100644 test/fixtures/keys/ca2-database.txt.attr create mode 100644 test/fixtures/keys/ca2-database.txt.attr.old create mode 100644 test/fixtures/keys/ca2-database.txt.old create mode 100644 test/fixtures/keys/ca2-key.pem create mode 100644 test/fixtures/keys/ca2-serial create mode 100644 test/fixtures/keys/ca2.cnf create mode 100644 test/fixtures/keys/ca3-cert.pem create mode 100644 test/fixtures/keys/ca3-cert.srl create mode 100644 test/fixtures/keys/ca3-csr.pem create mode 100644 test/fixtures/keys/ca3-key.pem create mode 100644 test/fixtures/keys/ca3.cnf create mode 100644 test/fixtures/keys/ca4-cert.pem create mode 100644 test/fixtures/keys/ca4-cert.srl create mode 100644 test/fixtures/keys/ca4-csr.pem create mode 100644 test/fixtures/keys/ca4-key.pem create mode 100644 test/fixtures/keys/ca4.cnf create mode 100644 test/fixtures/keys/ca5-cert.pem create mode 100644 test/fixtures/keys/ca5-cert.srl create mode 100644 test/fixtures/keys/ca5-csr.pem create mode 100644 test/fixtures/keys/ca5-key.pem create mode 100644 test/fixtures/keys/ca5.cnf create mode 100644 test/fixtures/keys/ca6-cert.pem create mode 100644 test/fixtures/keys/ca6-cert.srl create mode 100644 test/fixtures/keys/ca6-csr.pem create mode 100644 test/fixtures/keys/ca6-key.pem create mode 100644 test/fixtures/keys/ca6.cnf create mode 100644 test/fixtures/keys/dh1024.pem create mode 100644 test/fixtures/keys/dh2048.pem create mode 100644 test/fixtures/keys/dh512.pem create mode 100644 test/fixtures/keys/dherror.pem create mode 100644 test/fixtures/keys/dns-cert1.cnf create mode 100644 test/fixtures/keys/dsa1025.pem create mode 100644 test/fixtures/keys/dsa_params.pem create mode 100644 test/fixtures/keys/dsa_private.pem create mode 100644 test/fixtures/keys/dsa_private_1025.pem create mode 100644 test/fixtures/keys/dsa_private_encrypted.pem create mode 100644 test/fixtures/keys/dsa_private_encrypted_1025.pem create mode 100644 test/fixtures/keys/dsa_private_pkcs8.pem create mode 100644 test/fixtures/keys/dsa_public.pem create mode 100644 test/fixtures/keys/dsa_public_1025.pem create mode 100644 test/fixtures/keys/ec-cert.pem create mode 100644 test/fixtures/keys/ec-csr.pem create mode 100644 test/fixtures/keys/ec-key.pem create mode 100644 test/fixtures/keys/ec.cnf create mode 100644 test/fixtures/keys/ec.pfx create mode 100644 test/fixtures/keys/ec10-cert.pem create mode 100644 test/fixtures/keys/ec10-csr.pem create mode 100644 test/fixtures/keys/ec10-key.pem create mode 100644 test/fixtures/keys/ec10.pfx create mode 100644 test/fixtures/keys/ec_p256_private.pem create mode 100644 test/fixtures/keys/ec_p256_public.pem create mode 100644 test/fixtures/keys/ec_p384_private.pem create mode 100644 test/fixtures/keys/ec_p384_public.pem create mode 100644 test/fixtures/keys/ec_p521_private.pem create mode 100644 test/fixtures/keys/ec_p521_public.pem create mode 100644 test/fixtures/keys/ec_secp256k1_private.pem create mode 100644 test/fixtures/keys/ec_secp256k1_public.pem create mode 100644 test/fixtures/keys/ed25519_private.pem create mode 100644 test/fixtures/keys/ed25519_public.pem create mode 100644 test/fixtures/keys/ed448_private.pem create mode 100644 test/fixtures/keys/ed448_public.pem create mode 100644 test/fixtures/keys/fake-cnnic-root-cert.pem create mode 100644 test/fixtures/keys/fake-cnnic-root-cert.srl create mode 100644 test/fixtures/keys/fake-cnnic-root-key.pem create mode 100644 test/fixtures/keys/fake-cnnic-root.cnf create mode 100644 test/fixtures/keys/fake-startcom-root-cert.pem create mode 100644 test/fixtures/keys/fake-startcom-root-database.txt create mode 100644 test/fixtures/keys/fake-startcom-root-database.txt.attr create mode 100644 test/fixtures/keys/fake-startcom-root-database.txt.attr.old create mode 100644 test/fixtures/keys/fake-startcom-root-database.txt.old create mode 100644 test/fixtures/keys/fake-startcom-root-issued-certs/01.pem create mode 100644 test/fixtures/keys/fake-startcom-root-issued-certs/02.pem create mode 100644 test/fixtures/keys/fake-startcom-root-key.pem create mode 100644 test/fixtures/keys/fake-startcom-root-serial create mode 100644 test/fixtures/keys/fake-startcom-root-serial.old create mode 100644 test/fixtures/keys/fake-startcom-root.cnf create mode 100644 test/fixtures/keys/incorrect_san_correct_subject-cert.pem create mode 100644 test/fixtures/keys/incorrect_san_correct_subject-key.pem create mode 100644 test/fixtures/keys/irrelevant_san_correct_subject-cert.pem create mode 100644 test/fixtures/keys/irrelevant_san_correct_subject-key.pem create mode 100644 test/fixtures/keys/rsa_ca.crt create mode 100644 test/fixtures/keys/rsa_cert.cnf create mode 100644 test/fixtures/keys/rsa_cert.crt create mode 100644 test/fixtures/keys/rsa_cert.pfx create mode 100644 test/fixtures/keys/rsa_cert_foafssl_b.cnf create mode 100644 test/fixtures/keys/rsa_cert_foafssl_b.crt create mode 100644 test/fixtures/keys/rsa_cert_foafssl_b.exponent create mode 100644 test/fixtures/keys/rsa_cert_foafssl_b.modulus create mode 100644 test/fixtures/keys/rsa_private.pem create mode 100644 test/fixtures/keys/rsa_private_1024.pem create mode 100644 test/fixtures/keys/rsa_private_2048.pem create mode 100644 test/fixtures/keys/rsa_private_4096.pem create mode 100644 test/fixtures/keys/rsa_private_b.pem create mode 100644 test/fixtures/keys/rsa_private_encrypted.pem create mode 100644 test/fixtures/keys/rsa_private_pkcs8.pem create mode 100644 test/fixtures/keys/rsa_private_pkcs8_bad.pem create mode 100644 test/fixtures/keys/rsa_pss_private_2048.pem create mode 100644 test/fixtures/keys/rsa_pss_private_2048_sha1_sha1_20.pem create mode 100644 test/fixtures/keys/rsa_pss_private_2048_sha256_sha256_16.pem create mode 100644 test/fixtures/keys/rsa_pss_private_2048_sha512_sha256_20.pem create mode 100644 test/fixtures/keys/rsa_pss_public_2048.pem create mode 100644 test/fixtures/keys/rsa_pss_public_2048_sha1_sha1_20.pem create mode 100644 test/fixtures/keys/rsa_pss_public_2048_sha256_sha256_16.pem create mode 100644 test/fixtures/keys/rsa_pss_public_2048_sha512_sha256_20.pem create mode 100644 test/fixtures/keys/rsa_public.pem create mode 100644 test/fixtures/keys/rsa_public_1024.pem create mode 100644 test/fixtures/keys/rsa_public_2048.pem create mode 100644 test/fixtures/keys/rsa_public_4096.pem create mode 100644 test/fixtures/keys/rsa_public_b.pem create mode 100644 test/fixtures/keys/rsa_public_sha1_signature_signedby_rsa_private.sha1 create mode 100644 test/fixtures/keys/rsa_public_sha1_signature_signedby_rsa_private_pkcs8.sha1 create mode 100644 test/fixtures/keys/rsa_spkac.spkac create mode 100644 test/fixtures/keys/rsa_spkac_invalid.spkac create mode 100644 test/fixtures/keys/selfsigned-no-keycertsign/README.md create mode 100644 test/fixtures/keys/selfsigned-no-keycertsign/cert.conf create mode 100644 test/fixtures/keys/selfsigned-no-keycertsign/cert.pem create mode 100644 test/fixtures/keys/selfsigned-no-keycertsign/https_renew_cert.sh create mode 100644 test/fixtures/keys/selfsigned-no-keycertsign/key.pem create mode 100644 test/fixtures/keys/x25519_private.pem create mode 100644 test/fixtures/keys/x25519_public.pem create mode 100644 test/fixtures/keys/x448_private.pem create mode 100644 test/fixtures/keys/x448_public.pem diff --git a/Cargo.lock b/Cargo.lock index f36072e..e05a0a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -222,7 +222,7 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -626,12 +626,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.4.0-alpha" @@ -643,5 +637,4 @@ dependencies = [ "lazy_static", "libc", "url", - "wasi 0.11.0+wasi-snapshot-preview1", ] 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/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/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/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/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/src/internal_module/crypto/lib.rs b/src/internal_module/crypto/lib.rs index 5e41e9b..29d2fc1 100644 --- a/src/internal_module/crypto/lib.rs +++ b/src/internal_module/crypto/lib.rs @@ -499,16 +499,14 @@ pub fn hex_to_u8array(arr: &str) -> Option> { b'0'..=b'9' => h - b'0', b'a'..=b'f' => 10 + h - b'a', b'A'..=b'F' => 10 + h - b'A', - _ => unreachable!() + _ => unreachable!(), } } Some( arr.as_bytes() .chunks(2) - .map(|v| { - (hex_byte_to_u8(v[0]) << 4) + hex_byte_to_u8(v[1]) - }) + .map(|v| (hex_byte_to_u8(v[0]) << 4) + hex_byte_to_u8(v[1])) .collect(), ) } 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 0000000000000000000000000000000000000000..59eb6c7c8831cc21f840333ef9b1fd375997969e GIT binary patch literal 256 zcmV+b0ssCa`<*sOtJZ2_Xso%22DtCR2=2O46@e(%w`g0WQup7IIJApcZ^}TeJ#Wkq zV`aA)8anIRgMvQXTus&?Pku}yJzz4U(i-#tP?~8T>FH!KK*NFD;GH1zfk(ftAO{6W z%J>u4hmn(!5@)Aqyt(dyuVg=+HJ@I6j)2da{z5wKI_T_qi8Ui=mq#ILj*w_vtdv9k zcJ0?Q5I7u7R@C!qbI2e*WnGM@ZEfmL%l^*?HDw*2v41)Oho&$)*G7*F2%F*$iwS;-5}QR8`Y^#%RKg+hC6Rnu7TKAP?aKwGco0 GrD6ik<9l}i literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f830eccead5284d4abe5be09830a64c4718287df GIT binary patch literal 2445 zcmZXVc{CJ^8pdZci)D&z6Ot_18;!Cg24xSEK_pw&$d(ZqVa70ak}b;&m8A@=_>AQv zOW9>l23eZ;5GhNR>pSP(``vTyAMbh3`90@-{&>%G&Vwc);z2+#nuM@`BIIL@VmEn! zFd&_Tz(Pm}twXGiCPCHyk)U)ERQ3=_fq;O+CH9X3M2kW>|Lwqi=!k^CjV4}Y8Jx`k zfk1FT6p7>e!jTjN%+vH+RF_aqvz3(3<+OW^3f7KC8;TOTSc&_IYfDfi9BP_E+)4RZ zBk!=va66Z0cLXfIzP*2Sh&)_L#*K2rJFKf~l$&oW$`P%7_P;~=4XXrOhU?AnY+wQs z)M%b7rmIMOXK12{LfJyn&{n3z;V>iKh0x~tyvi+xhg_FASi`)C7$mV8M^`H3#>s5b6V zriTdFCuq4y#^;4Gm-CjthIV{=al?JN`zv=Er_5%_1-N)KK=?9DqJ~t^*(;Lkm6Zmq8XF_@DPLUi@1~X(iVsNv}^|uTLpwwa_#4fB7JTBP*6>C3DV3?y%yL41{T;MNTGZ2+Ayt0xQ6sDD6{<ubvX3Wj_0KAZhOTj`sl=3bFW`T)yc*OkG!qy;aGR-;B)%|);&}RS zYegFNkP7lDV}j8;)16SznEb1B9!qc0P?ppeVpzn@FR4r4Q8BtD0ppndLZfA?UOoPK zn7?X1wS}xbw-q;+U%I_myf^U~9ACXBvbX=!!hEWVRVi+8=6**oMJ!$#<7b&W1y-{< zVyQohbxMaK+EwY}E*6MiLM zTXLwIuUM=rf78e0eDkKkN8zC$q4C6F8)Ir9(kDRAGLDrMo5}Pqt!qs3qc)LZM3iP6 z5@pxCaFyd}?pF?o&>quCr=a?oHrvM*PYYrQCc((!s0-1LGNc!;dI`RG>cwzd^}7f5 z-oa|V5r`q+Pj3!DO4OO}VF$rOC4U*!dImE-m%4B|C`$frv$=9LCzbWB5`>L~Ob2}6 zFuY$=KMDE1ES{k6%g+gK4%0jk=jFu2_R5jjsh#ZGIn3{&G`BOYA{Rx(irK@@QCN>$ z>h41vNi_bUfJe4lMBd-swM|$lbqms5ekN;?VIcd;<|9U!| z*A&7LA%RDMn&ytL{LH;5FGtjMgr};?UvhHrsD4rw3i~=tQ{~yxZXT@S|(U+`*i6N}rnFDk;m-*`;K&Wq+`Eq*OWF+6;O z%-twjbZ!M$@JEk&;bbQgO#*HIPeIU0phXA?v~Y;O91aW0^`E-n1cDC5l0#Vh|1Dx# zFfG8S3x@sOB8TTuE|>_dw6L*o+-ABX&m|${PB=lU1=p(c!MEqz4qfh+&Be>SGZlCL8PKF>}D@Z?ioh|-!!SkMWbVKre3AO$x!)M&n;W4&$kLO0PB^Gswbb3gtS}&DBh%`Dwnz6Zh6mdXM5Yy4X<> zOB-f9lW_EI2>DXyjdg$=r(Omb08iRo(Mb$A_zyb|$R@Xd8ql`$Rxr zQ1KZ+fPYDHqk<*z=Eu?1u~@UZ7`^F073Mi%s*hJMV%u|0W_#~Bf5T+rC&Z-xtUYX>m{MRx!>H|glrlTYd~rLL>^>!+!O{tQxPTt zgG><|CG;f2>RG2mHce>swrzDYV_XFnpfS0Mq>R^SVyF&u(h57-$Z8Yj%w>W_6g*oY{-*6_DDz6 zK=id9?bMA-dJm>uW=lOqnKKu|t;2jDeB_ar1M}1i=L^I@&9!LYs4gwcZ z1%dM~a5f?cl=V+UogD;e}!zf9vh zxzW&H3q(wb2wxXOyDo)OA+?1$`Bu08rJ^!;#=fZ}buOD&pqZAOT0w8&>e#PBRu040 zbCKAbXIW(B8_Yg<%Y38Ljm~981y19V?38|XYe`2G7h1D8SIkkj(K~p0>P86Z{E8BX z&U^y|%dI{9{we`n6XNZ(98?VIHJ!cE3wUHAMXG@E8Y(9{GDd&r^CorJ@k~7!rmz-Y zxgAd)ex3~d@La>s?1lPI%u#3Q-GsS6B`?sLC>{UXiM-ENv5Bpw8fxy{6J3RV01rmh z4c|2b7pdYZ{4yKUhl3|cSWDEpC4{%VGa5V~CIg@xmiP&fChw2jpqD^gXY207G^{MZN1h-2k13OT@Hb zKes23oEI4Fn(4w!$j?uR$A*I&;l~-IOxa*&|Fj;DuO!`3&g7uVJxC=_F2R1+qpYDt zFY7!f++5F236)NZy>`s5YDgszsO^yYYK+2&W{`80fDD}ZDQ8Z8_~)vJ1LmLfz2c|`P}@1OKULZmAts-n{KmZNh3A`Y-;^_QSYK3pjIRGA|{d;DtT@lZCv%flfRYfG*&kA)wJE%tnw9R;_zA0 z0)AR^^EUA)kRu9NVkKgdmMyp+~2DsgV09i7*JTDEN$iT$X9#f9fuceA@k9aYY`a!T7Le1n8k?4I?iRZ8G zYps;+Kgk%C3_8U|P%T<8X~~uCFmL&EeQm~3d(Oi~;~>{gSa_#`@Pg9XZ0KI@9jx7` ztEq0;Jt>P!Z69Upb_2Uv(GZ6dRDe6fSzK`->-2J|%vmUZ&iu!x@jm0%o9Y^Dkj$t9 z2|Ke-71iv%v_Y|GueS!)^+Q9qY9*<;U*dLyjn1--);wO%v_$(u_hfkp&>2@-=|;va z>`J=7r^6wST4|nCW9gzSKf}W=$@B)5p3P6n^!xh5%9mQG@pjbBcUj7x#Pld>3FeET z4>_DMiGup(cvT7ehu=6}`k`XvU!;tHIe3KXSh!RB-0ML=8b(S4GBaZ>?5hIRkBQTC zT}2SqH0Ju58z=KvuLb=FeKKyScEqo|ETzflrbTe6*U@5l2{QPp8CZ$05gxIxBReyF zt>+6{%|7+;(y@0^vO}Yb48FHl2RE&BH~w8mr)K~L=FO4Hz@{1onMI5g)O+P|HPjxL7&a=aEZ;(e*Pw+mh9 zM4?gP&RD`a#PNV`6lL!!F>K+!lDn53OT_Q0wGSRCx<#obe5FbpxXfgG|5&CX_k-M0LxwKIMt}rj zFsYlY)^tE3xD)}p>Qu2Jkwa5ici(1hIG_vr$L40*j4?&DE~sus`1f%x7p@4!k?yk` zQKnb^$neQg>_(^nxEdUyR``~B7n)b)6LKnP^AO&@fcOt z=D@d%C3vyzfCEXOkFU%x_&%3fd-fDPSx#^*@LNDkya)Sc`MjvmA8dS*&tV789Hdwr z5C49dC&$8KG#70je-bH2o1ng*`Np@nxqd#S=eNRMHm>qsLyMHg!KOvW;IBCyTFpN_(g zPMi=y6vzJ~BH2L{zo>#JmM`%9#Y|I!|Dz?eK#Gg7@B-%kzpd<3)*CeJhFkt^D;MW4 z71JA|_Kf?r1~h*RAJ_t7^8T9R79Y?7cjdo4?^s!Cf;G^O6a^YVgfd&a)PyaKl6hXg z{#=;B=Gv*%?^;_x#N>{m_?hTxs@^c;hcN3i`w`s$z44ls6DBpv&uQGpHRf}NH1?~m zWUo_KVOSi9ym}M6qEeOPW~2~plH}BA(x%WxeKlgq$l`YSS5^;q^m0i~Lbz zmFZH9NQ1@dk_`TA^Nn&%+ghf<0OLfd}U6J)7^v#o`{ z|5$IJz6E|u3^7AikT(mE`T}M>TaQdmqGA$G#i^bvEj|E77H^gNE$Fj7du;Qi86U43 zJH1;SsHBec8-22BM2;MAZ*=Km*8fy|-l`d$#STPg9bN}`t*)jL3p>u@E)$dM2R&mPT2$$dbh}U5pxP;^g zC(*eQI7jHwj5qZUTVYsV0lr3k*pV^W%Y1EYvz_wsW4>Jkuuk+c=@3x9cH@oz`Quh* zlr+d5f10BhTz4-zu~^kn8io-3?^j=Dm1d)2k=3U))d^?H;Cc7c9QLu2^(dNE9wCk3 zM_i`9B+fud!FdTl#Ufg_1K_T-K{qYU>>%$=I#u^VmZ?Bh^ZlnbTFKA;{_cMP&@8o{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1f1d827dc9336527049a6d1722b27d2016ae4476 GIT binary patch literal 3029 zcmZXWXEYm(8plOb#EiXSmLgWvo~3phG_*$4CN{B0DT3Ox6}w7}s#T?CX+^bowRWvq zsTgVOJ*(Gy&b{}&=iCp^dCvL$&-4HEeE7jIG$Z7ID=-XA4h0DM*x>OI9e@&0jG;*c zVrb$naWo7=74=UgTaS*fW1ff*|u zid4=i4F{{hOEon6Z3OzXMa$*S(-$aAjMJniy;3uY?2lnWBNuHJN?7-*$gb3M=1?bl zb4KD1*Uhdhc`k*x7W`XkL=G9dU60b9);yp2Q&4`^>Jy#{^p#*BNm-fq*cjB>=X-+B zwSG0#w%POhF5@$`&w0TXPW5RIL)JMWDfY@nY!f;;ZGn5n;LLiIhoL#{)(0fmdSSH(5zSsH3NFy%@+KywJ z>+%}=6Oyt8o1eW|bG0NV`Baex*7lvp7Q9K8W{bVqBzmS824zf}uV?Q*e%02b2HHz= zblhZp4Lg1;m^I%W%UP1DuxItfEOaMIS}IcSP1{>-lLJDEOK=&zasTP&)sQ1&%m=8L z;nh!fKOo%svT7qYt5k5-5Ku|$c+&H`RQV^ds9&i!!Sn4vhk6w|KvILr#D4XsJ_n=*9t+zq#tj+m_*F&ms{EyvD*D zojeM%iUIAW=Mc2smyCkRB&3AaZbN$`PMzb%_yrdOt;y1A=Zw=)FiDs;)2$!^;_E?TUepDjya+|Um{YR>^wARC zP18~)y?4gnB_LV!^{Gkvcc8+dXr^JGt-0p{(zT7U11`{O*+Ej$$Br&MsvEE(ay#i8 zYi7tCvmAe^WT*a^9G15-m_jWPebe(j(ThmuiL6iGZvDuZ+^w5K%2HiS4I&uWLf{Gh zrIeW+4+rNB#RKDy=2y8p6x0_Tv9l3o+dEOroVP{J4U@7+Z_#_2a$APB3a4#{AD@eN zn?l`}HGeGkN#*gO(`cDFWw)6=8y;i*mfCyS?V7r#8WFq}#Kxq?tH}D&QKsV`PQ4PK zve<;%tq&MqNjgX%Gb?>4&1O_g98tQ->5;hU%Br|;#uwiCJFnLWYd^lSD!jaHC1dAp ze!49_mtZ<2sWv9bY^?w6P#&_)$(75l(QT;iXJ+)?$mG$VwUntk3rko0wd7ltUqSVf zMT~K$W3RQZN|pAfBt&L;4p~9rAK?k+ho5eO{3@#(xIEJUCt+FqwYZ;{ zIIj%?>PsgA#46Ca$>a`QAQ~2%Wc^tHNOlm>o$1rymaq-#J%@wT!A2^jxT2?;i@{5z)`|s zIo0l~NU%^U0@X0yJG!n>1J0^IP)o&sjB)hg?@fn6)n&Y3r9_G#i_xas^jfZ<3;p`a z%k`lEzCqS!5uk;uA0sN>+dr`vNN@!HQWcuzP0E69 z2{hqO&^oCFNUoddzyy4Y;cEYsu$vM_ech@pgFTL3JO3AeYcsk}`FY|OS)8H~KF{5Undqd!;*Y$}!jIZJQr#dc6voMqyax--rh$tXRsxJ03$r zAOVU#?m8le?@HJT++>?COu$%k|M33ukVFQ87<5XAX7aE;2_L;iL01WX?wZq3c=r}Q zwl1yN1R=+>mu{;myGcX+mY7IaOqP(%6Dtd&21M0A&HNKHr71-Iv+b6Pi3Q<^;vk>W z=uQ}K3_(PfKXM_mr-J9V3>aJ1;CY%WKN?E9$GZhC`9v~Gc?uf1TCSN!^NW>~GkPa<#S zBeX2}Ak%P^cA9|DK4na?!tbr5f_iFssUJ~Dee&#?#VWg zAFudWWiEVL z+R9XPG`iHH44f;WoK&Q=a3%yJs$SWklG(w-dC#FkhNJDuBvVOg<~^(Va{X#IrQxFKIgO@nL|Vh>S9hWn2fb9>X^m1%Gm`6AzK z8GTzZsYXO5jhmfO@Bs8u;O$v)zLA$2)@;wy)jF2n!^4361vt^GUVie#&1FJX7{eD^ zxEybHA!Dt=ub7R zZ|bTlFd57a0G)%<3F(_hh0%$?gxT3ffv!mT9*n-5scJ+9Lu*1H9zR2rm83Xb+%NYWZ& z>I@6sGSXw)@_UrQbfR=J0wI2u=2qyMQ6c^lRSFof`)#o)wPSauif>{EdFwvg=fe+Xu_vs!SxTjmFN;gMR-#=&J#48H(yg8 z6!r)fYe?EN?%=rYK{ncBraOO3AjLvCa=6}s*Of)kcpWZ58DZG9|9&-yRfY}7GAn2F zBlW@9loBSdCbcHiDfI5UcQ6^401QNNRh<3`IoDM(Aj^kwij&EX@21Yb$?32hs80v@ TNf{s&y?6%NjQ51~@9+K>A5NA- literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..44ce3992f4b7933ea13facd4172a6cb56bd29947 GIT binary patch literal 565 zcmY*V%WlIU5bT++*dM5shbuJ)q+02v7b{YaQG{^3wTUpeW}PN~U%-#VaDd&J-JP8y z>@!i~kL)$UiJfhG7kDEyPFdrCL&z|8gbsf_J~Bn2$id@qSg$rIE9Lz;JK-F5{;*Ox zQl{#atFaVDHW&#!v=*)}E_kMxW|F8=MNhcwHZUI`YdhPtG>w^G4#u+E+iLf`$%t~e zZiOGn!qBHhr{WnOR$l1x`|tCKxOh3>Rq5nxo6#!qP#IjUrPgRFJV@vMY*#Ldy~!EO z6lh0I=rm~c9Y%XHO?mK*!-v#^S?|py(x;wFPkF!AtMxRT@h* dc2V@#Ar`$S@iHFx?7G%l8HU)XNF|EWUSCE2N^E zqlm9zi)PrFrDgu-_wVz4{>wjFDxul*o>u5=x#&xe_b#tH=*NE9H(N`VbDO$t zU)jv+%4&OQL0gpUq{KIlWw_dphZbTyS?~R#@7Fh*zAxQC|Hl zAF~#%bC|4UrM@z!T-GqbxH$3A*_}&2UG;G)5r4A&d6iA-YH=Rsjwy`)R(ve{ch*YX z@uO$&>tmt*UaTkAsF>+wzc_Jv!VO~%h1(0`m^SYVkY4s-#-s56DQDh0E&h_g!_t3p zmg7z5->ljy$9N3`^*%^1dSCRZ?WO21hW1Z#7eX1^b4@1t3I+Y>{?k_^uepEQnzRnL zYwvzCDl^;WdP>BC z$NLid)#(ODF8Wko*t&n;`=+hB6%UzyG}uiyo?5pcJ|jQ8dajhif8&IeVK4X8f14h& zss9wKW}JIWRKyAC*LpCDD3GHGAuD@Vq`)}cncc8urz)&XnbeT_?nFymIS#N znHDr|F=*U~nJxqT1K+EEtnCM-OP0n7F8cj_i+H`RL@a)}t$IP&pQkqF%NERVI-(d` zwof{L-;GD_J!M|z)!VvUQmdLF9@sZ!zGg1t`&qA<-LKwGQrI^!E2R9S$;3E|5TmtK z_KnrwKc!v$@b~hdIa(aW~ zOAHJR6b(4pShe|>nWR`5SVT$!C-$G4bz|vtPOWEgrw&Km6b|NK;aLCIt64EJ#suV9 E0DuO+Q2+n{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..59448e550d4aed2e9692794ca5d762faa97a685d GIT binary patch literal 2198 zcmV;H2x<2)f(ViV0Ru3C2v`OQDuzgg_YDCD0ic2iNd$rjMKFR0K`?>`HwFnRhDe6@ z4FLxRpn?Y|FoFjp0s#Opf(IQ22`Yw2hW8Bt2LUh~1_~;MNQUS9cTbvm1VrU0>TW6`LThKfR))q}W9CdVQ zN{^@cDbQ4LdkGmjzSTVNQiqmWe+HhL0;FHBo*kk64z zawAbp!bH;mNHuUq9v@^NBjksAn9P`jf`I$F$#(iQGDA{U#N#fhWH%>Qww>S0RQkZ0 zkk%C~tI1m49606Cf53#%zsWbT7LqxSS97A}d=Wh2D zrloD(rBs(E72AbhFh%BzT8%E@CmPdjtytI9qNK_y0mh)Yt)4TV%9D4CIM+)i(j z-#I=4!f>t$ zhtV*JzDV&P8=Xx1g|aE0?@E{-KpM`Y&JDVA4v0uAvkM26XxBzXAN*Q5KPvFWN5V0? zh45JBA-%dQ6(s*16W~QbmG|6Kg9Z}Bwy~>=CI|vh0q#8C19Ud0nj;rj=~;#e@{a9DY12Tj3fSZq#dwFT~t!%!~_&zt;uRddfl zSBZICT_W(R@$%>a4|#^Ub!~V2ra>fjis_Xc047n8p+(Xh_5|b1@bIgPF7~7J*|%#GBy#B2_$I|mrC^l z<{4}IhgvZU8CAN7SbcB_b*}XJ2Q0`r)7Oa4Ei!XQp&2x$2Zi_RgFQ0?gtboi?>MrL7tOkog#OObtK}4Mb1+IT&lgvKg zkkuP_pWQd8&l|qJDb05k_oAjVHPm6si=&k1m^17L@{J7siVAeZ*N&@Ix6|_Twg&RY zuf|9CCLb39R+CC_m4%!d$7GVrc_P!>Uz@c~R2gC_xOEt==%#21en%GTsO#ihVy;|sx@E&l+2=_`rf$hz z8ttj<(qCG*jT%9 z?Uc!HYU2u1f$U~4R_O8#z*3Tz%eTfm>@%BdpQjbZ_Z6rZWGbp(Y^+_C0;wq&)L(hd zd~;%N(6a(8XMc*qk@`67v@O8qoOd?R>2792GhRpP5m``S6Fi#n(mke;;8+E3<`pEF zX~(?&cc9RwS9WDADKC7B(PHy;ZVKVTAeVKsS1qCD+eeN*EBD|tMuyY_hMdmS7$TGM zkKP9O!s}m$u&5=pi*ZQaE}Dp|kQY&v5+%@ImWFf|QA^YhYrZ%tF=MSW0Ph02?=L_x z|D2Im{d{P1Ri)Sw^(@X$%^${D`|GIFjo%fM>7n9~Pi-$j94i}25obBxV$y)#q*g=Y z@?foXbpCmVV8M8ZV1dPt!Y@vy_wnv6_Ru*#n@UScx_ z<;`1*yO+|mSI~Ttx)K!uB1d8q(gx;f#wFuV=;5Ogdlxyf(N{uU)dj`bmJOl*6QbE| z8|wqi+bAPZ8QkHc2ZS`3-z4QTi>(2qB~J|J23_6~F$qvaHC}d1WY@G#y@5XF12%@7 zvkcK~%cv(_IAktkuj3#s^J9M3gTeLlgKGSZXK{kBp-iHHexMohr|#Z13K$p_kDQGf z9__VH2x=e)y*l_*k;L_@0IEjMFoFRB1_>&LNQU zK)=O9G_{fuJZWeUKSr$wJMCK#pGa5UhX=RmHx9~cIZUZBB`_lf2`Yw2hW8Bt2^BFG z1Qe!OrCNQtr4d@PGE7T3B6%a*rSLE@Fd;Ar1_dh)0|FWa00a~XG=N-tx>9oz11v=+Lf5GRmEzlR>df7?bTMbNbSA8 z7(pqjW<^mV#`T?Z@BQvM_mB6y=RD6j&!0aymRSQpO9#g?pJQN>ir07ebsi`Z%_8Ss(%G2Dq>ZE{a&48dFxHQlu}99mks{ew}wdAxmn z|95hlt0+aYQ0inl3HbbT+bn=`xU0b}K6=}|0X&+w+}$bZ7sp%z2z9D-Kv0T#Adj&Jlh%z_2HR1!AtR#|K81a;V{_GzS(GTO)& z8)9hIOwXb8f*v4reUjT2AoeD*|-#Pd}WKG z2QLwa`jnl(v3{Um3I_aN$ zw!Wl8ZcTxaZ{K~gs@c$pA%N;)2-^~)UdoI`iA&!bd#Wq`hK&Z6o8_ieB7VrdjmtEv-_|lzw1Oj9 z3wH?L-?w&9eyCnQhAq5(MHFl;-Xttve6t?ZA26RwYc`bpkzYz!!RWyzL9PV~P;hEs zV}cABYpX%4UH(XL`K;TmKX>`QP;)BUnI+y*sVhg~qH1d^S`0TG^vFw_qY2@+FmwH7 zLPONtAi8z^mG)O-B!BsdzdO%O1+lhm5gf;ugGA><E-&@hL3)BP>-C)c@!l$<~R%*%Rg(ZS$IY~Xtf{tdeb8?JQvg3gyk>nNq>Lk zyKz6@_jUqW8O+icMe+%q7N-0HKQc;B5cKEy6%kYSWz%mN{XqQuB2-HwXL}z<>C4Hz zLAoZpJ_%aA8>Kf}{~?Ioz|QVkBP!9Ed1d@X_;OF0u=`e~zqt)NV4&)D=*H^l?q$&Q z)CnTx=d6ojOC^s15z5Y{ZzI;4U>|QwUZ$?u9XdC+{&iY-MA~Hg;}zZE+W+g$soij9 zIndYq`=8(+58x#p-*08?>+(`Ynn{_Lrd}()z29M+9Y6T?zKA@%mLHXEX&NTE6?OA{ z*cXnGTRCJ^6_!ZMjmC%Y`B~R@YkdjREx>#Jh2k!d?5ggJSJiL}=M_y%iHnj9{=5hA zj=07?Tq22tbXss`%`}OW3LGmd`hhg#_(RkW=9AeS8w3dhtOsEpmsRDw-WankIp^e5 zS)A=aE=Ci^s#Z4P6xbxVss6MvIukh+gO@ zu1&S6{7CtASlZ96M2ZkZ3LvdZXqU-Th#3@MD4aXQSDZ$8rh|(EEKg6!@$pq2Kg(g9 ze6KnaBfg0*92>OE>)DfXbKUA}!4()4?J2cV^nt>#&6ItL+zC<7x~IjQaY=EsZzIq; zvx|?gPbts-Hq;Cq_bKl+?)v#Y!SST_v(da>9Wn`yrPuf$knmV~1Q1Jq^(>Y;I|&As z|M0|23pjJ^*BM}I|F0G;8>^1U-CtRMs|CaYyX%^NyXZol8LrDNrN8KmW728Yv^Imy zWoL^37&j^(=w)Z$ezHP@-CPe#oOUsn&)FE04LtO4Mxi?cwk9VAYqwWhP(=Fbd@cSL z^E^dr5E?$9?&`qB%7P;UKSW-Hp0mb4b5M~*Tp?&-U-!f4LrsCA=B zyw`_wQ`eLo0H~yDpY&W|^>=ytZ(gpHL3Q?|lb+;H^07t-uLN7hB0V|uk=nh*W?h}u z2Ggk$`eOJ&v)pqV5A^~QY$&pyZ!x`Xw}M@0+RnRB!wN9Kx{Q-WFTbn7w}N|ICFQN> zMgjoa;I~5>cht#J;bDaK@n%WWnM9CE6X6wb}KBSFys-Ur%sw+D{GRnmXNmqR7opI@o8`t;$hiI8F-Z2#WcKte?|8( z4b!f9AQr1~C9Zw=r=pNtlHN-(=tQvnU>V&PjF^b7j0(RGJP7mrTZq!jZQ0Uml}es5Z6_vrF%)3#O84Av1Y&;orH6 zHj>r*W^Avj4XB&|o*q3daoq*?eJHNSIxTn|U?FUCVojs6B`0B|yEATcd+IOeId>8- zljUA@*Q`WZbZXZfb5bPdoA)2yD|=D@N$3uhin2qNkOT(WAiQifmuj#Dn)tvfa6GCd zQ`0+RNjTr2CQiz!`x;qbU3E+ui9tMiP${(Nezk~t&Z?MKw)WZXBYisX^h_+K%*m@; z%=Vs*3r_K96mL4|%JrT^@s~htAmeJDr;8L1xW?u9TSY<}-=NE`^LqSI)-)SSbpM!lsZz8i@L#lQv!Ho!a%4Svx!BZ-3 zP{DM_y5N9g5MN_d5tdvXa6ZgpEOnc@G;8i{a;18ssOxw#oL&E;gROeFd<_oTyZl_|6QI(V5}p-Xc>PR;OTyQ%V8LRvjsMr(Og^T(}_ZTnu5|#lybpxZF2vI_^r^ zy01Ed!c!45F}u6{qFu4esC1>7iBO`kEKVC0q5zvoYtç¦mxd4Eíc(Ü9É³È´Û ‘kÍíLóíîÐ2í¦+ÇÜqôSiÛË2:’Ì­[dfj/°´¢5Пñ9¾O<ãöé8À±'. O¤ŽeL—8íÉî“2ÿtàÓŸ/¦¥3æK’Œçä ,øЊÞl…úZÚ…ßt¬[Œ¿4œf¦™#µ* ñYí êË餡w/²«Ä2ß—fꢅ´X®ÏhÆaYtç¦mxd4Eíc(Ü9É³È´Û ‘kÍíLóíîÐ2í¦+ÇÜqôSiÛË2:’Ì­[dfj/°´¢5Пñ9¾O<ãöé8À±'. O¤ŽeL—8íÉî“2ÿtàÓŸ/¦¥3æK’Œçä ,øЊÞl…úZÚ…ßt¬[Œ¿4œf¦™#µ* ñYí êË餡w/²«Ä2ß—fꢅ´X®ÏhÆa Date: Mon, 16 Jan 2023 21:00:45 +0800 Subject: [PATCH 19/25] remove unnecessary finalizer --- src/internal_module/crypto/mod.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/internal_module/crypto/mod.rs b/src/internal_module/crypto/mod.rs index 4baf83a..2a5ebc6 100644 --- a/src/internal_module/crypto/mod.rs +++ b/src/internal_module/crypto/mod.rs @@ -236,10 +236,6 @@ impl JsClassDef for JsHash { _ => Err(JsValue::UnDefined), } } - - fn finalizer(data: &mut Self::RefType, _event_loop: Option<&mut EventLoop>) { - std::mem::drop(data) - } } struct JsHmac { @@ -307,10 +303,6 @@ impl JsClassDef for JsHmac { _ => Err(JsValue::UnDefined), } } - - fn finalizer(data: &mut Self::RefType, _event_loop: Option<&mut EventLoop>) { - std::mem::drop(data) - } } enum JsCipher { @@ -458,10 +450,6 @@ impl JsClassDef for JsCipher { Err(JsValue::UnDefined) } } - - fn finalizer(data: &mut Self::RefType, _event_loop: Option<&mut EventLoop>) { - std::mem::drop(data) - } } struct Crypto; From 16a0369f124e4b5753dd50e43e34042ba769874e Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Mon, 16 Jan 2023 21:46:18 +0800 Subject: [PATCH 20/25] impl: SecretKey --- modules/crypto.js | 12 +++++------ modules/internal/crypto/cipher.js | 4 ++++ modules/internal/crypto/hash.js | 3 +++ modules/internal/crypto/hkdf.js | 6 +++--- modules/internal/crypto/keys.js | 33 +++++++++++++++++++++++++++---- test/crypto/test-crypto-hkdf.js | 7 +++---- test/crypto/test-crypto-hmac.js | 4 ++-- 7 files changed, 50 insertions(+), 19 deletions(-) diff --git a/modules/crypto.js b/modules/crypto.js index 8f770b8..279891f 100644 --- a/modules/crypto.js +++ b/modules/crypto.js @@ -40,13 +40,13 @@ import { hkdf, hkdfSync } from "./internal/crypto/hkdf"; generateKeyPair, generateKeyPairSync, generateKeySync, -} from "./internal/crypto/keygen"; +} from "./internal/crypto/keygen";*/ import { createPrivateKey, createPublicKey, createSecretKey, KeyObject, -} from "./internal/crypto/keys"; +} from "./internal/crypto/keys";/* import { DiffieHellman, diffieHellman, @@ -166,10 +166,10 @@ export default { createDiffieHellmanGroup, createECDH,*/ createHash, - createHmac,/* + createHmac, createPrivateKey, createPublicKey, - createSecretKey, + createSecretKey,/* createSign, createVerify,*/ Decipheriv,/* @@ -231,10 +231,10 @@ export { createDiffieHellmanGroup, createECDH,*/ createHash, - createHmac,/* + createHmac, createPrivateKey, createPublicKey, - createSecretKey, + createSecretKey,/* createSign, createVerify,*/ Decipheriv,/* diff --git a/modules/internal/crypto/cipher.js b/modules/internal/crypto/cipher.js index ca8e375..378656c 100644 --- a/modules/internal/crypto/cipher.js +++ b/modules/internal/crypto/cipher.js @@ -30,6 +30,7 @@ import { } from '../validators'; import { + isKeyObject, preparePrivateKey, preparePublicOrPrivateKey, prepareSecretKey, @@ -125,6 +126,9 @@ 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(); diff --git a/modules/internal/crypto/hash.js b/modules/internal/crypto/hash.js index f572244..972c56b 100644 --- a/modules/internal/crypto/hash.js +++ b/modules/internal/crypto/hash.js @@ -133,6 +133,9 @@ function Hmac(hmac, key, options) { } 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 diff --git a/modules/internal/crypto/hkdf.js b/modules/internal/crypto/hkdf.js index 9f3683c..ef8e503 100644 --- a/modules/internal/crypto/hkdf.js +++ b/modules/internal/crypto/hkdf.js @@ -47,7 +47,7 @@ import { hkdf_sync } from "_node:crypto"; const validateParameters = hideStackFrames((hash, key, salt, info, length) => { validateString(hash, 'digest'); - key = prepareKey(key); + key = prepareKey(key).export(); salt = validateByteSource(salt, 'salt'); info = validateByteSource(info, 'info'); @@ -83,7 +83,7 @@ function prepareKey(key) { return key; if (isAnyArrayBuffer(key)) - return getArrayBufferOrView(key); + return createSecretKey(key); key = toBuf(key); @@ -101,7 +101,7 @@ function prepareKey(key) { key); } - return getArrayBufferOrView(key); + return createSecretKey(key); } function hkdf(hash, key, salt, info, length, callback) { diff --git a/modules/internal/crypto/keys.js b/modules/internal/crypto/keys.js index abc78a3..8cadf03 100644 --- a/modules/internal/crypto/keys.js +++ b/modules/internal/crypto/keys.js @@ -1,8 +1,33 @@ // Copyright Joyent and Node contributors. All rights reserved. MIT license. 'use strict'; -class KeyObjectHandle { - // TODO +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"); @@ -584,8 +609,8 @@ function prepareSecretKey(key, encoding, bufferOnly = false) { function createSecretKey(key, encoding) { key = prepareSecretKey(key, encoding, true); - const handle = new KeyObjectHandle(); - handle.init(kKeyTypeSecret, key); + const handle = new SecretKeyHandle(key); + // handle.init(kKeyTypeSecret, key); return new SecretKeyObject(handle); } diff --git a/test/crypto/test-crypto-hkdf.js b/test/crypto/test-crypto-hkdf.js index 37ed84f..f9ee5d2 100644 --- a/test/crypto/test-crypto-hkdf.js +++ b/test/crypto/test-crypto-hkdf.js @@ -10,7 +10,7 @@ if (!common.hasCrypto) import { kMaxLength } from 'buffer'; import assert from 'assert'; import { - /*createSecretKey,*/ + createSecretKey, hkdf, hkdfSync, getHashes @@ -157,8 +157,7 @@ algorithms.forEach(([ hash, secret, salt, info, length ]) => { })); } - // unimplemented now - /*{ + { const key_secret = createSecretKey(Buffer.from(secret)); const buf_salt = Buffer.from(salt); const buf_info = Buffer.from(info); @@ -168,7 +167,7 @@ algorithms.forEach(([ hash, secret, salt, info, length ]) => { common.mustSucceed((asyncResult) => { assert.deepStrictEqual(syncResult, asyncResult); })); - }*/ + } { const ta_secret = new Uint8Array(Buffer.from(secret)); diff --git a/test/crypto/test-crypto-hmac.js b/test/crypto/test-crypto-hmac.js index 9cf0c17..3672303 100644 --- a/test/crypto/test-crypto-hmac.js +++ b/test/crypto/test-crypto-hmac.js @@ -460,11 +460,11 @@ assert.strictEqual( /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(), ); -}*/ +} From 920f2c7283870c46c870564b3f78ef98e95f5264 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Thu, 18 Jan 2024 23:48:44 +0800 Subject: [PATCH 21/25] fix: fit new runtime api --- tests/test-crypto.rs | 76 ++++++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/tests/test-crypto.rs b/tests/test-crypto.rs index 5c54d22..4d57bc2 100644 --- a/tests/test-crypto.rs +++ b/tests/test-crypto.rs @@ -5,31 +5,59 @@ use wasmedge_quickjs::*; fn test_js_file(file_path: &str) { use wasmedge_quickjs as q; - let mut rt = q::Runtime::new(); - rt.run_with_context(|ctx| { - let code = std::fs::read_to_string(&file_path); - match code { - Ok(code) => { - 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"); + + env_logger::builder() + // .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); + + let tokio_rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + tokio_rt.block_on(async { + let mut rt = q::Runtime::new(); + let file_path = file_path.to_string(); + rt.async_run_with_context(Box::new(move |ctx| { + let code = std::fs::read_to_string(&file_path); + match code { + Ok(code) => { + 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"); + } } - } - ctx.js_loop().unwrap(); - if let JsValue::Function(func) = ctx.get_global().get("_onExit") { - func.call(&[]); - } - ctx.js_loop().unwrap(); - if let JsValue::Function(func) = ctx.get_global().get("commonExitCheck") { - func.call(&[]); - } - ctx.js_loop().unwrap(); - if let JsValue::Bool(false) = ctx.get_global().get("assertPass") { - assert!(false, "js assert 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; }); } From 73e7cab8aacf84e967a6f7a2cb486c7f2c632328 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Wed, 24 Jan 2024 23:25:10 +0800 Subject: [PATCH 22/25] fix: add assertPass check --- tests/test-path.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test-path.rs b/tests/test-path.rs index fde7f70..c4cd20a 100644 --- a/tests/test-path.rs +++ b/tests/test-path.rs @@ -14,6 +14,9 @@ fn test_js_file(file_path: &str) { Ok(code) => { ctx.put_args(vec![file_path.clone()]); ctx.eval_module_str(code, &file_path); + if let JsValue::Bool(false) = ctx.get_global().get("assertPass") { + assert!(false, "js assert fail"); + } } Err(e) => { eprintln!("{}", e.to_string()); From 906f4c8da00e34d4ef6850e4059f6f7ecd006c7d Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Thu, 25 Jan 2024 00:33:11 +0800 Subject: [PATCH 23/25] refactor: separate wasi-crypto binding as a crate --- Cargo.toml | 3 +- src/internal_module/crypto/lib.rs | 769 ------ src/internal_module/crypto/mod.rs | 130 +- src/internal_module/crypto/raw.rs | 3860 ----------------------------- 4 files changed, 115 insertions(+), 4647 deletions(-) delete mode 100644 src/internal_module/crypto/lib.rs delete mode 100644 src/internal_module/crypto/raw.rs diff --git a/Cargo.toml b/Cargo.toml index 3b50daf..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,4 +39,4 @@ img = ["image", "imageproc"] tensorflow = ["img"] wasi_nn = ["img"] cjs = [] -nodejs_crypto = [] +nodejs_crypto = ["crypto-wasi"] diff --git a/src/internal_module/crypto/lib.rs b/src/internal_module/crypto/lib.rs deleted file mode 100644 index 29d2fc1..0000000 --- a/src/internal_module/crypto/lib.rs +++ /dev/null @@ -1,769 +0,0 @@ -use super::raw; - -pub type CryptoErrno = raw::CryptoErrno; - -const NONE_OPTS: raw::OptOptions = raw::OptOptions { - tag: raw::OPT_OPTIONS_U_NONE.raw(), - u: raw::OptOptionsUnion { none: () }, -}; - -const NONE_KEY: raw::OptSymmetricKey = raw::OptSymmetricKey { - tag: raw::OPT_SYMMETRIC_KEY_U_NONE.raw(), - u: raw::OptSymmetricKeyUnion { none: () }, -}; - -pub struct Hmac { - handle: raw::SymmetricState, -} - -impl Hmac { - pub fn create(alg: &str, key: T) -> Result - where - T: AsRef<[u8]>, - { - let alg = match alg { - "sha256" | "SHA256" | "HMAC/SHA-256" => "HMAC/SHA-256", - "sha512" | "SHA512" | "HMAC/SHA-512" => "HMAC/SHA-512", - _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), - }; - let handle = { - let key = key.as_ref(); - unsafe { - let key = raw::symmetric_key_import(alg, key.as_ptr(), key.len())?; - let opt = raw::OptSymmetricKey { - tag: raw::OPT_SYMMETRIC_KEY_U_SOME.raw(), - u: raw::OptSymmetricKeyUnion { some: key }, - }; - let state = raw::symmetric_state_open(alg, opt, NONE_OPTS)?; - raw::symmetric_key_close(key)?; - state - } - }; - Ok(Self { handle }) - } - - pub fn update(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { - let data = data.as_ref(); - unsafe { raw::symmetric_state_absorb(self.handle, data.as_ptr(), data.len()) } - } - - pub fn digest(&mut self) -> Result, raw::CryptoErrno> { - unsafe { - let tag = raw::symmetric_state_squeeze_tag(self.handle)?; - let len = raw::symmetric_tag_len(tag)?; - let mut out = vec![0; len]; - raw::symmetric_tag_pull(tag, out.as_mut_ptr(), out.len())?; - raw::symmetric_tag_close(tag)?; - Ok(out) - } - } - - pub fn digest_into(&mut self, mut buf: impl AsMut<[u8]>) -> Result<(), raw::CryptoErrno> { - let buf = buf.as_mut(); - unsafe { - let tag = raw::symmetric_state_squeeze_tag(self.handle)?; - raw::symmetric_tag_pull(tag, buf.as_mut_ptr(), buf.len())?; - raw::symmetric_tag_close(tag)?; - } - Ok(()) - } -} - -impl Drop for Hmac { - fn drop(&mut self) { - unsafe { - raw::symmetric_state_close(self.handle).unwrap(); - } - } -} - -pub struct Hash { - handle: raw::SymmetricState, - hash_len: usize, -} - -impl Hash { - pub fn create(alg: &str) -> Result { - let (alg, hash_len) = match alg { - "sha256" | "SHA256" | "SHA-256" => ("SHA-256", 32), - "sha512" | "SHA512" | "SHA-512" => ("SHA-512", 64), - _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), - }; - let handle = { unsafe { raw::symmetric_state_open(alg, NONE_KEY, NONE_OPTS)? } }; - Ok(Self { handle, hash_len }) - } - - pub fn update(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { - let data = data.as_ref(); - unsafe { raw::symmetric_state_absorb(self.handle, data.as_ptr(), data.len()) } - } - - pub fn digest(&mut self) -> Result, raw::CryptoErrno> { - let mut out = vec![0; self.hash_len]; - self.digest_into(&mut out)?; - Ok(out) - } - - pub fn digest_into(&mut self, mut buf: impl AsMut<[u8]>) -> Result<(), raw::CryptoErrno> { - let buf = buf.as_mut(); - unsafe { - raw::symmetric_state_squeeze(self.handle, buf.as_mut_ptr(), buf.len())?; - } - Ok(()) - } - - pub fn copy(&self) -> Result { - let h = unsafe { raw::symmetric_state_clone(self.handle) }?; - Ok(Self { - handle: h, - hash_len: self.hash_len, - }) - } -} - -impl Drop for Hash { - fn drop(&mut self) { - unsafe { - raw::symmetric_state_close(self.handle).unwrap(); - } - } -} - -/// Behaviour like -/// -/// ```js -/// let hmac = createHmac(alg, key); -/// infos.forEach(info => hmac.update(info)); -/// let return = hmac.digest(); -/// ``` -pub fn hmac( - alg: &str, - key: impl AsRef<[u8]>, - infos: &[impl AsRef<[u8]>], -) -> Result, raw::CryptoErrno> { - let mut hash = Hmac::create(alg, key)?; - for info in infos { - hash.update(info)?; - } - hash.digest() -} - -/// Behaviour like -/// -/// ```js -/// let hash = createHash(alg); -/// infos.forEach(info => hash.update(info)); -/// let return = hash.digest(); -/// ``` -pub fn hash(alg: &str, infos: &[impl AsRef<[u8]>]) -> Result, raw::CryptoErrno> { - let mut hash = Hash::create(alg)?; - for info in infos { - hash.update(info)?; - } - hash.digest() -} - -fn hkdf_extract( - alg: &str, - key: impl AsRef<[u8]>, - salt: impl AsRef<[u8]>, -) -> Result { - let (extract_alg, expand_alg) = match alg { - "sha256" | "SHA256" => ("HKDF-EXTRACT/SHA-256", "HKDF-EXPAND/SHA-256"), - "sha512" | "SHA512" => ("HKDF-EXTRACT/SHA-512", "HKDF-EXPAND/SHA-512"), - _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), - }; - let key = key.as_ref(); - let salt = salt.as_ref(); - if !key.is_empty() { - unsafe { - let extract_key = raw::symmetric_key_import(extract_alg, key.as_ptr(), key.len())?; - let extract_handle = raw::symmetric_state_open( - extract_alg, - raw::OptSymmetricKey { - tag: raw::OPT_SYMMETRIC_KEY_U_SOME.raw(), - u: raw::OptSymmetricKeyUnion { some: extract_key }, - }, - NONE_OPTS, - )?; - raw::symmetric_state_absorb(extract_handle, salt.as_ptr(), salt.len())?; - let expand_key = raw::symmetric_state_squeeze_key(extract_handle, expand_alg)?; - raw::symmetric_state_close(extract_handle)?; - raw::symmetric_key_close(extract_key)?; - Ok(expand_key) - } - } else { - let res = hmac(alg, salt, &[key])?; - unsafe { raw::symmetric_key_import(expand_alg, res.as_ptr(), res.len()) } - } -} - -fn hkdf_extract_raw( - alg: &str, - key: impl AsRef<[u8]>, - salt: impl AsRef<[u8]>, -) -> Result, raw::CryptoErrno> { - hmac(alg, salt, &[key]) -} - -/// As same as `hkdf`, but use hmac to manual expand -pub fn hkdf_hmac( - alg: &str, - key: impl AsRef<[u8]>, - salt: impl AsRef<[u8]>, - info: impl AsRef<[u8]>, - key_len: usize, -) -> Result, raw::CryptoErrno> { - let key = key.as_ref(); - let salt = salt.as_ref(); - let info = info.as_ref(); - let (_, _, hash_len) = match alg { - "sha256" | "SHA256" => ("HKDF-EXTRACT/SHA-256", "HKDF-EXPAND/SHA-256", 32), - "sha512" | "SHA512" => ("HKDF-EXTRACT/SHA-512", "HKDF-EXPAND/SHA-512", 64), - _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), - }; - let expand_key = hkdf_extract_raw(alg, key, salt)?; - let mut out = vec![0; key_len]; - let mut last = [].as_slice(); - for (idx, chunk) in out.chunks_mut(hash_len).enumerate() { - let counter = [idx as u8 + 1]; - chunk.clone_from_slice(&hmac(alg, &expand_key, &[last, info, &counter])?[..chunk.len()]); - last = chunk; - } - Ok(out) -} - -/// Behaviour like `crypto.hkdfSync` -pub fn hkdf( - alg: &str, - key: impl AsRef<[u8]>, - salt: impl AsRef<[u8]>, - info: impl AsRef<[u8]>, - key_len: usize, -) -> Result, raw::CryptoErrno> { - let key = key.as_ref(); - let salt = salt.as_ref(); - let info = info.as_ref(); - let (_, expand_alg) = match alg { - "sha256" | "SHA256" => ("HKDF-EXTRACT/SHA-256", "HKDF-EXPAND/SHA-256"), - "sha512" | "SHA512" => ("HKDF-EXTRACT/SHA-512", "HKDF-EXPAND/SHA-512"), - _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), - }; - let mut out = vec![0; key_len]; - let expand_key = hkdf_extract(alg, key, salt)?; - unsafe { - let expand_handle = raw::symmetric_state_open( - expand_alg, - raw::OptSymmetricKey { - tag: raw::OPT_SYMMETRIC_KEY_U_SOME.raw(), - u: raw::OptSymmetricKeyUnion { some: expand_key }, - }, - NONE_OPTS, - )?; - raw::symmetric_state_absorb(expand_handle, info.as_ptr(), info.len())?; - raw::symmetric_state_squeeze(expand_handle, out.as_mut_ptr(), out.len())?; - raw::symmetric_state_close(expand_handle)?; - raw::symmetric_key_close(expand_key)?; - } - Ok(out) -} - -/// Behaviour like `crypto.pbkdf2Sync` -pub fn pbkdf2( - alg: &str, - password: impl AsRef<[u8]>, - salt: impl AsRef<[u8]>, - iters: usize, - key_len: usize, -) -> Result, raw::CryptoErrno> { - let hash_len = match alg { - "sha256" | "SHA256" | "HMAC/SHA-256" => 32, - "sha512" | "SHA512" | "HMAC/SHA-512" => 64, - _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), - }; - let mut out = vec![0; key_len]; - for (idx, chunk) in out.chunks_mut(hash_len).enumerate() { - let mut salt_2 = salt.as_ref().to_vec(); - let idx = idx + 1; - salt_2.push(((idx >> 24) & 0xff) as u8); - salt_2.push(((idx >> 16) & 0xff) as u8); - salt_2.push(((idx >> 8) & 0xff) as u8); - salt_2.push(((idx) & 0xff) as u8); - let mut res_t = hmac(alg, password.as_ref(), &[&salt_2])?; - let mut res_u = res_t.clone(); - for _ in 0..iters - 1 { - res_u = hmac(alg, password.as_ref(), &[&res_u])?; - for k in 0..res_t.len() { - res_t[k] ^= res_u[k]; - } - } - chunk.copy_from_slice(&res_t[..chunk.len()]); - } - Ok(out) -} - -struct ScryptRom { - b: Vec, - r: usize, - n: usize, - xy: Vec, - v: Vec, - b32: Vec, - x: Vec, - xx: Vec, -} - -fn blockxor(a: &[u8], b: &mut [u8]) { - for i in 0..a.len() { - b[i] ^= a[i]; - } -} - -impl ScryptRom { - fn romix(&mut self, i: usize, r: usize) { - let block_start = i * 128 * r; - let offset = (2 * r - 1) * 64; - let block_len = 128 * r; - self.xy[0..block_len].copy_from_slice(&self.b[block_start..(block_start + block_len)]); - for i1 in 0..self.n { - self.v[i1 * block_len..(i1 + 1) * block_len].copy_from_slice(&self.xy[0..block_len]); - self.blockmix(block_len); - } - - fn read_u32le(p: &[u8]) -> u32 { - (p[0] as u32) + ((p[1] as u32) << 8) + ((p[2] as u32) << 16) + ((p[3] as u32) << 24) - } - - for _ in 0..self.n { - let j = read_u32le(&self.xy[offset..]) as usize & (self.n - 1); - blockxor( - &self.v[j * block_len..(j + 1) * block_len], - &mut self.xy[0..block_len], - ); - self.blockmix(block_len); - } - self.b[block_start..block_start + block_len].copy_from_slice(&self.xy[0..block_len]); - } - - fn blockmix(&mut self, block_len: usize) { - self.xx[0..64].copy_from_slice(&self.xy[(2 * self.r - 1) * 64..(2 * self.r) * 64]); - for i in 0..2 * self.r { - blockxor(&self.xy[i * 64..(i + 1) * 64], &mut self.xx[0..64]); - self.salsa20_8(); - self.xy[block_len + (i * 64)..block_len + (i * 64) + 64] - .copy_from_slice(&self.xx[0..64]); - } - for i in 0..self.r { - self.xy.copy_within( - block_len + (i * 2) * 64..block_len + (i * 2) * 64 + 64, - i * 64, - ); - self.xy.copy_within( - block_len + (i * 2 + 1) * 64..block_len + (i * 2 + 1) * 64 + 64, - (i + self.r) * 64, - ); - } - } - - fn salsa20_8(&mut self) { - #[inline(always)] - #[allow(non_snake_case)] - fn R(i: i32, r: i32) -> i32 { - i.rotate_left(r as u32) - } - - for i in 0..16 { - self.b32[i] = ((self.xx[i * 4 + 0] & 0xff) as i32) << 0; - self.b32[i] |= ((self.xx[i * 4 + 1] & 0xff) as i32) << 8; - self.b32[i] |= ((self.xx[i * 4 + 2] & 0xff) as i32) << 16; - self.b32[i] |= ((self.xx[i * 4 + 3] & 0xff) as i32) << 24; - } - - self.x.copy_from_slice(&self.b32); - - for _ in 0..4 { - self.x[4] ^= R(self.x[0].wrapping_add(self.x[12]), 7); - self.x[8] ^= R(self.x[4].wrapping_add(self.x[0]), 9); - self.x[12] ^= R(self.x[8].wrapping_add(self.x[4]), 13); - self.x[0] ^= R(self.x[12].wrapping_add(self.x[8]), 18); - self.x[9] ^= R(self.x[5].wrapping_add(self.x[1]), 7); - self.x[13] ^= R(self.x[9].wrapping_add(self.x[5]), 9); - self.x[1] ^= R(self.x[13].wrapping_add(self.x[9]), 13); - self.x[5] ^= R(self.x[1].wrapping_add(self.x[13]), 18); - self.x[14] ^= R(self.x[10].wrapping_add(self.x[6]), 7); - self.x[2] ^= R(self.x[14].wrapping_add(self.x[10]), 9); - self.x[6] ^= R(self.x[2].wrapping_add(self.x[14]), 13); - self.x[10] ^= R(self.x[6].wrapping_add(self.x[2]), 18); - self.x[3] ^= R(self.x[15].wrapping_add(self.x[11]), 7); - self.x[7] ^= R(self.x[3].wrapping_add(self.x[15]), 9); - self.x[11] ^= R(self.x[7].wrapping_add(self.x[3]), 13); - self.x[15] ^= R(self.x[11].wrapping_add(self.x[7]), 18); - self.x[1] ^= R(self.x[0].wrapping_add(self.x[3]), 7); - self.x[2] ^= R(self.x[1].wrapping_add(self.x[0]), 9); - self.x[3] ^= R(self.x[2].wrapping_add(self.x[1]), 13); - self.x[0] ^= R(self.x[3].wrapping_add(self.x[2]), 18); - self.x[6] ^= R(self.x[5].wrapping_add(self.x[4]), 7); - self.x[7] ^= R(self.x[6].wrapping_add(self.x[5]), 9); - self.x[4] ^= R(self.x[7].wrapping_add(self.x[6]), 13); - self.x[5] ^= R(self.x[4].wrapping_add(self.x[7]), 18); - self.x[11] ^= R(self.x[10].wrapping_add(self.x[9]), 7); - self.x[8] ^= R(self.x[11].wrapping_add(self.x[10]), 9); - self.x[9] ^= R(self.x[8].wrapping_add(self.x[11]), 13); - self.x[10] ^= R(self.x[9].wrapping_add(self.x[8]), 18); - self.x[12] ^= R(self.x[15].wrapping_add(self.x[14]), 7); - self.x[13] ^= R(self.x[12].wrapping_add(self.x[15]), 9); - self.x[14] ^= R(self.x[13].wrapping_add(self.x[12]), 13); - self.x[15] ^= R(self.x[14].wrapping_add(self.x[13]), 18); - } - - for i in 0..16 { - self.b32[i] = self.b32[i].wrapping_add(self.x[i]); - } - - for i in 0..16 { - self.xx[i * 4 + 0] = (self.b32[i] >> 0 & 0xff) as u8; - self.xx[i * 4 + 1] = (self.b32[i] >> 8 & 0xff) as u8; - self.xx[i * 4 + 2] = (self.b32[i] >> 16 & 0xff) as u8; - self.xx[i * 4 + 3] = (self.b32[i] >> 24 & 0xff) as u8; - } - } -} - -fn scrypt_rom(b: &[u8], r: usize, n: usize, p: usize) -> Vec { - let mut rom = ScryptRom { - b: b.to_vec(), - r, - n, - xy: vec![0; 256 * r], - v: vec![0; 128 * r * n], - b32: vec![0; 16], - x: vec![0; 16], - xx: vec![0; 64], - }; - for i in 0..p { - rom.romix(i, r); - } - rom.b -} - -/// Behaviour like `crypto.scryptSync` -pub fn scrypt( - password: impl AsRef<[u8]>, - salt: impl AsRef<[u8]>, - n: usize, - r: usize, - p: usize, - keylen: usize, -) -> Result, raw::CryptoErrno> { - let blen = p * 128 * r; - let b = pbkdf2("HMAC/SHA-256", &password, salt, 1, blen)?; - let s = scrypt_rom(&b, r, n, p); - let f = pbkdf2("HMAC/SHA-256", &password, &s, 1, keylen)?; - Ok(f) -} - -/// Convert u8 array to hex string, -/// behaviour like `Buffer.from(arr).toString("hex")` -/// -/// # Examples -/// -/// ``` -/// use crypto_wasi::u8array_to_hex; -/// -/// assert_eq!(u8array_to_hex([01, 23, 45]), "01172d".to_string()); -/// ``` -pub fn u8array_to_hex(arr: impl AsRef<[u8]>) -> String { - arr.as_ref() - .iter() - .map(|v| format!("{:02x}", v)) - .collect::>() - .join("") -} - -/// Convert hex string to u8 array -/// -/// # Examples -/// -/// ``` -/// use crypto_wasi::hex_to_u8array; -/// -/// assert_eq!(hex_to_u8array("01172d"), Some(vec![01, 23, 45])); -/// ``` -pub fn hex_to_u8array(arr: &str) -> Option> { - if arr.len() % 2 != 0 || arr.chars().any(|v| !v.is_ascii_hexdigit()) { - return None; - } - - fn hex_byte_to_u8(h: u8) -> u8 { - match h { - b'0'..=b'9' => h - b'0', - b'a'..=b'f' => 10 + h - b'a', - b'A'..=b'F' => 10 + h - b'A', - _ => unreachable!(), - } - } - - Some( - arr.as_bytes() - .chunks(2) - .map(|v| (hex_byte_to_u8(v[0]) << 4) + hex_byte_to_u8(v[1])) - .collect(), - ) -} - -pub struct Cipher { - handle: raw::SymmetricState, - message: Vec, - tag: Option>, -} - -impl Cipher { - pub fn create( - alg: &str, - key: impl AsRef<[u8]>, - iv: impl AsRef<[u8]>, - ) -> Result { - let alg = match alg { - "aes-128-gcm" | "AES-128-GCM" => "AES-128-GCM", - "aes-256-gcm" | "AES-256-GCM" => "AES-256-GCM", - _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), - }; - let handle = { - let key = key.as_ref(); - let iv = iv.as_ref(); - unsafe { - let raw_key = raw::symmetric_key_import(alg, key.as_ptr(), key.len())?; - let key = raw::OptSymmetricKey { - tag: raw::OPT_SYMMETRIC_KEY_U_SOME.raw(), - u: raw::OptSymmetricKeyUnion { some: raw_key }, - }; - let opt = raw::options_open(raw::ALGORITHM_TYPE_SYMMETRIC)?; - raw::options_set(opt, "nonce", iv.as_ptr(), iv.len())?; - let opts = raw::OptOptions { - tag: raw::OPT_OPTIONS_U_SOME.raw(), - u: raw::OptOptionsUnion { some: opt }, - }; - let state = raw::symmetric_state_open(alg, key, opts).unwrap(); - raw::symmetric_key_close(raw_key).unwrap(); - state - } - }; - Ok(Self { - handle, - message: vec![], - tag: None, - }) - } - - pub fn set_aad(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { - let data = data.as_ref(); - unsafe { raw::symmetric_state_absorb(self.handle, data.as_ptr(), data.len()) } - } - - /// in WasmEdge implement of wasi-crypto, `encrypt` can't be called multiple times, - /// multiple call `encrypt` is also not equivalent to multiple call `update`. - /// so we store all message and concat it, then encrypt one-time on `final` - pub fn update(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { - self.message.extend_from_slice(data.as_ref()); - Ok(()) - } - - /// `final` is reserved keyword, `fin` looks better than `r#final` - pub fn fin(&mut self) -> Result, raw::CryptoErrno> { - let mut out = vec![0; self.message.len()]; - unsafe { - let tag = raw::symmetric_state_encrypt_detached( - self.handle, - out.as_mut_ptr(), - out.len(), - self.message.as_ptr(), - self.message.len(), - )?; - let len = raw::symmetric_tag_len(tag)?; - let mut buf = vec![0; len]; - raw::symmetric_tag_pull(tag, buf.as_mut_ptr(), buf.len())?; - raw::symmetric_tag_close(tag)?; - self.tag = Some(buf); - } - Ok(out) - } - - /// equivalent to `update(data)` then `final` - pub fn encrypt(&mut self, data: impl AsRef<[u8]>) -> Result, raw::CryptoErrno> { - let data = data.as_ref(); - let mut out = vec![0; data.len()]; - unsafe { - let tag = raw::symmetric_state_encrypt_detached( - self.handle, - out.as_mut_ptr(), - out.len(), - data.as_ptr(), - data.len(), - )?; - let len = raw::symmetric_tag_len(tag)?; - let mut buf = vec![0; len]; - raw::symmetric_tag_pull(tag, buf.as_mut_ptr(), buf.len())?; - raw::symmetric_tag_close(tag)?; - self.tag = Some(buf); - } - Ok(out) - } - - pub fn get_auth_tag(&self) -> Result<&Vec, raw::CryptoErrno> { - self.tag.as_ref().ok_or(raw::CRYPTO_ERRNO_INVALID_OPERATION) - } - - pub fn take_auth_tag(&mut self) -> Result, raw::CryptoErrno> { - self.tag.take().ok_or(raw::CRYPTO_ERRNO_INVALID_OPERATION) - } -} - -impl Drop for Cipher { - fn drop(&mut self) { - unsafe { - raw::symmetric_state_close(self.handle).unwrap(); - } - } -} - -pub fn encrypt( - alg: &str, - key: impl AsRef<[u8]>, - iv: impl AsRef<[u8]>, - aad: impl AsRef<[u8]>, - msg: impl AsRef<[u8]>, -) -> Result<(Vec, Vec), raw::CryptoErrno> { - let mut c = Cipher::create(alg, key, iv)?; - c.set_aad(aad)?; - let out = c.encrypt(msg)?; - let tag = c.take_auth_tag()?; - Ok((out, tag)) -} - -pub struct Decipher { - handle: raw::SymmetricState, - message: Vec, - tag: Option>, -} - -impl Decipher { - pub fn create( - alg: &str, - key: impl AsRef<[u8]>, - iv: impl AsRef<[u8]>, - ) -> Result { - let alg = match alg { - "aes-128-gcm" | "AES-128-GCM" => "AES-128-GCM", - "aes-256-gcm" | "AES-256-GCM" => "AES-256-GCM", - _ => return Err(raw::CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM), - }; - let handle = { - let key = key.as_ref(); - let iv = iv.as_ref(); - unsafe { - let raw_key = raw::symmetric_key_import(alg, key.as_ptr(), key.len())?; - let key = raw::OptSymmetricKey { - tag: raw::OPT_SYMMETRIC_KEY_U_SOME.raw(), - u: raw::OptSymmetricKeyUnion { some: raw_key }, - }; - let opt = raw::options_open(raw::ALGORITHM_TYPE_SYMMETRIC)?; - raw::options_set(opt, "nonce", iv.as_ptr(), iv.len())?; - let opts = raw::OptOptions { - tag: raw::OPT_OPTIONS_U_SOME.raw(), - u: raw::OptOptionsUnion { some: opt }, - }; - let state = raw::symmetric_state_open(alg, key, opts).unwrap(); - raw::symmetric_key_close(raw_key).unwrap(); - state - } - }; - Ok(Self { - handle, - message: vec![], - tag: None, - }) - } - - pub fn set_aad(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { - let data = data.as_ref(); - unsafe { raw::symmetric_state_absorb(self.handle, data.as_ptr(), data.len()) } - } - - /// in WasmEdge implement of wasi-crypto, `decrypt` can't be called multiple times, - /// multiple call `decrypt` is also not equivalent to multiple call `update`. - /// so we store all message and concat it, then decrypt one-time on `final` - pub fn update(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { - self.message.extend_from_slice(data.as_ref()); - Ok(()) - } - - /// `final` is reserved keyword, `fin` looks better than `r#final` - pub fn fin(&mut self) -> Result, raw::CryptoErrno> { - if let Some(tag) = &self.tag { - let mut out = vec![0; self.message.len()]; - unsafe { - raw::symmetric_state_decrypt_detached( - self.handle, - out.as_mut_ptr(), - out.len(), - self.message.as_ptr(), - self.message.len(), - tag.as_ptr(), - tag.len(), - )?; - } - Ok(out) - } else { - Err(raw::CRYPTO_ERRNO_INVALID_OPERATION) - } - } - - /// equivalent to `update(data)` then `final` - pub fn decrypt(&mut self, data: impl AsRef<[u8]>) -> Result, raw::CryptoErrno> { - let data = data.as_ref(); - if let Some(tag) = &self.tag { - let mut out = vec![0; data.len()]; - unsafe { - raw::symmetric_state_decrypt_detached( - self.handle, - out.as_mut_ptr(), - out.len(), - data.as_ptr(), - data.len(), - tag.as_ptr(), - tag.len(), - )?; - } - Ok(out) - } else { - Err(raw::CRYPTO_ERRNO_INVALID_OPERATION) - } - } - - pub fn set_auth_tag(&mut self, data: impl AsRef<[u8]>) -> Result<(), raw::CryptoErrno> { - self.tag = Some(data.as_ref().to_vec()); - Ok(()) - } -} - -impl Drop for Decipher { - fn drop(&mut self) { - unsafe { - raw::symmetric_state_close(self.handle).unwrap(); - } - } -} - -pub fn decrypt( - alg: &str, - key: impl AsRef<[u8]>, - iv: impl AsRef<[u8]>, - aad: impl AsRef<[u8]>, - auth_tag: impl AsRef<[u8]>, - msg: impl AsRef<[u8]>, -) -> Result, raw::CryptoErrno> { - let mut c = Decipher::create(alg, key, iv)?; - c.set_aad(aad)?; - c.set_auth_tag(auth_tag)?; - c.decrypt(msg) -} diff --git a/src/internal_module/crypto/mod.rs b/src/internal_module/crypto/mod.rs index 2a5ebc6..be2e77a 100644 --- a/src/internal_module/crypto/mod.rs +++ b/src/internal_module/crypto/mod.rs @@ -1,11 +1,11 @@ -mod lib; -mod raw; - -use core::arch; - 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")] @@ -79,8 +79,6 @@ fn random_fill(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsVal }; } -use self::lib::{hkdf_hmac, pbkdf2, scrypt}; - 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()))); @@ -97,11 +95,11 @@ fn pbkdf2_sync(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsVal let alg = get_arg!(argv, JsValue::String, 4); match { pbkdf2( - alg.as_str(), 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(), @@ -163,8 +161,26 @@ fn hkdf_sync(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue } } +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: lib::Hash, + handle: Hash, } impl JsHash { @@ -195,7 +211,7 @@ impl JsHash { } } - fn copy(&self) -> Result { + fn copy(&self) -> Result { self.handle.copy().map(|h| JsHash { handle: h }) } } @@ -221,7 +237,7 @@ impl JsClassDef for JsHash { fn constructor_fn(ctx: &mut Context, argv: &[JsValue]) -> Result { match argv.get(0) { - Some(JsValue::String(alg)) => lib::Hash::create(alg.as_str()) + 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))) @@ -239,7 +255,7 @@ impl JsClassDef for JsHash { } struct JsHmac { - handle: lib::Hmac, + handle: Hmac, } impl JsHmac { @@ -293,7 +309,7 @@ impl JsClassDef for JsHmac { fn constructor_fn(ctx: &mut Context, argv: &[JsValue]) -> Result { match (argv.get(0), argv.get(1)) { (Some(JsValue::String(alg)), Some(JsValue::ArrayBuffer(key))) => { - lib::Hmac::create(alg.as_str(), key.as_ref()) + 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))) @@ -306,8 +322,8 @@ impl JsClassDef for JsHmac { } enum JsCipher { - Cipher(lib::Cipher), - Decipher(lib::Decipher), + Cipher(Cipheriv), + Decipher(Decipheriv), } impl JsCipher { @@ -432,14 +448,14 @@ impl JsClassDef for JsCipher { ) = (argv.get(0), argv.get(1), argv.get(2), argv.get(4)) { if *is_encrypt { - lib::Cipher::create(alg.as_str(), key.as_ref(), iv.as_ref()) + 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 { - lib::Decipher::create(alg.as_str(), key.as_ref(), iv.as_ref()) + 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))) @@ -452,6 +468,76 @@ impl JsClassDef for JsCipher { } } +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 { @@ -477,9 +563,17 @@ impl ModuleInit for Crypto { "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), + ); } } @@ -493,9 +587,11 @@ pub fn init_module(ctx: &mut Context) { "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/crypto/raw.rs b/src/internal_module/crypto/raw.rs deleted file mode 100644 index 1e86471..0000000 --- a/src/internal_module/crypto/raw.rs +++ /dev/null @@ -1,3860 +0,0 @@ -#![allow(dead_code, unused_variables)] -use core::fmt; -use core::mem::MaybeUninit; -#[repr(transparent)] -#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct CryptoErrno(u16); -/// Operation succeeded. -pub const CRYPTO_ERRNO_SUCCESS: CryptoErrno = CryptoErrno(0); -/// An error occurred when trying to during a conversion from a host type to a -/// guest type. -/// -/// Only an internal bug can throw this error. -pub const CRYPTO_ERRNO_GUEST_ERROR: CryptoErrno = CryptoErrno(1); -/// The requested operation is valid, but not implemented by the host. -pub const CRYPTO_ERRNO_NOT_IMPLEMENTED: CryptoErrno = CryptoErrno(2); -/// The requested feature is not supported by the chosen algorithm. -pub const CRYPTO_ERRNO_UNSUPPORTED_FEATURE: CryptoErrno = CryptoErrno(3); -/// The requested operation is valid, but was administratively prohibited. -pub const CRYPTO_ERRNO_PROHIBITED_OPERATION: CryptoErrno = CryptoErrno(4); -/// Unsupported encoding for an import or export operation. -pub const CRYPTO_ERRNO_UNSUPPORTED_ENCODING: CryptoErrno = CryptoErrno(5); -/// The requested algorithm is not supported by the host. -pub const CRYPTO_ERRNO_UNSUPPORTED_ALGORITHM: CryptoErrno = CryptoErrno(6); -/// The requested option is not supported by the currently selected algorithm. -pub const CRYPTO_ERRNO_UNSUPPORTED_OPTION: CryptoErrno = CryptoErrno(7); -/// An invalid or incompatible key was supplied. -/// -/// The key may not be valid, or was generated for a different algorithm or -/// parameters set. -pub const CRYPTO_ERRNO_INVALID_KEY: CryptoErrno = CryptoErrno(8); -/// The currently selected algorithm doesn't support the requested output -/// length. -/// -/// This error is thrown by non-extensible hash functions, when requesting an -/// output size larger than they produce out of a single block. -pub const CRYPTO_ERRNO_INVALID_LENGTH: CryptoErrno = CryptoErrno(9); -/// A signature or authentication tag verification failed. -pub const CRYPTO_ERRNO_VERIFICATION_FAILED: CryptoErrno = CryptoErrno(10); -/// A secure random numbers generator is not available. -/// -/// The requested operation requires random numbers, but the host cannot -/// securely generate them at the moment. -pub const CRYPTO_ERRNO_RNG_ERROR: CryptoErrno = CryptoErrno(11); -/// An error was returned by the underlying cryptography library. -/// -/// The host may be running out of memory, parameters may be incompatible with -/// the chosen implementation of an algorithm or another unexpected error may -/// have happened. -/// -/// Ideally, the specification should provide enough details and guidance to -/// make this error impossible to ever be thrown. -/// -/// Realistically, the WASI crypto module cannot possibly cover all possible -/// error types implementations can return, especially since some of these may -/// be language-specific. This error can thus be thrown when other error types -/// are not suitable, and when the original error comes from the cryptographic -/// primitives themselves and not from the WASI module. -pub const CRYPTO_ERRNO_ALGORITHM_FAILURE: CryptoErrno = CryptoErrno(12); -/// The supplied signature is invalid, or incompatible with the chosen -/// algorithm. -pub const CRYPTO_ERRNO_INVALID_SIGNATURE: CryptoErrno = CryptoErrno(13); -/// An attempt was made to close a handle that was already closed. -pub const CRYPTO_ERRNO_CLOSED: CryptoErrno = CryptoErrno(14); -/// A function was called with an unassigned handle, a closed handle, or handle -/// of an unexpected type. -pub const CRYPTO_ERRNO_INVALID_HANDLE: CryptoErrno = CryptoErrno(15); -/// The host needs to copy data to a guest-allocated buffer, but that buffer is -/// too small. -pub const CRYPTO_ERRNO_OVERFLOW: CryptoErrno = CryptoErrno(16); -/// An internal error occurred. -/// -/// This error is reserved to internal consistency checks, and must only be sent -/// if the internal state of the host remains safe after an inconsistency was -/// detected. -pub const CRYPTO_ERRNO_INTERNAL_ERROR: CryptoErrno = CryptoErrno(17); -/// Too many handles are currently open, and a new one cannot be created. -/// -/// Implementations are free to represent handles as they want, and to enforce -/// limits to limit resources usage. -pub const CRYPTO_ERRNO_TOO_MANY_HANDLES: CryptoErrno = CryptoErrno(18); -/// A key was provided, but the chosen algorithm doesn't support keys. -/// -/// This is returned by symmetric operations. -/// -/// Many hash functions, in particular, do not support keys without being used -/// in particular constructions. Blindly ignoring a key provided by mistake -/// while trying to open a context for such as function could cause serious -/// security vulnerabilities. -/// -/// These functions must refuse to create the context and return this error -/// instead. -pub const CRYPTO_ERRNO_KEY_NOT_SUPPORTED: CryptoErrno = CryptoErrno(19); -/// A key is required for the chosen algorithm, but none was given. -pub const CRYPTO_ERRNO_KEY_REQUIRED: CryptoErrno = CryptoErrno(20); -/// The provided authentication tag is invalid or incompatible with the current -/// algorithm. -/// -/// This error is returned by decryption functions and tag verification -/// functions. -/// -/// Unlike `verification_failed`, this error code is returned when the tag -/// cannot possibly verify for any input. -pub const CRYPTO_ERRNO_INVALID_TAG: CryptoErrno = CryptoErrno(21); -/// The requested operation is incompatible with the current scheme. -/// -/// For example, the `symmetric_state_encrypt()` function cannot complete if the -/// selected construction is a key derivation function. This error code will be -/// returned instead. -pub const CRYPTO_ERRNO_INVALID_OPERATION: CryptoErrno = CryptoErrno(22); -/// A nonce is required. -/// -/// Most encryption schemes require a nonce. -/// -/// In the absence of a nonce, the WASI cryptography module can automatically -/// generate one, if that can be done safely. The nonce can be retrieved later -/// with the `symmetric_state_option_get()` function using the `nonce` -/// parameter. If automatically generating a nonce cannot be done safely, the -/// module never falls back to an insecure option and requests an explicit nonce -/// by throwing that error. -pub const CRYPTO_ERRNO_NONCE_REQUIRED: CryptoErrno = CryptoErrno(23); -/// The provided nonce doesn't have a correct size for the given cipher. -pub const CRYPTO_ERRNO_INVALID_NONCE: CryptoErrno = CryptoErrno(24); -/// The named option was not set. -/// -/// The caller tried to read the value of an option that was not set. -/// This error is used to make the distinction between an empty option, and an -/// option that was not set and left to its default value. -pub const CRYPTO_ERRNO_OPTION_NOT_SET: CryptoErrno = CryptoErrno(25); -/// A key or key pair matching the requested identifier cannot be found using -/// the supplied information. -/// -/// This error is returned by a secrets manager via the `keypair_from_id()` -/// function. -pub const CRYPTO_ERRNO_NOT_FOUND: CryptoErrno = CryptoErrno(26); -/// The algorithm requires parameters that haven't been set. -/// -/// Non-generic options are required and must be given by building an `options` -/// set and giving that object to functions instantiating that algorithm. -pub const CRYPTO_ERRNO_PARAMETERS_MISSING: CryptoErrno = CryptoErrno(27); -/// A requested computation is not done yet, and additional calls to the -/// function are required. -/// -/// Some functions, such as functions generating key pairs and password -/// stretching functions, can take a long time to complete. -/// -/// In order to avoid a host call to be blocked for too long, these functions -/// can return prematurely, requiring additional calls with the same parameters -/// until they complete. -pub const CRYPTO_ERRNO_IN_PROGRESS: CryptoErrno = CryptoErrno(28); -/// Multiple keys have been provided, but they do not share the same type. -/// -/// This error is returned when trying to build a key pair from a public key and -/// a secret key that were created for different and incompatible algorithms. -pub const CRYPTO_ERRNO_INCOMPATIBLE_KEYS: CryptoErrno = CryptoErrno(29); -/// A managed key or secret expired and cannot be used any more. -pub const CRYPTO_ERRNO_EXPIRED: CryptoErrno = CryptoErrno(30); -impl CryptoErrno { - pub const fn raw(&self) -> u16 { - self.0 - } - - pub fn name(&self) -> &'static str { - match self.0 { - 0 => "SUCCESS", - 1 => "GUEST_ERROR", - 2 => "NOT_IMPLEMENTED", - 3 => "UNSUPPORTED_FEATURE", - 4 => "PROHIBITED_OPERATION", - 5 => "UNSUPPORTED_ENCODING", - 6 => "UNSUPPORTED_ALGORITHM", - 7 => "UNSUPPORTED_OPTION", - 8 => "INVALID_KEY", - 9 => "INVALID_LENGTH", - 10 => "VERIFICATION_FAILED", - 11 => "RNG_ERROR", - 12 => "ALGORITHM_FAILURE", - 13 => "INVALID_SIGNATURE", - 14 => "CLOSED", - 15 => "INVALID_HANDLE", - 16 => "OVERFLOW", - 17 => "INTERNAL_ERROR", - 18 => "TOO_MANY_HANDLES", - 19 => "KEY_NOT_SUPPORTED", - 20 => "KEY_REQUIRED", - 21 => "INVALID_TAG", - 22 => "INVALID_OPERATION", - 23 => "NONCE_REQUIRED", - 24 => "INVALID_NONCE", - 25 => "OPTION_NOT_SET", - 26 => "NOT_FOUND", - 27 => "PARAMETERS_MISSING", - 28 => "IN_PROGRESS", - 29 => "INCOMPATIBLE_KEYS", - 30 => "EXPIRED", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } - - pub fn message(&self) -> &'static str { - match self.0 { - 0 => "Operation succeeded.", - 1 => { - "An error occurred when trying to during a conversion from a host type to a guest \ - type. - -Only an internal bug can throw this error." - } - 2 => "The requested operation is valid, but not implemented by the host.", - 3 => "The requested feature is not supported by the chosen algorithm.", - 4 => "The requested operation is valid, but was administratively prohibited.", - 5 => "Unsupported encoding for an import or export operation.", - 6 => "The requested algorithm is not supported by the host.", - 7 => "The requested option is not supported by the currently selected algorithm.", - 8 => { - "An invalid or incompatible key was supplied. - -The key may not be valid, or was generated for a different algorithm or parameters set." - } - 9 => { - "The currently selected algorithm doesn't support the requested output length. - -This error is thrown by non-extensible hash functions, when requesting an output size larger than \ - they produce out of a single block." - } - 10 => "A signature or authentication tag verification failed.", - 11 => { - "A secure random numbers generator is not available. - -The requested operation requires random numbers, but the host cannot securely generate them at the \ - moment." - } - 12 => { - "An error was returned by the underlying cryptography library. - -The host may be running out of memory, parameters may be incompatible with the chosen \ - implementation of an algorithm or another unexpected error may have happened. - -Ideally, the specification should provide enough details and guidance to make this error \ - impossible to ever be thrown. - -Realistically, the WASI crypto module cannot possibly cover all possible error types \ - implementations can return, especially since some of these may be \ - language-specific. -This error can thus be thrown when other error types are not suitable, and when the original error \ - comes from the cryptographic primitives themselves and not from the WASI module." - } - 13 => "The supplied signature is invalid, or incompatible with the chosen algorithm.", - 14 => "An attempt was made to close a handle that was already closed.", - 15 => { - "A function was called with an unassigned handle, a closed handle, or handle of an \ - unexpected type." - } - 16 => { - "The host needs to copy data to a guest-allocated buffer, but that buffer is too \ - small." - } - 17 => { - "An internal error occurred. - -This error is reserved to internal consistency checks, and must only be sent if the internal state \ - of the host remains safe after an inconsistency was detected." - } - 18 => { - "Too many handles are currently open, and a new one cannot be created. - -Implementations are free to represent handles as they want, and to enforce limits to limit \ - resources usage." - } - 19 => { - "A key was provided, but the chosen algorithm doesn't support keys. - -This is returned by symmetric operations. - -Many hash functions, in particular, do not support keys without being used in particular \ - constructions. -Blindly ignoring a key provided by mistake while trying to open a context for such as function \ - could cause serious security vulnerabilities. - -These functions must refuse to create the context and return this error instead." - } - 20 => "A key is required for the chosen algorithm, but none was given.", - 21 => { - "The provided authentication tag is invalid or incompatible with the current \ - algorithm. - -This error is returned by decryption functions and tag verification functions. - -Unlike `verification_failed`, this error code is returned when the tag cannot possibly verify for \ - any input." - } - 22 => { - "The requested operation is incompatible with the current scheme. - -For example, the `symmetric_state_encrypt()` function cannot complete if the selected construction \ - is a key derivation function. -This error code will be returned instead." - } - 23 => { - "A nonce is required. - -Most encryption schemes require a nonce. - -In the absence of a nonce, the WASI cryptography module can automatically generate one, if that \ - can be done safely. The nonce can be retrieved later with the \ - `symmetric_state_option_get()` function using the `nonce` parameter. -If automatically generating a nonce cannot be done safely, the module never falls back to an \ - insecure option and requests an explicit nonce by throwing that error." - } - 24 => "The provided nonce doesn't have a correct size for the given cipher.", - 25 => { - "The named option was not set. - -The caller tried to read the value of an option that was not set. -This error is used to make the distinction between an empty option, and an option that was not set \ - and left to its default value." - } - 26 => { - "A key or key pair matching the requested identifier cannot be found using the \ - supplied information. - -This error is returned by a secrets manager via the `keypair_from_id()` function." - } - 27 => { - "The algorithm requires parameters that haven't been set. - -Non-generic options are required and must be given by building an `options` set and giving that \ - object to functions instantiating that algorithm." - } - 28 => { - "A requested computation is not done yet, and additional calls to the function are \ - required. - -Some functions, such as functions generating key pairs and password stretching functions, can take \ - a long time to complete. - -In order to avoid a host call to be blocked for too long, these functions can return prematurely, \ - requiring additional calls with the same parameters until they complete." - } - 29 => { - "Multiple keys have been provided, but they do not share the same type. - -This error is returned when trying to build a key pair from a public key and a secret key that \ - were created for different and incompatible algorithms." - } - 30 => "A managed key or secret expired and cannot be used any more.", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } -} -impl fmt::Debug for CryptoErrno { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("CryptoErrno") - .field("code", &self.0) - .field("name", &self.name()) - .field("message", &self.message()) - .finish() - } -} -impl fmt::Display for CryptoErrno { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} (error {})", self.name(), self.0) - } -} - -#[cfg(feature = "std")] -extern crate std; -#[cfg(feature = "std")] -impl std::error::Error for CryptoErrno {} - -#[repr(transparent)] -#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct KeypairEncoding(u16); -/// Raw bytes. -pub const KEYPAIR_ENCODING_RAW: KeypairEncoding = KeypairEncoding(0); -/// PCSK8/DER encoding. -pub const KEYPAIR_ENCODING_PKCS8: KeypairEncoding = KeypairEncoding(1); -/// PEM encoding. -pub const KEYPAIR_ENCODING_PEM: KeypairEncoding = KeypairEncoding(2); -/// Implementation-defined encoding. -pub const KEYPAIR_ENCODING_LOCAL: KeypairEncoding = KeypairEncoding(3); -impl KeypairEncoding { - pub const fn raw(&self) -> u16 { - self.0 - } - - pub fn name(&self) -> &'static str { - match self.0 { - 0 => "RAW", - 1 => "PKCS8", - 2 => "PEM", - 3 => "LOCAL", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } - - pub fn message(&self) -> &'static str { - match self.0 { - 0 => "Raw bytes.", - 1 => "PCSK8/DER encoding.", - 2 => "PEM encoding.", - 3 => "Implementation-defined encoding.", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } -} -impl fmt::Debug for KeypairEncoding { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("KeypairEncoding") - .field("code", &self.0) - .field("name", &self.name()) - .field("message", &self.message()) - .finish() - } -} - -#[repr(transparent)] -#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct PublickeyEncoding(u16); -/// Raw bytes. -pub const PUBLICKEY_ENCODING_RAW: PublickeyEncoding = PublickeyEncoding(0); -/// PKCS8/DER encoding. -pub const PUBLICKEY_ENCODING_PKCS8: PublickeyEncoding = PublickeyEncoding(1); -/// PEM encoding. -pub const PUBLICKEY_ENCODING_PEM: PublickeyEncoding = PublickeyEncoding(2); -/// SEC-1 encoding. -pub const PUBLICKEY_ENCODING_SEC: PublickeyEncoding = PublickeyEncoding(3); -/// Implementation-defined encoding. -pub const PUBLICKEY_ENCODING_LOCAL: PublickeyEncoding = PublickeyEncoding(4); -impl PublickeyEncoding { - pub const fn raw(&self) -> u16 { - self.0 - } - - pub fn name(&self) -> &'static str { - match self.0 { - 0 => "RAW", - 1 => "PKCS8", - 2 => "PEM", - 3 => "SEC", - 4 => "LOCAL", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } - - pub fn message(&self) -> &'static str { - match self.0 { - 0 => "Raw bytes.", - 1 => "PKCS8/DER encoding.", - 2 => "PEM encoding.", - 3 => "SEC-1 encoding.", - 4 => "Implementation-defined encoding.", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } -} -impl fmt::Debug for PublickeyEncoding { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("PublickeyEncoding") - .field("code", &self.0) - .field("name", &self.name()) - .field("message", &self.message()) - .finish() - } -} - -#[repr(transparent)] -#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct SecretkeyEncoding(u16); -/// Raw bytes. -pub const SECRETKEY_ENCODING_RAW: SecretkeyEncoding = SecretkeyEncoding(0); -/// PKCS8/DER encoding. -pub const SECRETKEY_ENCODING_PKCS8: SecretkeyEncoding = SecretkeyEncoding(1); -/// PEM encoding. -pub const SECRETKEY_ENCODING_PEM: SecretkeyEncoding = SecretkeyEncoding(2); -/// SEC-1 encoding. -pub const SECRETKEY_ENCODING_SEC: SecretkeyEncoding = SecretkeyEncoding(3); -/// Implementation-defined encoding. -pub const SECRETKEY_ENCODING_LOCAL: SecretkeyEncoding = SecretkeyEncoding(4); -impl SecretkeyEncoding { - pub const fn raw(&self) -> u16 { - self.0 - } - - pub fn name(&self) -> &'static str { - match self.0 { - 0 => "RAW", - 1 => "PKCS8", - 2 => "PEM", - 3 => "SEC", - 4 => "LOCAL", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } - - pub fn message(&self) -> &'static str { - match self.0 { - 0 => "Raw bytes.", - 1 => "PKCS8/DER encoding.", - 2 => "PEM encoding.", - 3 => "SEC-1 encoding.", - 4 => "Implementation-defined encoding.", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } -} -impl fmt::Debug for SecretkeyEncoding { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SecretkeyEncoding") - .field("code", &self.0) - .field("name", &self.name()) - .field("message", &self.message()) - .finish() - } -} - -#[repr(transparent)] -#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct SignatureEncoding(u16); -/// Raw bytes. -pub const SIGNATURE_ENCODING_RAW: SignatureEncoding = SignatureEncoding(0); -/// DER encoding. -pub const SIGNATURE_ENCODING_DER: SignatureEncoding = SignatureEncoding(1); -impl SignatureEncoding { - pub const fn raw(&self) -> u16 { - self.0 - } - - pub fn name(&self) -> &'static str { - match self.0 { - 0 => "RAW", - 1 => "DER", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } - - pub fn message(&self) -> &'static str { - match self.0 { - 0 => "Raw bytes.", - 1 => "DER encoding.", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } -} -impl fmt::Debug for SignatureEncoding { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SignatureEncoding") - .field("code", &self.0) - .field("name", &self.name()) - .field("message", &self.message()) - .finish() - } -} - -#[repr(transparent)] -#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct AlgorithmType(u16); -pub const ALGORITHM_TYPE_SIGNATURES: AlgorithmType = AlgorithmType(0); -pub const ALGORITHM_TYPE_SYMMETRIC: AlgorithmType = AlgorithmType(1); -pub const ALGORITHM_TYPE_KEY_EXCHANGE: AlgorithmType = AlgorithmType(2); -impl AlgorithmType { - pub const fn raw(&self) -> u16 { - self.0 - } - - pub fn name(&self) -> &'static str { - match self.0 { - 0 => "SIGNATURES", - 1 => "SYMMETRIC", - 2 => "KEY_EXCHANGE", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } - - pub fn message(&self) -> &'static str { - match self.0 { - 0 => "", - 1 => "", - 2 => "", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } -} -impl fmt::Debug for AlgorithmType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AlgorithmType") - .field("code", &self.0) - .field("name", &self.name()) - .field("message", &self.message()) - .finish() - } -} - -pub type Version = u64; -pub type Size = usize; -pub type Timestamp = u64; -pub type ArrayOutput = u32; -pub type Options = u32; -pub type SecretsManager = u32; -pub type Keypair = u32; -pub type SignatureState = u32; -pub type Signature = u32; -pub type Publickey = u32; -pub type Secretkey = u32; -pub type SignatureVerificationState = u32; -pub type SymmetricState = u32; -pub type SymmetricKey = u32; -pub type SymmetricTag = u32; -#[repr(transparent)] -#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct OptOptionsU(u8); -pub const OPT_OPTIONS_U_SOME: OptOptionsU = OptOptionsU(0); -pub const OPT_OPTIONS_U_NONE: OptOptionsU = OptOptionsU(1); -impl OptOptionsU { - pub const fn raw(&self) -> u8 { - self.0 - } - - pub fn name(&self) -> &'static str { - match self.0 { - 0 => "SOME", - 1 => "NONE", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } - - pub fn message(&self) -> &'static str { - match self.0 { - 0 => "", - 1 => "", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } -} -impl fmt::Debug for OptOptionsU { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("OptOptionsU") - .field("code", &self.0) - .field("name", &self.name()) - .field("message", &self.message()) - .finish() - } -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub union OptOptionsUnion { - pub none: (), - pub some: Options, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct OptOptions { - pub tag: u8, - pub u: OptOptionsUnion, -} - -#[repr(transparent)] -#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct OptSymmetricKeyU(u8); -pub const OPT_SYMMETRIC_KEY_U_SOME: OptSymmetricKeyU = OptSymmetricKeyU(0); -pub const OPT_SYMMETRIC_KEY_U_NONE: OptSymmetricKeyU = OptSymmetricKeyU(1); -impl OptSymmetricKeyU { - pub const fn raw(&self) -> u8 { - self.0 - } - - pub fn name(&self) -> &'static str { - match self.0 { - 0 => "SOME", - 1 => "NONE", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } - - pub fn message(&self) -> &'static str { - match self.0 { - 0 => "", - 1 => "", - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } -} -impl fmt::Debug for OptSymmetricKeyU { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("OptSymmetricKeyU") - .field("code", &self.0) - .field("name", &self.name()) - .field("message", &self.message()) - .finish() - } -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub union OptSymmetricKeyUnion { - pub none: (), - pub some: SymmetricKey, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct OptSymmetricKey { - pub tag: u8, - pub u: OptSymmetricKeyUnion, -} - -pub type U64 = u64; -pub type SignatureKeypair = Keypair; -pub type SignaturePublickey = Publickey; -pub type SignatureSecretkey = Secretkey; -pub type KxKeypair = Keypair; -pub type KxPublickey = Publickey; -pub type KxSecretkey = Secretkey; -/// Create a new object to set non-default options. -/// -/// Example usage: -/// -/// ```rust -/// let options_handle = options_open(AlgorithmType::Symmetric)?; -/// options_set(options_handle, "context", context)?; -/// options_set_u64(options_handle, "threads", 4)?; -/// let state = symmetric_state_open("BLAKE3", None, Some(options_handle))?; -/// options_close(options_handle)?; -/// ``` -pub unsafe fn options_open(algorithm_type: AlgorithmType) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_common::options_open( - algorithm_type.0 as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Options)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Destroy an options object. -/// -/// Objects are reference counted. It is safe to close an object immediately -/// after the last function needing it is called. -pub unsafe fn options_close(handle: Options) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_common::options_close(handle as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Set or update an option. -/// -/// This is used to set algorithm-specific parameters, but also to provide -/// credentials for the secrets management facilities, if required. -/// -/// This function may return `unsupported_option` if an option that doesn't -/// exist for any implemented algorithms is specified. -pub unsafe fn options_set( - handle: Options, - name: &str, - value: *const u8, - value_len: Size, -) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_common::options_set( - handle as i32, - name.as_ptr() as i32, - name.len() as i32, - value as i32, - value_len as i32, - ); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Set or update an integer option. -/// -/// This is used to set algorithm-specific parameters. -/// -/// This function may return `unsupported_option` if an option that doesn't -/// exist for any implemented algorithms is specified. -pub unsafe fn options_set_u64(handle: Options, name: &str, value: u64) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_common::options_set_u64( - handle as i32, - name.as_ptr() as i32, - name.len() as i32, - value as i64, - ); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Set or update a guest-allocated memory that the host can use or return data -/// into. -/// -/// This is for example used to set the scratch buffer required by memory-hard -/// functions. -/// -/// This function may return `unsupported_option` if an option that doesn't -/// exist for any implemented algorithms is specified. -pub unsafe fn options_set_guest_buffer( - handle: Options, - name: &str, - buffer: *mut u8, - buffer_len: Size, -) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_common::options_set_guest_buffer( - handle as i32, - name.as_ptr() as i32, - name.len() as i32, - buffer as i32, - buffer_len as i32, - ); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Return the length of an `array_output` object. -/// -/// This allows a guest to allocate a buffer of the correct size in order to -/// copy the output of a function returning this object type. -pub unsafe fn array_output_len(array_output: ArrayOutput) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_common::array_output_len( - array_output as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Copy the content of an `array_output` object into an application-allocated -/// buffer. -/// -/// Multiple calls to that function can be made in order to consume the data in -/// a streaming fashion, if necessary. -/// -/// The function returns the number of bytes that were actually copied. `0` -/// means that the end of the stream has been reached. The total size always -/// matches the output of `array_output_len()`. -/// -/// The handle is automatically closed after all the data has been consumed. -/// -/// Example usage: -/// -/// ```rust -/// let len = array_output_len(output_handle)?; -/// let mut out = vec![0u8; len]; -/// array_output_pull(output_handle, &mut out)?; -/// ``` -pub unsafe fn array_output_pull( - array_output: ArrayOutput, - buf: *mut u8, - buf_len: Size, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_common::array_output_pull( - array_output as i32, - buf as i32, - buf_len as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Create a context to use a secrets manager. -/// -/// The set of required and supported options is defined by the host. -/// -/// The function returns the `unsupported_feature` error code if secrets -/// management facilities are not supported by the host. This is also an -/// optional import, meaning that the function may not even exist. -pub unsafe fn secrets_manager_open(options: OptOptions) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_common::secrets_manager_open( - &options as *const _ as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const SecretsManager - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Destroy a secrets manager context. -/// -/// The function returns the `unsupported_feature` error code if secrets -/// management facilities are not supported by the host. This is also an -/// optional import, meaning that the function may not even exist. -pub unsafe fn secrets_manager_close(secrets_manager: SecretsManager) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_common::secrets_manager_close(secrets_manager as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Invalidate a managed key or key pair given an identifier and a version. -/// -/// This asks the secrets manager to delete or revoke a stored key, a specific -/// version of a key. -/// -/// `key_version` can be set to a version number, to `version.latest` to -/// invalidate the current version, or to `version.all` to invalidate all -/// versions of a key. -/// -/// The function returns `unsupported_feature` if this operation is not -/// supported by the host, and `not_found` if the identifier and version don't -/// match any existing key. -/// -/// This is an optional import, meaning that the function may not even exist. -pub unsafe fn secrets_manager_invalidate( - secrets_manager: SecretsManager, - key_id: *const u8, - key_id_len: Size, - key_version: Version, -) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_common::secrets_manager_invalidate( - secrets_manager as i32, - key_id as i32, - key_id_len as i32, - key_version as i64, - ); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -pub mod wasi_ephemeral_crypto_common { - #[link(wasm_import_module = "wasi_ephemeral_crypto_common")] - extern "C" { - /// Create a new object to set non-default options. - /// - /// Example usage: - /// - /// ```rust - /// let options_handle = options_open(AlgorithmType::Symmetric)?; - /// options_set(options_handle, "context", context)?; - /// options_set_u64(options_handle, "threads", 4)?; - /// let state = symmetric_state_open("BLAKE3", None, Some(options_handle))?; - /// options_close(options_handle)?; - /// ``` - pub fn options_open(arg0: i32, arg1: i32) -> i32; - /// Destroy an options object. - /// - /// Objects are reference counted. It is safe to close an object - /// immediately after the last function needing it is called. - pub fn options_close(arg0: i32) -> i32; - /// Set or update an option. - /// - /// This is used to set algorithm-specific parameters, but also to - /// provide credentials for the secrets management facilities, if - /// required. - /// - /// This function may return `unsupported_option` if an option that - /// doesn't exist for any implemented algorithms is specified. - pub fn options_set(arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32) -> i32; - /// Set or update an integer option. - /// - /// This is used to set algorithm-specific parameters. - /// - /// This function may return `unsupported_option` if an option that - /// doesn't exist for any implemented algorithms is specified. - pub fn options_set_u64(arg0: i32, arg1: i32, arg2: i32, arg3: i64) -> i32; - /// Set or update a guest-allocated memory that the host can use or - /// return data into. - /// - /// This is for example used to set the scratch buffer required by - /// memory-hard functions. - /// - /// This function may return `unsupported_option` if an option that - /// doesn't exist for any implemented algorithms is specified. - pub fn options_set_guest_buffer( - arg0: i32, - arg1: i32, - arg2: i32, - arg3: i32, - arg4: i32, - ) -> i32; - /// Return the length of an `array_output` object. - /// - /// This allows a guest to allocate a buffer of the correct size in - /// order to copy the output of a function returning this object type. - pub fn array_output_len(arg0: i32, arg1: i32) -> i32; - /// Copy the content of an `array_output` object into an - /// application-allocated buffer. - /// - /// Multiple calls to that function can be made in order to consume the - /// data in a streaming fashion, if necessary. - /// - /// The function returns the number of bytes that were actually copied. - /// `0` means that the end of the stream has been reached. The total - /// size always matches the output of `array_output_len()`. - /// - /// The handle is automatically closed after all the data has been - /// consumed. - /// - /// Example usage: - /// - /// ```rust - /// let len = array_output_len(output_handle)?; - /// let mut out = vec![0u8; len]; - /// array_output_pull(output_handle, &mut out)?; - /// ``` - pub fn array_output_pull(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; - /// __(optional)__ - /// Create a context to use a secrets manager. - /// - /// The set of required and supported options is defined by the host. - /// - /// The function returns the `unsupported_feature` error code if secrets - /// management facilities are not supported by the host. This is - /// also an optional import, meaning that the function may not even - /// exist. - pub fn secrets_manager_open(arg0: i32, arg1: i32) -> i32; - /// __(optional)__ - /// Destroy a secrets manager context. - /// - /// The function returns the `unsupported_feature` error code if secrets - /// management facilities are not supported by the host. This is - /// also an optional import, meaning that the function may not even - /// exist. - pub fn secrets_manager_close(arg0: i32) -> i32; - /// __(optional)__ - /// Invalidate a managed key or key pair given an identifier and a - /// version. - /// - /// This asks the secrets manager to delete or revoke a stored key, a - /// specific version of a key. - /// - /// `key_version` can be set to a version number, to `version.latest` to - /// invalidate the current version, or to `version.all` to invalidate - /// all versions of a key. - /// - /// The function returns `unsupported_feature` if this operation is not - /// supported by the host, and `not_found` if the identifier and version - /// don't match any existing key. - /// - /// This is an optional import, meaning that the function may not even - /// exist. - pub fn secrets_manager_invalidate(arg0: i32, arg1: i32, arg2: i32, arg3: i64) -> i32; - } -} -/// Generate a new key pair. -/// -/// Internally, a key pair stores the supplied algorithm and optional -/// parameters. -/// -/// Trying to use that key pair with different parameters will throw an -/// `invalid_key` error. -/// -/// This function may return `$crypto_errno.unsupported_feature` if key -/// generation is not supported by the host for the chosen algorithm. -/// -/// The function may also return `unsupported_algorithm` if the algorithm is not -/// supported by the host. -/// -/// Finally, if generating that type of key pair is an expensive operation, the -/// function may return `in_progress`. In that case, the guest should retry with -/// the same parameters until the function completes. -/// -/// Example usage: -/// -/// ```rust -/// let kp_handle = -/// ctx.keypair_generate(AlgorithmType::Signatures, "RSA_PKCS1_2048_SHA256", None)?; -/// ``` -pub unsafe fn keypair_generate( - algorithm_type: AlgorithmType, - algorithm: &str, - options: OptOptions, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_generate( - algorithm_type.0 as i32, - algorithm.as_ptr() as i32, - algorithm.len() as i32, - &options as *const _ as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Keypair)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Import a key pair. -/// -/// This function creates a `keypair` object from existing material. -/// -/// It may return `unsupported_algorithm` if the encoding scheme is not -/// supported, or `invalid_key` if the key cannot be decoded. -/// -/// The function may also return `unsupported_algorithm` if the algorithm is not -/// supported by the host. -/// -/// Example usage: -/// -/// ```rust -/// let kp_handle = ctx.keypair_import( -/// AlgorithmType::Signatures, -/// "RSA_PKCS1_2048_SHA256", -/// KeypairEncoding::PKCS8, -/// )?; -/// ``` -pub unsafe fn keypair_import( - algorithm_type: AlgorithmType, - algorithm: &str, - encoded: *const u8, - encoded_len: Size, - encoding: KeypairEncoding, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_import( - algorithm_type.0 as i32, - algorithm.as_ptr() as i32, - algorithm.len() as i32, - encoded as i32, - encoded_len as i32, - encoding.0 as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Keypair)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Generate a new managed key pair. -/// -/// The key pair is generated and stored by the secrets management facilities. -/// -/// It may be used through its identifier, but the host may not allow it to be -/// exported. -/// -/// The function returns the `unsupported_feature` error code if secrets -/// management facilities are not supported by the host, -/// or `unsupported_algorithm` if a key cannot be created for the chosen -/// algorithm. -/// -/// The function may also return `unsupported_algorithm` if the algorithm is not -/// supported by the host. -/// -/// This is also an optional import, meaning that the function may not even -/// exist. -pub unsafe fn keypair_generate_managed( - secrets_manager: SecretsManager, - algorithm_type: AlgorithmType, - algorithm: &str, - options: OptOptions, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_generate_managed( - secrets_manager as i32, - algorithm_type.0 as i32, - algorithm.as_ptr() as i32, - algorithm.len() as i32, - &options as *const _ as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Keypair)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Store a key pair into the secrets manager. -/// -/// On success, the function stores the key pair identifier into `$kp_id`, -/// into which up to `$kp_id_max_len` can be written. -/// -/// The function returns `overflow` if the supplied buffer is too small. -pub unsafe fn keypair_store_managed( - secrets_manager: SecretsManager, - kp: Keypair, - kp_id: *mut u8, - kp_id_max_len: Size, -) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_store_managed( - secrets_manager as i32, - kp as i32, - kp_id as i32, - kp_id_max_len as i32, - ); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Replace a managed key pair. -/// -/// This function crates a new version of a managed key pair, by replacing -/// `$kp_old` with `$kp_new`. -/// -/// It does several things: -/// -/// - The key identifier for `$kp_new` is set to the one of `$kp_old`. -/// - A new, unique version identifier is assigned to `$kp_new`. This version -/// will be equivalent to using `$version_latest` until the key is replaced. -/// - The `$kp_old` handle is closed. -/// -/// Both keys must share the same algorithm and have compatible parameters. If -/// this is not the case, `incompatible_keys` is returned. -/// -/// The function may also return the `unsupported_feature` error code if secrets -/// management facilities are not supported by the host, or if keys cannot be -/// rotated. -/// -/// Finally, `prohibited_operation` can be returned if `$kp_new` wasn't created -/// by the secrets manager, and the secrets manager prohibits imported keys. -/// -/// If the operation succeeded, the new version is returned. -/// -/// This is an optional import, meaning that the function may not even exist. -pub unsafe fn keypair_replace_managed( - secrets_manager: SecretsManager, - kp_old: Keypair, - kp_new: Keypair, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_replace_managed( - secrets_manager as i32, - kp_old as i32, - kp_new as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Version)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Return the key pair identifier and version of a managed key pair. -/// -/// If the key pair is not managed, `unsupported_feature` is returned instead. -/// -/// This is an optional import, meaning that the function may not even exist. -pub unsafe fn keypair_id( - kp: Keypair, - kp_id: *mut u8, - kp_id_max_len: Size, -) -> Result<(Size, Version), CryptoErrno> { - let mut rp0 = MaybeUninit::::uninit(); - let mut rp1 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_id( - kp as i32, - kp_id as i32, - kp_id_max_len as i32, - rp0.as_mut_ptr() as i32, - rp1.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(( - core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size), - core::ptr::read(rp1.as_mut_ptr() as i32 as *const Version), - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Return a managed key pair from a key identifier. -/// -/// `kp_version` can be set to `version_latest` to retrieve the most recent -/// version of a key pair. -/// -/// If no key pair matching the provided information is found, `not_found` is -/// returned instead. -/// -/// This is an optional import, meaning that the function may not even exist. -/// ``` -pub unsafe fn keypair_from_id( - secrets_manager: SecretsManager, - kp_id: *const u8, - kp_id_len: Size, - kp_version: Version, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_from_id( - secrets_manager as i32, - kp_id as i32, - kp_id_len as i32, - kp_version as i64, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Keypair)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Create a key pair from a public key and a secret key. -pub unsafe fn keypair_from_pk_and_sk( - publickey: Publickey, - secretkey: Secretkey, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_from_pk_and_sk( - publickey as i32, - secretkey as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Keypair)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Export a key pair as the given encoding format. -/// -/// May return `prohibited_operation` if this operation is denied or -/// `unsupported_encoding` if the encoding is not supported. -pub unsafe fn keypair_export( - kp: Keypair, - encoding: KeypairEncoding, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_export( - kp as i32, - encoding.0 as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const ArrayOutput - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Get the public key of a key pair. -pub unsafe fn keypair_publickey(kp: Keypair) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_publickey( - kp as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Publickey)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Get the secret key of a key pair. -pub unsafe fn keypair_secretkey(kp: Keypair) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_secretkey( - kp as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Secretkey)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Destroy a key pair. -/// -/// The host will automatically wipe traces of the secret key from memory. -/// -/// If this is a managed key, the key will not be removed from persistent -/// storage, and can be reconstructed later using the key identifier. -pub unsafe fn keypair_close(kp: Keypair) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_asymmetric_common::keypair_close(kp as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Import a public key. -/// -/// The function may return `unsupported_encoding` if importing from the given -/// format is not implemented or incompatible with the key type. -/// -/// It may also return `invalid_key` if the key doesn't appear to match the -/// supplied algorithm. -/// -/// Finally, the function may return `unsupported_algorithm` if the algorithm is -/// not supported by the host. -/// -/// Example usage: -/// -/// ```rust -/// let pk_handle = -/// ctx.publickey_import(AlgorithmType::Signatures, encoded, PublicKeyEncoding::Sec)?; -/// ``` -pub unsafe fn publickey_import( - algorithm_type: AlgorithmType, - algorithm: &str, - encoded: *const u8, - encoded_len: Size, - encoding: PublickeyEncoding, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::publickey_import( - algorithm_type.0 as i32, - algorithm.as_ptr() as i32, - algorithm.len() as i32, - encoded as i32, - encoded_len as i32, - encoding.0 as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Publickey)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Export a public key as the given encoding format. -/// -/// May return `unsupported_encoding` if the encoding is not supported. -pub unsafe fn publickey_export( - pk: Publickey, - encoding: PublickeyEncoding, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::publickey_export( - pk as i32, - encoding.0 as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const ArrayOutput - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Check that a public key is valid and in canonical form. -/// -/// This function may perform stricter checks than those made during importation -/// at the expense of additional CPU cycles. -/// -/// The function returns `invalid_key` if the public key didn't pass the checks. -pub unsafe fn publickey_verify(pk: Publickey) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_asymmetric_common::publickey_verify(pk as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Compute the public key for a secret key. -pub unsafe fn publickey_from_secretkey(sk: Secretkey) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::publickey_from_secretkey( - sk as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Publickey)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Destroy a public key. -/// -/// Objects are reference counted. It is safe to close an object immediately -/// after the last function needing it is called. -pub unsafe fn publickey_close(pk: Publickey) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_asymmetric_common::publickey_close(pk as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Import a secret key. -/// -/// The function may return `unsupported_encoding` if importing from the given -/// format is not implemented or incompatible with the key type. -/// -/// It may also return `invalid_key` if the key doesn't appear to match the -/// supplied algorithm. -/// -/// Finally, the function may return `unsupported_algorithm` if the algorithm is -/// not supported by the host. -/// -/// Example usage: -/// -/// ```rust -/// let pk_handle = ctx.secretkey_import(AlgorithmType::KX, encoded, SecretKeyEncoding::Raw)?; -/// ``` -pub unsafe fn secretkey_import( - algorithm_type: AlgorithmType, - algorithm: &str, - encoded: *const u8, - encoded_len: Size, - encoding: SecretkeyEncoding, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::secretkey_import( - algorithm_type.0 as i32, - algorithm.as_ptr() as i32, - algorithm.len() as i32, - encoded as i32, - encoded_len as i32, - encoding.0 as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Secretkey)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Export a secret key as the given encoding format. -/// -/// May return `unsupported_encoding` if the encoding is not supported. -pub unsafe fn secretkey_export( - sk: Secretkey, - encoding: SecretkeyEncoding, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_asymmetric_common::secretkey_export( - sk as i32, - encoding.0 as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const ArrayOutput - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Destroy a secret key. -/// -/// Objects are reference counted. It is safe to close an object immediately -/// after the last function needing it is called. -pub unsafe fn secretkey_close(sk: Secretkey) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_asymmetric_common::secretkey_close(sk as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -pub mod wasi_ephemeral_crypto_asymmetric_common { - #[link(wasm_import_module = "wasi_ephemeral_crypto_asymmetric_common")] - extern "C" { - /// Generate a new key pair. - /// - /// Internally, a key pair stores the supplied algorithm and optional - /// parameters. - /// - /// Trying to use that key pair with different parameters will throw an - /// `invalid_key` error. - /// - /// This function may return `$crypto_errno.unsupported_feature` if key - /// generation is not supported by the host for the chosen algorithm. - /// - /// The function may also return `unsupported_algorithm` if the - /// algorithm is not supported by the host. - /// - /// Finally, if generating that type of key pair is an expensive - /// operation, the function may return `in_progress`. - /// In that case, the guest should retry with the same parameters until - /// the function completes. - /// - /// Example usage: - /// - /// ```rust - /// let kp_handle = - /// ctx.keypair_generate(AlgorithmType::Signatures, "RSA_PKCS1_2048_SHA256", None)?; - /// ``` - pub fn keypair_generate(arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32) -> i32; - /// Import a key pair. - /// - /// This function creates a `keypair` object from existing material. - /// - /// It may return `unsupported_algorithm` if the encoding scheme is not - /// supported, or `invalid_key` if the key cannot be decoded. - /// - /// The function may also return `unsupported_algorithm` if the - /// algorithm is not supported by the host. - /// - /// Example usage: - /// - /// ```rust - /// let kp_handle = ctx.keypair_import( - /// AlgorithmType::Signatures, - /// "RSA_PKCS1_2048_SHA256", - /// KeypairEncoding::PKCS8, - /// )?; - /// ``` - pub fn keypair_import( - arg0: i32, - arg1: i32, - arg2: i32, - arg3: i32, - arg4: i32, - arg5: i32, - arg6: i32, - ) -> i32; - /// __(optional)__ - /// Generate a new managed key pair. - /// - /// The key pair is generated and stored by the secrets management - /// facilities. - /// - /// It may be used through its identifier, but the host may not allow it - /// to be exported. - /// - /// The function returns the `unsupported_feature` error code if secrets - /// management facilities are not supported by the host, - /// or `unsupported_algorithm` if a key cannot be created for the chosen - /// algorithm. - /// - /// The function may also return `unsupported_algorithm` if the - /// algorithm is not supported by the host. - /// - /// This is also an optional import, meaning that the function may not - /// even exist. - pub fn keypair_generate_managed( - arg0: i32, - arg1: i32, - arg2: i32, - arg3: i32, - arg4: i32, - arg5: i32, - ) -> i32; - /// __(optional)__ - /// Store a key pair into the secrets manager. - /// - /// On success, the function stores the key pair identifier into - /// `$kp_id`, into which up to `$kp_id_max_len` can be written. - /// - /// The function returns `overflow` if the supplied buffer is too small. - pub fn keypair_store_managed(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; - /// __(optional)__ - /// Replace a managed key pair. - /// - /// This function crates a new version of a managed key pair, by - /// replacing `$kp_old` with `$kp_new`. - /// - /// It does several things: - /// - /// - The key identifier for `$kp_new` is set to the one of `$kp_old`. - /// - A new, unique version identifier is assigned to `$kp_new`. This - /// version will be equivalent to using `$version_latest` until the - /// key is replaced. - /// - The `$kp_old` handle is closed. - /// - /// Both keys must share the same algorithm and have compatible - /// parameters. If this is not the case, `incompatible_keys` is - /// returned. - /// - /// The function may also return the `unsupported_feature` error code if - /// secrets management facilities are not supported by the host, - /// or if keys cannot be rotated. - /// - /// Finally, `prohibited_operation` can be returned if `$kp_new` wasn't - /// created by the secrets manager, and the secrets manager prohibits - /// imported keys. - /// - /// If the operation succeeded, the new version is returned. - /// - /// This is an optional import, meaning that the function may not even - /// exist. - pub fn keypair_replace_managed(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; - /// __(optional)__ - /// Return the key pair identifier and version of a managed key pair. - /// - /// If the key pair is not managed, `unsupported_feature` is returned - /// instead. - /// - /// This is an optional import, meaning that the function may not even - /// exist. - pub fn keypair_id(arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32) -> i32; - /// __(optional)__ - /// Return a managed key pair from a key identifier. - /// - /// `kp_version` can be set to `version_latest` to retrieve the most - /// recent version of a key pair. - /// - /// If no key pair matching the provided information is found, - /// `not_found` is returned instead. - /// - /// This is an optional import, meaning that the function may not even - /// exist. ``` - pub fn keypair_from_id(arg0: i32, arg1: i32, arg2: i32, arg3: i64, arg4: i32) -> i32; - /// Create a key pair from a public key and a secret key. - pub fn keypair_from_pk_and_sk(arg0: i32, arg1: i32, arg2: i32) -> i32; - /// Export a key pair as the given encoding format. - /// - /// May return `prohibited_operation` if this operation is denied or - /// `unsupported_encoding` if the encoding is not supported. - pub fn keypair_export(arg0: i32, arg1: i32, arg2: i32) -> i32; - /// Get the public key of a key pair. - pub fn keypair_publickey(arg0: i32, arg1: i32) -> i32; - /// Get the secret key of a key pair. - pub fn keypair_secretkey(arg0: i32, arg1: i32) -> i32; - /// Destroy a key pair. - /// - /// The host will automatically wipe traces of the secret key from - /// memory. - /// - /// If this is a managed key, the key will not be removed from - /// persistent storage, and can be reconstructed later using the key - /// identifier. - pub fn keypair_close(arg0: i32) -> i32; - /// Import a public key. - /// - /// The function may return `unsupported_encoding` if importing from the - /// given format is not implemented or incompatible with the key type. - /// - /// It may also return `invalid_key` if the key doesn't appear to match - /// the supplied algorithm. - /// - /// Finally, the function may return `unsupported_algorithm` if the - /// algorithm is not supported by the host. - /// - /// Example usage: - /// - /// ```rust - /// let pk_handle = - /// ctx.publickey_import(AlgorithmType::Signatures, encoded, PublicKeyEncoding::Sec)?; - /// ``` - pub fn publickey_import( - arg0: i32, - arg1: i32, - arg2: i32, - arg3: i32, - arg4: i32, - arg5: i32, - arg6: i32, - ) -> i32; - /// Export a public key as the given encoding format. - /// - /// May return `unsupported_encoding` if the encoding is not supported. - pub fn publickey_export(arg0: i32, arg1: i32, arg2: i32) -> i32; - /// Check that a public key is valid and in canonical form. - /// - /// This function may perform stricter checks than those made during - /// importation at the expense of additional CPU cycles. - /// - /// The function returns `invalid_key` if the public key didn't pass the - /// checks. - pub fn publickey_verify(arg0: i32) -> i32; - /// Compute the public key for a secret key. - pub fn publickey_from_secretkey(arg0: i32, arg1: i32) -> i32; - /// Destroy a public key. - /// - /// Objects are reference counted. It is safe to close an object - /// immediately after the last function needing it is called. - pub fn publickey_close(arg0: i32) -> i32; - /// Import a secret key. - /// - /// The function may return `unsupported_encoding` if importing from the - /// given format is not implemented or incompatible with the key type. - /// - /// It may also return `invalid_key` if the key doesn't appear to match - /// the supplied algorithm. - /// - /// Finally, the function may return `unsupported_algorithm` if the - /// algorithm is not supported by the host. - /// - /// Example usage: - /// - /// ```rust - /// let pk_handle = ctx.secretkey_import(AlgorithmType::KX, encoded, SecretKeyEncoding::Raw)?; - /// ``` - pub fn secretkey_import( - arg0: i32, - arg1: i32, - arg2: i32, - arg3: i32, - arg4: i32, - arg5: i32, - arg6: i32, - ) -> i32; - /// Export a secret key as the given encoding format. - /// - /// May return `unsupported_encoding` if the encoding is not supported. - pub fn secretkey_export(arg0: i32, arg1: i32, arg2: i32) -> i32; - /// Destroy a secret key. - /// - /// Objects are reference counted. It is safe to close an object - /// immediately after the last function needing it is called. - pub fn secretkey_close(arg0: i32) -> i32; - } -} -/// Export a signature. -/// -/// This function exports a signature object using the specified encoding. -/// -/// May return `unsupported_encoding` if the signature cannot be encoded into -/// the given format. -pub unsafe fn signature_export( - signature: Signature, - encoding: SignatureEncoding, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_signatures::signature_export( - signature as i32, - encoding.0 as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const ArrayOutput - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Create a signature object. -/// -/// This object can be used along with a public key to verify an existing -/// signature. -/// -/// It may return `invalid_signature` if the signature is invalid or -/// incompatible with the specified algorithm, as well as `unsupported_encoding` -/// if the encoding is not compatible with the signature type. -/// -/// The function may also return `unsupported_algorithm` if the algorithm is not -/// supported by the host. -/// -/// Example usage: -/// -/// ```rust -/// let signature_handle = -/// ctx.signature_import("ECDSA_P256_SHA256", SignatureEncoding::DER, encoded)?; -/// ``` -pub unsafe fn signature_import( - algorithm: &str, - encoded: *const u8, - encoded_len: Size, - encoding: SignatureEncoding, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_signatures::signature_import( - algorithm.as_ptr() as i32, - algorithm.len() as i32, - encoded as i32, - encoded_len as i32, - encoding.0 as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Signature)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Create a new state to collect data to compute a signature on. -/// -/// This function allows data to be signed to be supplied in a streaming -/// fashion. -/// -/// The state is not closed and can be used after a signature has been computed, -/// allowing incremental updates by calling `signature_state_update()` again -/// afterwards. -/// -/// Example usage - signature creation -/// -/// ```rust -/// let kp_handle = ctx.keypair_import( -/// AlgorithmType::Signatures, -/// "Ed25519ph", -/// keypair, -/// KeypairEncoding::Raw, -/// )?; -/// let state_handle = ctx.signature_state_open(kp_handle)?; -/// ctx.signature_state_update(state_handle, b"message part 1")?; -/// ctx.signature_state_update(state_handle, b"message part 2")?; -/// let sig_handle = ctx.signature_state_sign(state_handle)?; -/// let raw_sig = ctx.signature_export(sig_handle, SignatureEncoding::Raw)?; -/// ``` -pub unsafe fn signature_state_open(kp: SignatureKeypair) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = - wasi_ephemeral_crypto_signatures::signature_state_open(kp as i32, rp0.as_mut_ptr() as i32); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const SignatureState - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Absorb data into the signature state. -/// -/// This function may return `unsupported_feature` is the selected algorithm -/// doesn't support incremental updates. -pub unsafe fn signature_state_update( - state: SignatureState, - input: *const u8, - input_len: Size, -) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_signatures::signature_state_update( - state as i32, - input as i32, - input_len as i32, - ); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Compute a signature for all the data collected up to that point. -/// -/// The function can be called multiple times for incremental signing. -pub unsafe fn signature_state_sign(state: SignatureState) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_signatures::signature_state_sign( - state as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const ArrayOutput - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Destroy a signature state. -/// -/// Objects are reference counted. It is safe to close an object immediately -/// after the last function needing it is called. -/// -/// Note that closing a signature state doesn't close or invalidate the key pair -/// object, that be reused for further signatures. -pub unsafe fn signature_state_close(state: SignatureState) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_signatures::signature_state_close(state as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Create a new state to collect data to verify a signature on. -/// -/// This is the verification counterpart of `signature_state`. -/// -/// Data can be injected using `signature_verification_state_update()`, and the -/// state is not closed after a verification, allowing incremental verification. -/// -/// Example usage - signature verification: -/// -/// ```rust -/// let pk_handle = ctx.publickey_import( -/// AlgorithmType::Signatures, -/// "ECDSA_P256_SHA256", -/// encoded_pk, -/// PublicKeyEncoding::Sec, -/// )?; -/// let signature_handle = ctx.signature_import( -/// AlgorithmType::Signatures, -/// "ECDSA_P256_SHA256", -/// encoded_sig, -/// SignatureEncoding::Der, -/// )?; -/// let state_handle = ctx.signature_verification_state_open(pk_handle)?; -/// ctx.signature_verification_state_update(state_handle, "message")?; -/// ctx.signature_verification_state_verify(signature_handle)?; -/// ``` -pub unsafe fn signature_verification_state_open( - kp: SignaturePublickey, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_signatures::signature_verification_state_open( - kp as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const SignatureVerificationState - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Absorb data into the signature verification state. -/// -/// This function may return `unsupported_feature` is the selected algorithm -/// doesn't support incremental updates. -pub unsafe fn signature_verification_state_update( - state: SignatureVerificationState, - input: *const u8, - input_len: Size, -) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_signatures::signature_verification_state_update( - state as i32, - input as i32, - input_len as i32, - ); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Check that the given signature is verifies for the data collected up to that -/// point point. -/// -/// The state is not closed and can absorb more data to allow for incremental -/// verification. -/// -/// The function returns `invalid_signature` if the signature doesn't appear to -/// be valid. -pub unsafe fn signature_verification_state_verify( - state: SignatureVerificationState, - signature: Signature, -) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_signatures::signature_verification_state_verify( - state as i32, - signature as i32, - ); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Destroy a signature verification state. -/// -/// Objects are reference counted. It is safe to close an object immediately -/// after the last function needing it is called. -/// -/// Note that closing a signature state doesn't close or invalidate the public -/// key object, that be reused for further verifications. -pub unsafe fn signature_verification_state_close( - state: SignatureVerificationState, -) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_signatures::signature_verification_state_close(state as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Destroy a signature. -/// -/// Objects are reference counted. It is safe to close an object immediately -/// after the last function needing it is called. -pub unsafe fn signature_close(signature: Signature) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_signatures::signature_close(signature as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -pub mod wasi_ephemeral_crypto_signatures { - #[link(wasm_import_module = "wasi_ephemeral_crypto_signatures")] - extern "C" { - /// Export a signature. - /// - /// This function exports a signature object using the specified - /// encoding. - /// - /// May return `unsupported_encoding` if the signature cannot be encoded - /// into the given format. - pub fn signature_export(arg0: i32, arg1: i32, arg2: i32) -> i32; - /// Create a signature object. - /// - /// This object can be used along with a public key to verify an - /// existing signature. - /// - /// It may return `invalid_signature` if the signature is invalid or - /// incompatible with the specified algorithm, as well as - /// `unsupported_encoding` if the encoding is not compatible with the - /// signature type. - /// - /// The function may also return `unsupported_algorithm` if the - /// algorithm is not supported by the host. - /// - /// Example usage: - /// - /// ```rust - /// let signature_handle = - /// ctx.signature_import("ECDSA_P256_SHA256", SignatureEncoding::DER, encoded)?; - /// ``` - pub fn signature_import( - arg0: i32, - arg1: i32, - arg2: i32, - arg3: i32, - arg4: i32, - arg5: i32, - ) -> i32; - /// Create a new state to collect data to compute a signature on. - /// - /// This function allows data to be signed to be supplied in a streaming - /// fashion. - /// - /// The state is not closed and can be used after a signature has been - /// computed, allowing incremental updates by calling - /// `signature_state_update()` again afterwards. - /// - /// Example usage - signature creation - /// - /// ```rust - /// let kp_handle = ctx.keypair_import( - /// AlgorithmType::Signatures, - /// "Ed25519ph", - /// keypair, - /// KeypairEncoding::Raw, - /// )?; - /// let state_handle = ctx.signature_state_open(kp_handle)?; - /// ctx.signature_state_update(state_handle, b"message part 1")?; - /// ctx.signature_state_update(state_handle, b"message part 2")?; - /// let sig_handle = ctx.signature_state_sign(state_handle)?; - /// let raw_sig = ctx.signature_export(sig_handle, SignatureEncoding::Raw)?; - /// ``` - pub fn signature_state_open(arg0: i32, arg1: i32) -> i32; - /// Absorb data into the signature state. - /// - /// This function may return `unsupported_feature` is the selected - /// algorithm doesn't support incremental updates. - pub fn signature_state_update(arg0: i32, arg1: i32, arg2: i32) -> i32; - /// Compute a signature for all the data collected up to that point. - /// - /// The function can be called multiple times for incremental signing. - pub fn signature_state_sign(arg0: i32, arg1: i32) -> i32; - /// Destroy a signature state. - /// - /// Objects are reference counted. It is safe to close an object - /// immediately after the last function needing it is called. - /// - /// Note that closing a signature state doesn't close or invalidate the - /// key pair object, that be reused for further signatures. - pub fn signature_state_close(arg0: i32) -> i32; - /// Create a new state to collect data to verify a signature on. - /// - /// This is the verification counterpart of `signature_state`. - /// - /// Data can be injected using `signature_verification_state_update()`, - /// and the state is not closed after a verification, allowing - /// incremental verification. - /// - /// Example usage - signature verification: - /// - /// ```rust - /// let pk_handle = ctx.publickey_import( - /// AlgorithmType::Signatures, - /// "ECDSA_P256_SHA256", - /// encoded_pk, - /// PublicKeyEncoding::Sec, - /// )?; - /// let signature_handle = ctx.signature_import( - /// AlgorithmType::Signatures, - /// "ECDSA_P256_SHA256", - /// encoded_sig, - /// SignatureEncoding::Der, - /// )?; - /// let state_handle = ctx.signature_verification_state_open(pk_handle)?; - /// ctx.signature_verification_state_update(state_handle, "message")?; - /// ctx.signature_verification_state_verify(signature_handle)?; - /// ``` - pub fn signature_verification_state_open(arg0: i32, arg1: i32) -> i32; - /// Absorb data into the signature verification state. - /// - /// This function may return `unsupported_feature` is the selected - /// algorithm doesn't support incremental updates. - pub fn signature_verification_state_update(arg0: i32, arg1: i32, arg2: i32) -> i32; - /// Check that the given signature is verifies for the data collected up - /// to that point point. - /// - /// The state is not closed and can absorb more data to allow for - /// incremental verification. - /// - /// The function returns `invalid_signature` if the signature doesn't - /// appear to be valid. - pub fn signature_verification_state_verify(arg0: i32, arg1: i32) -> i32; - /// Destroy a signature verification state. - /// - /// Objects are reference counted. It is safe to close an object - /// immediately after the last function needing it is called. - /// - /// Note that closing a signature state doesn't close or invalidate the - /// public key object, that be reused for further verifications. - pub fn signature_verification_state_close(arg0: i32) -> i32; - /// Destroy a signature. - /// - /// Objects are reference counted. It is safe to close an object - /// immediately after the last function needing it is called. - pub fn signature_close(arg0: i32) -> i32; - } -} -/// Generate a new symmetric key for a given algorithm. -/// -/// `options` can be `None` to use the default parameters, or an -/// algoritm-specific set of parameters to override. -/// -/// This function may return `unsupported_feature` if key generation is not -/// supported by the host for the chosen algorithm, or `unsupported_algorithm` -/// if the algorithm is not supported by the host. -pub unsafe fn symmetric_key_generate( - algorithm: &str, - options: OptOptions, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_generate( - algorithm.as_ptr() as i32, - algorithm.len() as i32, - &options as *const _ as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const SymmetricKey - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Create a symmetric key from raw material. -/// -/// The algorithm is internally stored along with the key, and trying to use the -/// key with an operation expecting a different algorithm will return -/// `invalid_key`. -/// -/// The function may also return `unsupported_algorithm` if the algorithm is not -/// supported by the host. -pub unsafe fn symmetric_key_import( - algorithm: &str, - raw: *const u8, - raw_len: Size, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_import( - algorithm.as_ptr() as i32, - algorithm.len() as i32, - raw as i32, - raw_len as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const SymmetricKey - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Export a symmetric key as raw material. -/// -/// This is mainly useful to export a managed key. -/// -/// May return `prohibited_operation` if this operation is denied. -pub unsafe fn symmetric_key_export( - symmetric_key: SymmetricKey, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_export( - symmetric_key as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const ArrayOutput - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Destroy a symmetric key. -/// -/// Objects are reference counted. It is safe to close an object immediately -/// after the last function needing it is called. -pub unsafe fn symmetric_key_close(symmetric_key: SymmetricKey) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_close(symmetric_key as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Generate a new managed symmetric key. -/// -/// The key is generated and stored by the secrets management facilities. -/// -/// It may be used through its identifier, but the host may not allow it to be -/// exported. -/// -/// The function returns the `unsupported_feature` error code if secrets -/// management facilities are not supported by the host, -/// or `unsupported_algorithm` if a key cannot be created for the chosen -/// algorithm. -/// -/// The function may also return `unsupported_algorithm` if the algorithm is not -/// supported by the host. -/// -/// This is also an optional import, meaning that the function may not even -/// exist. -pub unsafe fn symmetric_key_generate_managed( - secrets_manager: SecretsManager, - algorithm: &str, - options: OptOptions, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_generate_managed( - secrets_manager as i32, - algorithm.as_ptr() as i32, - algorithm.len() as i32, - &options as *const _ as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const SymmetricKey - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Store a symmetric key into the secrets manager. -/// -/// On success, the function stores the key identifier into `$symmetric_key_id`, -/// into which up to `$symmetric_key_id_max_len` can be written. -/// -/// The function returns `overflow` if the supplied buffer is too small. -pub unsafe fn symmetric_key_store_managed( - secrets_manager: SecretsManager, - symmetric_key: SymmetricKey, - symmetric_key_id: *mut u8, - symmetric_key_id_max_len: Size, -) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_store_managed( - secrets_manager as i32, - symmetric_key as i32, - symmetric_key_id as i32, - symmetric_key_id_max_len as i32, - ); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Replace a managed symmetric key. -/// -/// This function crates a new version of a managed symmetric key, by replacing -/// `$kp_old` with `$kp_new`. -/// -/// It does several things: -/// -/// - The key identifier for `$symmetric_key_new` is set to the one of -/// `$symmetric_key_old`. -/// - A new, unique version identifier is assigned to `$kp_new`. This version -/// will be equivalent to using `$version_latest` until the key is replaced. -/// - The `$symmetric_key_old` handle is closed. -/// -/// Both keys must share the same algorithm and have compatible parameters. If -/// this is not the case, `incompatible_keys` is returned. -/// -/// The function may also return the `unsupported_feature` error code if secrets -/// management facilities are not supported by the host, or if keys cannot be -/// rotated. -/// -/// Finally, `prohibited_operation` can be returned if `$symmetric_key_new` -/// wasn't created by the secrets manager, and the secrets manager prohibits -/// imported keys. -/// -/// If the operation succeeded, the new version is returned. -/// -/// This is an optional import, meaning that the function may not even exist. -pub unsafe fn symmetric_key_replace_managed( - secrets_manager: SecretsManager, - symmetric_key_old: SymmetricKey, - symmetric_key_new: SymmetricKey, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_replace_managed( - secrets_manager as i32, - symmetric_key_old as i32, - symmetric_key_new as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Version)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Return the key identifier and version of a managed symmetric key. -/// -/// If the key is not managed, `unsupported_feature` is returned instead. -/// -/// This is an optional import, meaning that the function may not even exist. -pub unsafe fn symmetric_key_id( - symmetric_key: SymmetricKey, - symmetric_key_id: *mut u8, - symmetric_key_id_max_len: Size, -) -> Result<(Size, Version), CryptoErrno> { - let mut rp0 = MaybeUninit::::uninit(); - let mut rp1 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_id( - symmetric_key as i32, - symmetric_key_id as i32, - symmetric_key_id_max_len as i32, - rp0.as_mut_ptr() as i32, - rp1.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(( - core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size), - core::ptr::read(rp1.as_mut_ptr() as i32 as *const Version), - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// __(optional)__ -/// Return a managed symmetric key from a key identifier. -/// -/// `kp_version` can be set to `version_latest` to retrieve the most recent -/// version of a symmetric key. -/// -/// If no key matching the provided information is found, `not_found` is -/// returned instead. -/// -/// This is an optional import, meaning that the function may not even exist. -pub unsafe fn symmetric_key_from_id( - secrets_manager: SecretsManager, - symmetric_key_id: *const u8, - symmetric_key_id_len: Size, - symmetric_key_version: Version, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_key_from_id( - secrets_manager as i32, - symmetric_key_id as i32, - symmetric_key_id_len as i32, - symmetric_key_version as i64, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const SymmetricKey - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Create a new state to aborb and produce data using symmetric operations. -/// -/// The state remains valid after every operation in order to support -/// incremental updates. -/// -/// The function has two optional parameters: a key and an options set. -/// -/// It will fail with a `key_not_supported` error code if a key was provided but -/// the chosen algorithm doesn't natively support keying. -/// -/// On the other hand, if a key is required, but was not provided, a -/// `key_required` error will be thrown. -/// -/// Some algorithms may require additional parameters. They have to be supplied -/// as an options set: -/// -/// ```rust -/// let options_handle = ctx.options_open()?; -/// ctx.options_set("context", b"My application")?; -/// ctx.options_set_u64("fanout", 16)?; -/// let state_handle = ctx.symmetric_state_open("BLAKE2b-512", None, Some(options_handle))?; -/// ``` -/// -/// If some parameters are mandatory but were not set, the `parameters_missing` -/// error code will be returned. -/// -/// A notable exception is the `nonce` parameter, that is common to most AEAD -/// constructions. -/// -/// If a nonce is required but was not supplied: -/// -/// - If it is safe to do so, the host will automatically generate a nonce. This -/// is true for nonces that are large enough to be randomly generated, or if -/// the host is able to maintain a global counter. -/// - If not, the function will fail and return the dedicated `nonce_required` -/// error code. -/// -/// A nonce that was automatically generated can be retrieved after the function -/// returns with `symmetric_state_get(state_handle, "nonce")`. -/// -/// **Sample usage patterns:** -/// -/// - **Hashing** -/// -/// ```rust -/// let mut out = [0u8; 64]; -/// let state_handle = ctx.symmetric_state_open("SHAKE-128", None, None)?; -/// ctx.symmetric_state_absorb(state_handle, b"data")?; -/// ctx.symmetric_state_absorb(state_handle, b"more_data")?; -/// ctx.symmetric_state_squeeze(state_handle, &mut out)?; -/// ``` -/// -/// - **MAC** -/// -/// ```rust -/// let mut raw_tag = [0u8; 64]; -/// let key_handle = ctx.symmetric_key_import("HMAC/SHA-512", b"key")?; -/// let state_handle = ctx.symmetric_state_open("HMAC/SHA-512", Some(key_handle), None)?; -/// ctx.symmetric_state_absorb(state_handle, b"data")?; -/// ctx.symmetric_state_absorb(state_handle, b"more_data")?; -/// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; -/// ctx.symmetric_tag_pull(computed_tag_handle, &mut raw_tag)?; -/// ``` -/// -/// Verification: -/// -/// ```rust -/// let state_handle = ctx.symmetric_state_open("HMAC/SHA-512", Some(key_handle), None)?; -/// ctx.symmetric_state_absorb(state_handle, b"data")?; -/// ctx.symmetric_state_absorb(state_handle, b"more_data")?; -/// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; -/// ctx.symmetric_tag_verify(computed_tag_handle, expected_raw_tag)?; -/// ``` -/// -/// - **Tuple hashing** -/// -/// ```rust -/// let mut out = [0u8; 64]; -/// let state_handle = ctx.symmetric_state_open("TupleHashXOF256", None, None)?; -/// ctx.symmetric_state_absorb(state_handle, b"value 1")?; -/// ctx.symmetric_state_absorb(state_handle, b"value 2")?; -/// ctx.symmetric_state_absorb(state_handle, b"value 3")?; -/// ctx.symmetric_state_squeeze(state_handle, &mut out)?; -/// ``` -/// Unlike MACs and regular hash functions, inputs are domain separated instead -/// of being concatenated. -/// -/// - **Key derivation using extract-and-expand** -/// -/// Extract: -/// -/// ```rust -/// let mut prk = vec![0u8; 64]; -/// let key_handle = ctx.symmetric_key_import("HKDF-EXTRACT/SHA-512", b"key")?; -/// let state_handle = ctx.symmetric_state_open("HKDF-EXTRACT/SHA-512", Some(key_handle), None)?; -/// ctx.symmetric_state_absorb(state_handle, b"salt")?; -/// let prk_handle = ctx.symmetric_state_squeeze_key(state_handle, "HKDF-EXPAND/SHA-512")?; -/// ``` -/// -/// Expand: -/// -/// ```rust -/// let mut subkey = vec![0u8; 32]; -/// let state_handle = ctx.symmetric_state_open("HKDF-EXPAND/SHA-512", Some(prk_handle), None)?; -/// ctx.symmetric_state_absorb(state_handle, b"info")?; -/// ctx.symmetric_state_squeeze(state_handle, &mut subkey)?; -/// ``` -/// -/// - **Key derivation using a XOF** -/// -/// ```rust -/// let mut subkey1 = vec![0u8; 32]; -/// let mut subkey2 = vec![0u8; 32]; -/// let key_handle = ctx.symmetric_key_import("BLAKE3", b"key")?; -/// let state_handle = ctx.symmetric_state_open("BLAKE3", Some(key_handle), None)?; -/// ctx.symmetric_absorb(state_handle, b"context")?; -/// ctx.squeeze(state_handle, &mut subkey1)?; -/// ctx.squeeze(state_handle, &mut subkey2)?; -/// ``` -/// -/// - **Password hashing** -/// -/// ```rust -/// let mut memory = vec![0u8; 1_000_000_000]; -/// let options_handle = ctx.symmetric_options_open()?; -/// ctx.symmetric_options_set_guest_buffer(options_handle, "memory", &mut memory)?; -/// ctx.symmetric_options_set_u64(options_handle, "opslimit", 5)?; -/// ctx.symmetric_options_set_u64(options_handle, "parallelism", 8)?; -/// -/// let state_handle = ctx.symmetric_state_open("ARGON2-ID-13", None, Some(options))?; -/// ctx.symmtric_state_absorb(state_handle, b"password")?; -/// -/// let pw_str_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; -/// let mut pw_str = vec![0u8; ctx.symmetric_tag_len(pw_str_handle)?]; -/// ctx.symmetric_tag_pull(pw_str_handle, &mut pw_str)?; -/// ``` -/// -/// - **AEAD encryption with an explicit nonce** -/// -/// ```rust -/// let key_handle = ctx.symmetric_key_generate("AES-256-GCM", None)?; -/// let message = b"test"; -/// -/// let options_handle = ctx.symmetric_options_open()?; -/// ctx.symmetric_options_set(options_handle, "nonce", nonce)?; -/// -/// let state_handle = -/// ctx.symmetric_state_open("AES-256-GCM", Some(key_handle), Some(options_handle))?; -/// let mut ciphertext = vec![0u8; message.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; -/// ctx.symmetric_state_absorb(state_handle, "additional data")?; -/// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, message)?; -/// ``` -/// -/// - **AEAD encryption with automatic nonce generation** -/// -/// ```rust -/// let key_handle = ctx.symmetric_key_generate("AES-256-GCM-SIV", None)?; -/// let message = b"test"; -/// let mut nonce = [0u8; 24]; -/// -/// let state_handle = ctx.symmetric_state_open("AES-256-GCM-SIV", Some(key_handle), None)?; -/// -/// let nonce = ctx.symmetric_state_options_get(state_handle, "nonce")?; -/// -/// let mut ciphertext = vec![0u8; message.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; -/// ctx.symmetric_state_absorb(state_handle, "additional data")?; -/// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, message)?; -/// ``` -/// -/// - **Session authenticated modes** -/// -/// ```rust -/// let mut out = [0u8; 16]; -/// let mut out2 = [0u8; 16]; -/// let mut ciphertext = [0u8; 20]; -/// let key_handle = ctx.symmetric_key_generate("Xoodyak-128", None)?; -/// let state_handle = ctx.symmetric_state_open("Xoodyak-128", Some(key_handle), None)?; -/// ctx.symmetric_state_absorb(state_handle, b"data")?; -/// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, b"abcd")?; -/// ctx.symmetric_state_absorb(state_handle, b"more data")?; -/// ctx.symmetric_state_squeeze(state_handle, &mut out)?; -/// ctx.symmetric_state_squeeze(state_handle, &mut out2)?; -/// ctx.symmetric_state_ratchet(state_handle)?; -/// ctx.symmetric_state_absorb(state_handle, b"more data")?; -/// let next_key_handle = ctx.symmetric_state_squeeze_key(state_handle, "Xoodyak-128")?; -/// // ... -/// ``` -pub unsafe fn symmetric_state_open( - algorithm: &str, - key: OptSymmetricKey, - options: OptOptions, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_open( - algorithm.as_ptr() as i32, - algorithm.len() as i32, - &key as *const _ as i32, - &options as *const _ as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const SymmetricState - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Retrieve a parameter from the current state. -/// -/// In particular, `symmetric_state_options_get("nonce")` can be used to get a -/// nonce that as automatically generated. -/// -/// The function may return `options_not_set` if an option was not set, which is -/// different from an empty value. -/// -/// It may also return `unsupported_option` if the option doesn't exist for the -/// chosen algorithm. -pub unsafe fn symmetric_state_options_get( - handle: SymmetricState, - name: &str, - value: *mut u8, - value_max_len: Size, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_options_get( - handle as i32, - name.as_ptr() as i32, - name.len() as i32, - value as i32, - value_max_len as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Retrieve an integer parameter from the current state. -/// -/// The function may return `options_not_set` if an option was not set. -/// -/// It may also return `unsupported_option` if the option doesn't exist for the -/// chosen algorithm. -pub unsafe fn symmetric_state_options_get_u64( - handle: SymmetricState, - name: &str, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_options_get_u64( - handle as i32, - name.as_ptr() as i32, - name.len() as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const U64)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Clone a symmetric state. -/// -/// The function clones the internal state, assigns a new handle to it and -/// returns the new handle. -pub unsafe fn symmetric_state_clone(handle: SymmetricState) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_clone( - handle as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const SymmetricState - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Destroy a symmetric state. -/// -/// Objects are reference counted. It is safe to close an object immediately -/// after the last function needing it is called. -pub unsafe fn symmetric_state_close(handle: SymmetricState) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_close(handle as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Absorb data into the state. -/// -/// - **Hash functions:** adds data to be hashed. -/// - **MAC functions:** adds data to be authenticated. -/// - **Tuplehash-like constructions:** adds a new tuple to the state. -/// - **Key derivation functions:** adds to the IKM or to the subkey -/// information. -/// - **AEAD constructions:** adds additional data to be authenticated. -/// - **Stateful hash objects, permutation-based constructions:** absorbs. -/// -/// If the chosen algorithm doesn't accept input data, the `invalid_operation` -/// error code is returned. -/// -/// If too much data has been fed for the algorithm, `overflow` may be thrown. -pub unsafe fn symmetric_state_absorb( - handle: SymmetricState, - data: *const u8, - data_len: Size, -) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_absorb( - handle as i32, - data as i32, - data_len as i32, - ); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Squeeze bytes from the state. -/// -/// - **Hash functions:** this tries to output an `out_len` bytes digest from -/// the absorbed data. The hash function output will be truncated if -/// necessary. If the requested size is too large, the `invalid_len` error -/// code is returned. -/// - **Key derivation functions:** : outputs an arbitrary-long derived key. -/// - **RNGs, DRBGs, stream ciphers:**: outputs arbitrary-long data. -/// - **Stateful hash objects, permutation-based constructions:** squeeze. -/// -/// Other kinds of algorithms may return `invalid_operation` instead. -/// -/// For password-stretching functions, the function may return `in_progress`. -/// In that case, the guest should retry with the same parameters until the -/// function completes. -pub unsafe fn symmetric_state_squeeze( - handle: SymmetricState, - out: *mut u8, - out_len: Size, -) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_squeeze( - handle as i32, - out as i32, - out_len as i32, - ); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Compute and return a tag for all the data injected into the state so far. -/// -/// - **MAC functions**: returns a tag authenticating the absorbed data. -/// - **Tuplehash-like constructions:** returns a tag authenticating all the -/// absorbed tuples. -/// - **Password-hashing functions:** returns a standard string containing all -/// the required parameters for password verification. -/// -/// Other kinds of algorithms may return `invalid_operation` instead. -/// -/// For password-stretching functions, the function may return `in_progress`. -/// In that case, the guest should retry with the same parameters until the -/// function completes. -pub unsafe fn symmetric_state_squeeze_tag( - handle: SymmetricState, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_squeeze_tag( - handle as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const SymmetricTag - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Use the current state to produce a key for a target algorithm. -/// -/// For extract-then-expand constructions, this returns the PRK. -/// For session-base authentication encryption, this returns a key that can be -/// used to resume a session without storing a nonce. -/// -/// `invalid_operation` is returned for algorithms not supporting this -/// operation. -pub unsafe fn symmetric_state_squeeze_key( - handle: SymmetricState, - alg_str: &str, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_squeeze_key( - handle as i32, - alg_str.as_ptr() as i32, - alg_str.len() as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const SymmetricKey - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Return the maximum length of an authentication tag for the current -/// algorithm. -/// -/// This allows guests to compute the size required to store a ciphertext along -/// with its authentication tag. -/// -/// The returned length may include the encryption mode's padding requirements -/// in addition to the actual tag. -/// -/// For an encryption operation, the size of the output buffer should be -/// `input_len + symmetric_state_max_tag_len()`. -/// -/// For a decryption operation, the size of the buffer that will store the -/// decrypted data must be `ciphertext_len - symmetric_state_max_tag_len()`. -pub unsafe fn symmetric_state_max_tag_len(handle: SymmetricState) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_max_tag_len( - handle as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Encrypt data with an attached tag. -/// -/// - **Stream cipher:** adds the input to the stream cipher output. `out_len` -/// and `data_len` can be equal, as no authentication tags will be added. -/// - **AEAD:** encrypts `data` into `out`, including the authentication tag to -/// the output. Additional data must have been previously absorbed using -/// `symmetric_state_absorb()`. The `symmetric_state_max_tag_len()` function -/// can be used to retrieve the overhead of adding the tag, as well as padding -/// if necessary. -/// - **SHOE, Xoodyak, Strobe:** encrypts data, squeezes a tag and appends it to -/// the output. -/// -/// If `out` and `data` are the same address, encryption may happen in-place. -/// -/// The function returns the actual size of the ciphertext along with the tag. -/// -/// `invalid_operation` is returned for algorithms not supporting encryption. -pub unsafe fn symmetric_state_encrypt( - handle: SymmetricState, - out: *mut u8, - out_len: Size, - data: *const u8, - data_len: Size, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_encrypt( - handle as i32, - out as i32, - out_len as i32, - data as i32, - data_len as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Encrypt data, with a detached tag. -/// -/// - **Stream cipher:** returns `invalid_operation` since stream ciphers do not -/// include authentication tags. -/// - **AEAD:** encrypts `data` into `out` and returns the tag separately. -/// Additional data must have been previously absorbed using -/// `symmetric_state_absorb()`. The output and input buffers must be of the -/// same length. -/// - **SHOE, Xoodyak, Strobe:** encrypts data and squeezes a tag. -/// -/// If `out` and `data` are the same address, encryption may happen in-place. -/// -/// The function returns the tag. -/// -/// `invalid_operation` is returned for algorithms not supporting encryption. -pub unsafe fn symmetric_state_encrypt_detached( - handle: SymmetricState, - out: *mut u8, - out_len: Size, - data: *const u8, - data_len: Size, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_encrypt_detached( - handle as i32, - out as i32, - out_len as i32, - data as i32, - data_len as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const SymmetricTag - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// - **Stream cipher:** adds the input to the stream cipher output. `out_len` -/// and `data_len` can be equal, as no authentication tags will be added. -/// - **AEAD:** decrypts `data` into `out`. Additional data must have been -/// previously absorbed using `symmetric_state_absorb()`. -/// - **SHOE, Xoodyak, Strobe:** decrypts data, squeezes a tag and verify that -/// it matches the one that was appended to the ciphertext. -/// -/// If `out` and `data` are the same address, decryption may happen in-place. -/// -/// `out_len` must be exactly `data_len` + `max_tag_len` bytes. -/// -/// The function returns the actual size of the decrypted message, which can be -/// smaller than `out_len` for modes that requires padding. -/// -/// `invalid_tag` is returned if the tag didn't verify. -/// -/// `invalid_operation` is returned for algorithms not supporting encryption. -pub unsafe fn symmetric_state_decrypt( - handle: SymmetricState, - out: *mut u8, - out_len: Size, - data: *const u8, - data_len: Size, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_decrypt( - handle as i32, - out as i32, - out_len as i32, - data as i32, - data_len as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// - **Stream cipher:** returns `invalid_operation` since stream ciphers do not -/// include authentication tags. -/// - **AEAD:** decrypts `data` into `out`. Additional data must have been -/// previously absorbed using `symmetric_state_absorb()`. -/// - **SHOE, Xoodyak, Strobe:** decrypts data, squeezes a tag and verify that -/// it matches the expected one. -/// -/// `raw_tag` is the expected tag, as raw bytes. -/// -/// `out` and `data` be must have the same length. -/// If they also share the same address, decryption may happen in-place. -/// -/// The function returns the actual size of the decrypted message. -/// -/// `invalid_tag` is returned if the tag verification failed. -/// -/// `invalid_operation` is returned for algorithms not supporting encryption. -pub unsafe fn symmetric_state_decrypt_detached( - handle: SymmetricState, - out: *mut u8, - out_len: Size, - data: *const u8, - data_len: Size, - raw_tag: *const u8, - raw_tag_len: Size, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_decrypt_detached( - handle as i32, - out as i32, - out_len as i32, - data as i32, - data_len as i32, - raw_tag as i32, - raw_tag_len as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Make it impossible to recover the previous state. -/// -/// This operation is supported by some systems keeping a rolling state over an -/// entire session, for forward security. -/// -/// `invalid_operation` is returned for algorithms not supporting ratcheting. -pub unsafe fn symmetric_state_ratchet(handle: SymmetricState) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_symmetric::symmetric_state_ratchet(handle as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Return the length of an authentication tag. -/// -/// This function can be used by a guest to allocate the correct buffer size to -/// copy a computed authentication tag. -pub unsafe fn symmetric_tag_len(symmetric_tag: SymmetricTag) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_tag_len( - symmetric_tag as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Copy an authentication tag into a guest-allocated buffer. -/// -/// The handle automatically becomes invalid after this operation. Manually -/// closing it is not required. -/// -/// Example usage: -/// -/// ```rust -/// let mut raw_tag = [0u8; 16]; -/// ctx.symmetric_tag_pull(raw_tag_handle, &mut raw_tag)?; -/// ``` -/// -/// The function returns `overflow` if the supplied buffer is too small to copy -/// the tag. -/// -/// Otherwise, it returns the number of bytes that have been copied. -pub unsafe fn symmetric_tag_pull( - symmetric_tag: SymmetricTag, - buf: *mut u8, - buf_len: Size, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_symmetric::symmetric_tag_pull( - symmetric_tag as i32, - buf as i32, - buf_len as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read(rp0.as_mut_ptr() as i32 as *const Size)), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Verify that a computed authentication tag matches the expected value, in -/// constant-time. -/// -/// The expected tag must be provided as a raw byte string. -/// -/// The function returns `invalid_tag` if the tags don't match. -/// -/// Example usage: -/// -/// ```rust -/// let key_handle = ctx.symmetric_key_import("HMAC/SHA-256", b"key")?; -/// let state_handle = ctx.symmetric_state_open("HMAC/SHA-256", Some(key_handle), None)?; -/// ctx.symmetric_state_absorb(state_handle, b"data")?; -/// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; -/// ctx.symmetric_tag_verify(computed_tag_handle, expected_raw_tag)?; -/// ``` -pub unsafe fn symmetric_tag_verify( - symmetric_tag: SymmetricTag, - expected_raw_tag_ptr: *const u8, - expected_raw_tag_len: Size, -) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_symmetric::symmetric_tag_verify( - symmetric_tag as i32, - expected_raw_tag_ptr as i32, - expected_raw_tag_len as i32, - ); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Explicitly destroy an unused authentication tag. -/// -/// This is usually not necessary, as `symmetric_tag_pull()` automatically -/// closes a tag after it has been copied. -/// -/// Objects are reference counted. It is safe to close an object immediately -/// after the last function needing it is called. -pub unsafe fn symmetric_tag_close(symmetric_tag: SymmetricTag) -> Result<(), CryptoErrno> { - let ret = wasi_ephemeral_crypto_symmetric::symmetric_tag_close(symmetric_tag as i32); - match ret { - 0 => Ok(()), - _ => Err(CryptoErrno(ret as u16)), - } -} - -pub mod wasi_ephemeral_crypto_symmetric { - #[link(wasm_import_module = "wasi_ephemeral_crypto_symmetric")] - extern "C" { - /// Generate a new symmetric key for a given algorithm. - /// - /// `options` can be `None` to use the default parameters, or an - /// algoritm-specific set of parameters to override. - /// - /// This function may return `unsupported_feature` if key generation is - /// not supported by the host for the chosen algorithm, or - /// `unsupported_algorithm` if the algorithm is not supported by the - /// host. - pub fn symmetric_key_generate(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; - /// Create a symmetric key from raw material. - /// - /// The algorithm is internally stored along with the key, and trying to - /// use the key with an operation expecting a different algorithm will - /// return `invalid_key`. - /// - /// The function may also return `unsupported_algorithm` if the - /// algorithm is not supported by the host. - pub fn symmetric_key_import(arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32) -> i32; - /// Export a symmetric key as raw material. - /// - /// This is mainly useful to export a managed key. - /// - /// May return `prohibited_operation` if this operation is denied. - pub fn symmetric_key_export(arg0: i32, arg1: i32) -> i32; - /// Destroy a symmetric key. - /// - /// Objects are reference counted. It is safe to close an object - /// immediately after the last function needing it is called. - pub fn symmetric_key_close(arg0: i32) -> i32; - /// __(optional)__ - /// Generate a new managed symmetric key. - /// - /// The key is generated and stored by the secrets management - /// facilities. - /// - /// It may be used through its identifier, but the host may not allow it - /// to be exported. - /// - /// The function returns the `unsupported_feature` error code if secrets - /// management facilities are not supported by the host, - /// or `unsupported_algorithm` if a key cannot be created for the chosen - /// algorithm. - /// - /// The function may also return `unsupported_algorithm` if the - /// algorithm is not supported by the host. - /// - /// This is also an optional import, meaning that the function may not - /// even exist. - pub fn symmetric_key_generate_managed( - arg0: i32, - arg1: i32, - arg2: i32, - arg3: i32, - arg4: i32, - ) -> i32; - /// __(optional)__ - /// Store a symmetric key into the secrets manager. - /// - /// On success, the function stores the key identifier into - /// `$symmetric_key_id`, into which up to - /// `$symmetric_key_id_max_len` can be written. - /// - /// The function returns `overflow` if the supplied buffer is too small. - pub fn symmetric_key_store_managed(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; - /// __(optional)__ - /// Replace a managed symmetric key. - /// - /// This function crates a new version of a managed symmetric key, by - /// replacing `$kp_old` with `$kp_new`. - /// - /// It does several things: - /// - /// - The key identifier for `$symmetric_key_new` is set to the one of - /// `$symmetric_key_old`. - /// - A new, unique version identifier is assigned to `$kp_new`. This - /// version will be equivalent to using `$version_latest` until the - /// key is replaced. - /// - The `$symmetric_key_old` handle is closed. - /// - /// Both keys must share the same algorithm and have compatible - /// parameters. If this is not the case, `incompatible_keys` is - /// returned. - /// - /// The function may also return the `unsupported_feature` error code if - /// secrets management facilities are not supported by the host, - /// or if keys cannot be rotated. - /// - /// Finally, `prohibited_operation` can be returned if - /// `$symmetric_key_new` wasn't created by the secrets manager, and the - /// secrets manager prohibits imported keys. - /// - /// If the operation succeeded, the new version is returned. - /// - /// This is an optional import, meaning that the function may not even - /// exist. - pub fn symmetric_key_replace_managed(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; - /// __(optional)__ - /// Return the key identifier and version of a managed symmetric key. - /// - /// If the key is not managed, `unsupported_feature` is returned - /// instead. - /// - /// This is an optional import, meaning that the function may not even - /// exist. - pub fn symmetric_key_id(arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32) -> i32; - /// __(optional)__ - /// Return a managed symmetric key from a key identifier. - /// - /// `kp_version` can be set to `version_latest` to retrieve the most - /// recent version of a symmetric key. - /// - /// If no key matching the provided information is found, `not_found` is - /// returned instead. - /// - /// This is an optional import, meaning that the function may not even - /// exist. - pub fn symmetric_key_from_id(arg0: i32, arg1: i32, arg2: i32, arg3: i64, arg4: i32) -> i32; - /// Create a new state to aborb and produce data using symmetric - /// operations. - /// - /// The state remains valid after every operation in order to support - /// incremental updates. - /// - /// The function has two optional parameters: a key and an options set. - /// - /// It will fail with a `key_not_supported` error code if a key was - /// provided but the chosen algorithm doesn't natively support keying. - /// - /// On the other hand, if a key is required, but was not provided, a - /// `key_required` error will be thrown. - /// - /// Some algorithms may require additional parameters. They have to be - /// supplied as an options set: - /// - /// ```rust - /// let options_handle = ctx.options_open()?; - /// ctx.options_set("context", b"My application")?; - /// ctx.options_set_u64("fanout", 16)?; - /// let state_handle = ctx.symmetric_state_open("BLAKE2b-512", None, Some(options_handle))?; - /// ``` - /// - /// If some parameters are mandatory but were not set, the - /// `parameters_missing` error code will be returned. - /// - /// A notable exception is the `nonce` parameter, that is common to most - /// AEAD constructions. - /// - /// If a nonce is required but was not supplied: - /// - /// - If it is safe to do so, the host will automatically generate a - /// nonce. This is true for nonces that are large enough to be - /// randomly generated, or if the host is able to maintain a global - /// counter. - /// - If not, the function will fail and return the dedicated - /// `nonce_required` error code. - /// - /// A nonce that was automatically generated can be retrieved after the - /// function returns with `symmetric_state_get(state_handle, "nonce")`. - /// - /// **Sample usage patterns:** - /// - /// - **Hashing** - /// - /// ```rust - /// let mut out = [0u8; 64]; - /// let state_handle = ctx.symmetric_state_open("SHAKE-128", None, None)?; - /// ctx.symmetric_state_absorb(state_handle, b"data")?; - /// ctx.symmetric_state_absorb(state_handle, b"more_data")?; - /// ctx.symmetric_state_squeeze(state_handle, &mut out)?; - /// ``` - /// - /// - **MAC** - /// - /// ```rust - /// let mut raw_tag = [0u8; 64]; - /// let key_handle = ctx.symmetric_key_import("HMAC/SHA-512", b"key")?; - /// let state_handle = ctx.symmetric_state_open("HMAC/SHA-512", Some(key_handle), None)?; - /// ctx.symmetric_state_absorb(state_handle, b"data")?; - /// ctx.symmetric_state_absorb(state_handle, b"more_data")?; - /// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; - /// ctx.symmetric_tag_pull(computed_tag_handle, &mut raw_tag)?; - /// ``` - /// - /// Verification: - /// - /// ```rust - /// let state_handle = ctx.symmetric_state_open("HMAC/SHA-512", Some(key_handle), None)?; - /// ctx.symmetric_state_absorb(state_handle, b"data")?; - /// ctx.symmetric_state_absorb(state_handle, b"more_data")?; - /// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; - /// ctx.symmetric_tag_verify(computed_tag_handle, expected_raw_tag)?; - /// ``` - /// - /// - **Tuple hashing** - /// - /// ```rust - /// let mut out = [0u8; 64]; - /// let state_handle = ctx.symmetric_state_open("TupleHashXOF256", None, None)?; - /// ctx.symmetric_state_absorb(state_handle, b"value 1")?; - /// ctx.symmetric_state_absorb(state_handle, b"value 2")?; - /// ctx.symmetric_state_absorb(state_handle, b"value 3")?; - /// ctx.symmetric_state_squeeze(state_handle, &mut out)?; - /// ``` - /// Unlike MACs and regular hash functions, inputs are domain separated - /// instead of being concatenated. - /// - /// - **Key derivation using extract-and-expand** - /// - /// Extract: - /// - /// ```rust - /// let mut prk = vec![0u8; 64]; - /// let key_handle = ctx.symmetric_key_import("HKDF-EXTRACT/SHA-512", b"key")?; - /// let state_handle = ctx.symmetric_state_open("HKDF-EXTRACT/SHA-512", Some(key_handle), None)?; - /// ctx.symmetric_state_absorb(state_handle, b"salt")?; - /// let prk_handle = ctx.symmetric_state_squeeze_key(state_handle, "HKDF-EXPAND/SHA-512")?; - /// ``` - /// - /// Expand: - /// - /// ```rust - /// let mut subkey = vec![0u8; 32]; - /// let state_handle = ctx.symmetric_state_open("HKDF-EXPAND/SHA-512", Some(prk_handle), None)?; - /// ctx.symmetric_state_absorb(state_handle, b"info")?; - /// ctx.symmetric_state_squeeze(state_handle, &mut subkey)?; - /// ``` - /// - /// - **Key derivation using a XOF** - /// - /// ```rust - /// let mut subkey1 = vec![0u8; 32]; - /// let mut subkey2 = vec![0u8; 32]; - /// let key_handle = ctx.symmetric_key_import("BLAKE3", b"key")?; - /// let state_handle = ctx.symmetric_state_open("BLAKE3", Some(key_handle), None)?; - /// ctx.symmetric_absorb(state_handle, b"context")?; - /// ctx.squeeze(state_handle, &mut subkey1)?; - /// ctx.squeeze(state_handle, &mut subkey2)?; - /// ``` - /// - /// - **Password hashing** - /// - /// ```rust - /// let mut memory = vec![0u8; 1_000_000_000]; - /// let options_handle = ctx.symmetric_options_open()?; - /// ctx.symmetric_options_set_guest_buffer(options_handle, "memory", &mut memory)?; - /// ctx.symmetric_options_set_u64(options_handle, "opslimit", 5)?; - /// ctx.symmetric_options_set_u64(options_handle, "parallelism", 8)?; - /// - /// let state_handle = ctx.symmetric_state_open("ARGON2-ID-13", None, Some(options))?; - /// ctx.symmtric_state_absorb(state_handle, b"password")?; - /// - /// let pw_str_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; - /// let mut pw_str = vec![0u8; ctx.symmetric_tag_len(pw_str_handle)?]; - /// ctx.symmetric_tag_pull(pw_str_handle, &mut pw_str)?; - /// ``` - /// - /// - **AEAD encryption with an explicit nonce** - /// - /// ```rust - /// let key_handle = ctx.symmetric_key_generate("AES-256-GCM", None)?; - /// let message = b"test"; - /// - /// let options_handle = ctx.symmetric_options_open()?; - /// ctx.symmetric_options_set(options_handle, "nonce", nonce)?; - /// - /// let state_handle = - /// ctx.symmetric_state_open("AES-256-GCM", Some(key_handle), Some(options_handle))?; - /// let mut ciphertext = vec![0u8; message.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; - /// ctx.symmetric_state_absorb(state_handle, "additional data")?; - /// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, message)?; - /// ``` - /// - /// - **AEAD encryption with automatic nonce generation** - /// - /// ```rust - /// let key_handle = ctx.symmetric_key_generate("AES-256-GCM-SIV", None)?; - /// let message = b"test"; - /// let mut nonce = [0u8; 24]; - /// - /// let state_handle = ctx.symmetric_state_open("AES-256-GCM-SIV", Some(key_handle), None)?; - /// - /// let nonce = ctx.symmetric_state_options_get(state_handle, "nonce")?; - /// - /// let mut ciphertext = vec![0u8; message.len() + ctx.symmetric_state_max_tag_len(state_handle)?]; - /// ctx.symmetric_state_absorb(state_handle, "additional data")?; - /// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, message)?; - /// ``` - /// - /// - **Session authenticated modes** - /// - /// ```rust - /// let mut out = [0u8; 16]; - /// let mut out2 = [0u8; 16]; - /// let mut ciphertext = [0u8; 20]; - /// let key_handle = ctx.symmetric_key_generate("Xoodyak-128", None)?; - /// let state_handle = ctx.symmetric_state_open("Xoodyak-128", Some(key_handle), None)?; - /// ctx.symmetric_state_absorb(state_handle, b"data")?; - /// ctx.symmetric_state_encrypt(state_handle, &mut ciphertext, b"abcd")?; - /// ctx.symmetric_state_absorb(state_handle, b"more data")?; - /// ctx.symmetric_state_squeeze(state_handle, &mut out)?; - /// ctx.symmetric_state_squeeze(state_handle, &mut out2)?; - /// ctx.symmetric_state_ratchet(state_handle)?; - /// ctx.symmetric_state_absorb(state_handle, b"more data")?; - /// let next_key_handle = ctx.symmetric_state_squeeze_key(state_handle, "Xoodyak-128")?; - /// // ... - /// ``` - pub fn symmetric_state_open(arg0: i32, arg1: i32, arg2: i32, arg3: i32, arg4: i32) -> i32; - /// Retrieve a parameter from the current state. - /// - /// In particular, `symmetric_state_options_get("nonce")` can be used to - /// get a nonce that as automatically generated. - /// - /// The function may return `options_not_set` if an option was not set, - /// which is different from an empty value. - /// - /// It may also return `unsupported_option` if the option doesn't exist - /// for the chosen algorithm. - pub fn symmetric_state_options_get( - arg0: i32, - arg1: i32, - arg2: i32, - arg3: i32, - arg4: i32, - arg5: i32, - ) -> i32; - /// Retrieve an integer parameter from the current state. - /// - /// The function may return `options_not_set` if an option was not set. - /// - /// It may also return `unsupported_option` if the option doesn't exist - /// for the chosen algorithm. - pub fn symmetric_state_options_get_u64(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; - /// Clone a symmetric state. - /// - /// The function clones the internal state, assigns a new handle to it - /// and returns the new handle. - pub fn symmetric_state_clone(arg0: i32, arg1: i32) -> i32; - /// Destroy a symmetric state. - /// - /// Objects are reference counted. It is safe to close an object - /// immediately after the last function needing it is called. - pub fn symmetric_state_close(arg0: i32) -> i32; - /// Absorb data into the state. - /// - /// - **Hash functions:** adds data to be hashed. - /// - **MAC functions:** adds data to be authenticated. - /// - **Tuplehash-like constructions:** adds a new tuple to the state. - /// - **Key derivation functions:** adds to the IKM or to the subkey - /// information. - /// - **AEAD constructions:** adds additional data to be authenticated. - /// - **Stateful hash objects, permutation-based constructions:** - /// absorbs. - /// - /// If the chosen algorithm doesn't accept input data, the - /// `invalid_operation` error code is returned. - /// - /// If too much data has been fed for the algorithm, `overflow` may be - /// thrown. - pub fn symmetric_state_absorb(arg0: i32, arg1: i32, arg2: i32) -> i32; - /// Squeeze bytes from the state. - /// - /// - **Hash functions:** this tries to output an `out_len` bytes digest - /// from the absorbed data. The hash function output will be truncated - /// if necessary. If the requested size is too large, the - /// `invalid_len` error code is returned. - /// - **Key derivation functions:** : outputs an arbitrary-long derived - /// key. - /// - **RNGs, DRBGs, stream ciphers:**: outputs arbitrary-long data. - /// - **Stateful hash objects, permutation-based constructions:** - /// squeeze. - /// - /// Other kinds of algorithms may return `invalid_operation` instead. - /// - /// For password-stretching functions, the function may return - /// `in_progress`. In that case, the guest should retry with the - /// same parameters until the function completes. - pub fn symmetric_state_squeeze(arg0: i32, arg1: i32, arg2: i32) -> i32; - /// Compute and return a tag for all the data injected into the state so - /// far. - /// - /// - **MAC functions**: returns a tag authenticating the absorbed data. - /// - **Tuplehash-like constructions:** returns a tag authenticating all - /// the absorbed tuples. - /// - **Password-hashing functions:** returns a standard string - /// containing all the required parameters for password verification. - /// - /// Other kinds of algorithms may return `invalid_operation` instead. - /// - /// For password-stretching functions, the function may return - /// `in_progress`. In that case, the guest should retry with the - /// same parameters until the function completes. - pub fn symmetric_state_squeeze_tag(arg0: i32, arg1: i32) -> i32; - /// Use the current state to produce a key for a target algorithm. - /// - /// For extract-then-expand constructions, this returns the PRK. - /// For session-base authentication encryption, this returns a key that - /// can be used to resume a session without storing a nonce. - /// - /// `invalid_operation` is returned for algorithms not supporting this - /// operation. - pub fn symmetric_state_squeeze_key(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; - /// Return the maximum length of an authentication tag for the current - /// algorithm. - /// - /// This allows guests to compute the size required to store a - /// ciphertext along with its authentication tag. - /// - /// The returned length may include the encryption mode's padding - /// requirements in addition to the actual tag. - /// - /// For an encryption operation, the size of the output buffer should be - /// `input_len + symmetric_state_max_tag_len()`. - /// - /// For a decryption operation, the size of the buffer that will store - /// the decrypted data must be `ciphertext_len - - /// symmetric_state_max_tag_len()`. - pub fn symmetric_state_max_tag_len(arg0: i32, arg1: i32) -> i32; - /// Encrypt data with an attached tag. - /// - /// - **Stream cipher:** adds the input to the stream cipher output. - /// `out_len` and `data_len` can be equal, as no authentication tags - /// will be added. - /// - **AEAD:** encrypts `data` into `out`, including the authentication - /// tag to the output. Additional data must have been previously - /// absorbed using `symmetric_state_absorb()`. The - /// `symmetric_state_max_tag_len()` function can be used to retrieve - /// the overhead of adding the tag, as well as padding if necessary. - /// - **SHOE, Xoodyak, Strobe:** encrypts data, squeezes a tag and - /// appends it to the output. - /// - /// If `out` and `data` are the same address, encryption may happen - /// in-place. - /// - /// The function returns the actual size of the ciphertext along with - /// the tag. - /// - /// `invalid_operation` is returned for algorithms not supporting - /// encryption. - pub fn symmetric_state_encrypt( - arg0: i32, - arg1: i32, - arg2: i32, - arg3: i32, - arg4: i32, - arg5: i32, - ) -> i32; - /// Encrypt data, with a detached tag. - /// - /// - **Stream cipher:** returns `invalid_operation` since stream - /// ciphers do not include authentication tags. - /// - **AEAD:** encrypts `data` into `out` and returns the tag - /// separately. Additional data must have been previously absorbed - /// using `symmetric_state_absorb()`. The output and input buffers - /// must be of the same length. - /// - **SHOE, Xoodyak, Strobe:** encrypts data and squeezes a tag. - /// - /// If `out` and `data` are the same address, encryption may happen - /// in-place. - /// - /// The function returns the tag. - /// - /// `invalid_operation` is returned for algorithms not supporting - /// encryption. - pub fn symmetric_state_encrypt_detached( - arg0: i32, - arg1: i32, - arg2: i32, - arg3: i32, - arg4: i32, - arg5: i32, - ) -> i32; - /// - **Stream cipher:** adds the input to the stream cipher output. - /// `out_len` and `data_len` can be equal, as no authentication tags - /// will be added. - /// - **AEAD:** decrypts `data` into `out`. Additional data must have - /// been previously absorbed using `symmetric_state_absorb()`. - /// - **SHOE, Xoodyak, Strobe:** decrypts data, squeezes a tag and - /// verify that it matches the one that was appended to the - /// ciphertext. - /// - /// If `out` and `data` are the same address, decryption may happen - /// in-place. - /// - /// `out_len` must be exactly `data_len` + `max_tag_len` bytes. - /// - /// The function returns the actual size of the decrypted message, which - /// can be smaller than `out_len` for modes that requires padding. - /// - /// `invalid_tag` is returned if the tag didn't verify. - /// - /// `invalid_operation` is returned for algorithms not supporting - /// encryption. - pub fn symmetric_state_decrypt( - arg0: i32, - arg1: i32, - arg2: i32, - arg3: i32, - arg4: i32, - arg5: i32, - ) -> i32; - /// - **Stream cipher:** returns `invalid_operation` since stream - /// ciphers do not include authentication tags. - /// - **AEAD:** decrypts `data` into `out`. Additional data must have - /// been previously absorbed using `symmetric_state_absorb()`. - /// - **SHOE, Xoodyak, Strobe:** decrypts data, squeezes a tag and - /// verify that it matches the expected one. - /// - /// `raw_tag` is the expected tag, as raw bytes. - /// - /// `out` and `data` be must have the same length. - /// If they also share the same address, decryption may happen in-place. - /// - /// The function returns the actual size of the decrypted message. - /// - /// `invalid_tag` is returned if the tag verification failed. - /// - /// `invalid_operation` is returned for algorithms not supporting - /// encryption. - pub fn symmetric_state_decrypt_detached( - arg0: i32, - arg1: i32, - arg2: i32, - arg3: i32, - arg4: i32, - arg5: i32, - arg6: i32, - arg7: i32, - ) -> i32; - /// Make it impossible to recover the previous state. - /// - /// This operation is supported by some systems keeping a rolling state - /// over an entire session, for forward security. - /// - /// `invalid_operation` is returned for algorithms not supporting - /// ratcheting. - pub fn symmetric_state_ratchet(arg0: i32) -> i32; - /// Return the length of an authentication tag. - /// - /// This function can be used by a guest to allocate the correct buffer - /// size to copy a computed authentication tag. - pub fn symmetric_tag_len(arg0: i32, arg1: i32) -> i32; - /// Copy an authentication tag into a guest-allocated buffer. - /// - /// The handle automatically becomes invalid after this operation. - /// Manually closing it is not required. - /// - /// Example usage: - /// - /// ```rust - /// let mut raw_tag = [0u8; 16]; - /// ctx.symmetric_tag_pull(raw_tag_handle, &mut raw_tag)?; - /// ``` - /// - /// The function returns `overflow` if the supplied buffer is too small - /// to copy the tag. - /// - /// Otherwise, it returns the number of bytes that have been copied. - pub fn symmetric_tag_pull(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; - /// Verify that a computed authentication tag matches the expected - /// value, in constant-time. - /// - /// The expected tag must be provided as a raw byte string. - /// - /// The function returns `invalid_tag` if the tags don't match. - /// - /// Example usage: - /// - /// ```rust - /// let key_handle = ctx.symmetric_key_import("HMAC/SHA-256", b"key")?; - /// let state_handle = ctx.symmetric_state_open("HMAC/SHA-256", Some(key_handle), None)?; - /// ctx.symmetric_state_absorb(state_handle, b"data")?; - /// let computed_tag_handle = ctx.symmetric_state_squeeze_tag(state_handle)?; - /// ctx.symmetric_tag_verify(computed_tag_handle, expected_raw_tag)?; - /// ``` - pub fn symmetric_tag_verify(arg0: i32, arg1: i32, arg2: i32) -> i32; - /// Explicitly destroy an unused authentication tag. - /// - /// This is usually not necessary, as `symmetric_tag_pull()` - /// automatically closes a tag after it has been copied. - /// - /// Objects are reference counted. It is safe to close an object - /// immediately after the last function needing it is called. - pub fn symmetric_tag_close(arg0: i32) -> i32; - } -} -/// Perform a simple Diffie-Hellman key exchange. -/// -/// Both keys must be of the same type, or else the -/// `$crypto_errno.incompatible_keys` error is returned. The algorithm also has -/// to support this kind of key exchange. If this is not the case, the -/// `$crypto_errno.invalid_operation` error is returned. -/// -/// Otherwide, a raw shared key is returned, and can be imported as a symmetric -/// key. ``` -pub unsafe fn kx_dh(pk: Publickey, sk: Secretkey) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_kx::kx_dh(pk as i32, sk as i32, rp0.as_mut_ptr() as i32); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const ArrayOutput - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Create a shared secret and encrypt it for the given public key. -/// -/// This operation is only compatible with specific algorithms. -/// If a selected algorithm doesn't support it, -/// `$crypto_errno.invalid_operation` is returned. -/// -/// On success, both the shared secret and its encrypted version are returned. -pub unsafe fn kx_encapsulate(pk: Publickey) -> Result<(ArrayOutput, ArrayOutput), CryptoErrno> { - let mut rp0 = MaybeUninit::::uninit(); - let mut rp1 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_kx::kx_encapsulate( - pk as i32, - rp0.as_mut_ptr() as i32, - rp1.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(( - core::ptr::read(rp0.as_mut_ptr() as i32 as *const ArrayOutput), - core::ptr::read(rp1.as_mut_ptr() as i32 as *const ArrayOutput), - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -/// Decapsulate an encapsulated secret crated with `kx_encapsulate` -/// -/// Return the secret, or `$crypto_errno.verification_failed` on error. -pub unsafe fn kx_decapsulate( - sk: Secretkey, - encapsulated_secret: *const u8, - encapsulated_secret_len: Size, -) -> Result { - let mut rp0 = MaybeUninit::::uninit(); - let ret = wasi_ephemeral_crypto_kx::kx_decapsulate( - sk as i32, - encapsulated_secret as i32, - encapsulated_secret_len as i32, - rp0.as_mut_ptr() as i32, - ); - match ret { - 0 => Ok(core::ptr::read( - rp0.as_mut_ptr() as i32 as *const ArrayOutput - )), - _ => Err(CryptoErrno(ret as u16)), - } -} - -pub mod wasi_ephemeral_crypto_kx { - #[link(wasm_import_module = "wasi_ephemeral_crypto_kx")] - extern "C" { - /// Perform a simple Diffie-Hellman key exchange. - /// - /// Both keys must be of the same type, or else the - /// `$crypto_errno.incompatible_keys` error is returned. - /// The algorithm also has to support this kind of key exchange. If this - /// is not the case, the `$crypto_errno.invalid_operation` error is - /// returned. - /// - /// Otherwide, a raw shared key is returned, and can be imported as a - /// symmetric key. ``` - pub fn kx_dh(arg0: i32, arg1: i32, arg2: i32) -> i32; - /// Create a shared secret and encrypt it for the given public key. - /// - /// This operation is only compatible with specific algorithms. - /// If a selected algorithm doesn't support it, - /// `$crypto_errno.invalid_operation` is returned. - /// - /// On success, both the shared secret and its encrypted version are - /// returned. - pub fn kx_encapsulate(arg0: i32, arg1: i32, arg2: i32) -> i32; - /// Decapsulate an encapsulated secret crated with `kx_encapsulate` - /// - /// Return the secret, or `$crypto_errno.verification_failed` on error. - pub fn kx_decapsulate(arg0: i32, arg1: i32, arg2: i32, arg3: i32) -> i32; - } -} -pub const VERSION_UNSPECIFIED: Version = 18374686479671623680; -pub const VERSION_LATEST: Version = 18374686479671623681; -pub const VERSION_ALL: Version = 18374686479671623682; From b98e71aa6c55eaa0cdfc51c8f709314e55365b28 Mon Sep 17 00:00:00 2001 From: Puellaquae Date: Thu, 25 Jan 2024 00:36:25 +0800 Subject: [PATCH 24/25] refactor: rename crypto/mod.rs to crypto.rs --- src/internal_module/{crypto/mod.rs => crypto.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/internal_module/{crypto/mod.rs => crypto.rs} (100%) diff --git a/src/internal_module/crypto/mod.rs b/src/internal_module/crypto.rs similarity index 100% rename from src/internal_module/crypto/mod.rs rename to src/internal_module/crypto.rs From 7a1999dd9934060dd1a7239bda1bc417b275b484 Mon Sep 17 00:00:00 2001 From: Puelloc Date: Thu, 25 Jan 2024 22:25:08 +0800 Subject: [PATCH 25/25] ci: install wasi_crypto plugin --- .github/workflows/examples.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 47c0863..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