├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.d.ts ├── jsdoc.json ├── package-lock.json ├── package.json ├── src ├── Crypto.js ├── SubtleCrypto.js ├── algorithms │ ├── AES-CBC.js │ ├── AES-CTR.js │ ├── AES-GCM.js │ ├── AES-KW.js │ ├── Algorithm.js │ ├── ECDSA.js │ ├── EDDSA.js │ ├── HMAC.js │ ├── RSA-OAEP.js │ ├── RSA-PSS.js │ ├── RSASSA-PKCS1-v1_5.js │ ├── RegisteredAlgorithms.js │ ├── SHA.js │ ├── SupportedAlgorithms.js │ └── index.js ├── dictionaries │ ├── AES-KW.js │ ├── AesCbcParams.js │ ├── AesCfbParams.js │ ├── AesCmacParams.js │ ├── AesCtrParams.js │ ├── AesDerivedKeyParams.js │ ├── AesGcmParams.js │ ├── AesKeyAlgorithm.js │ ├── AesKeyGenParams.js │ ├── Algorithm.js │ ├── ConcatParams.js │ ├── DhImportKeyParams.js │ ├── DhKeyAlgorithm.js │ ├── DhKeyDeriveParams.js │ ├── DhKeyGenParams.js │ ├── EcKeyAlgorithm.js │ ├── EcKeyGenParams.js │ ├── EcKeyImportParams.js │ ├── EcdhKeyDeriveParams.js │ ├── EcdsaParams.js │ ├── HkdfCtrParams.js │ ├── HmacImportParams.js │ ├── HmacKeyAlgorithm.js │ ├── HmacKeyGenParams.js │ ├── KeyAlgorithm.js │ ├── Pbkdf2Params.js │ ├── RsaHashedImportParams.js │ ├── RsaHashedKeyAlgorithm.js │ ├── RsaHashedKeyGenParams.js │ ├── RsaKeyAlgorithm.js │ ├── RsaKeyGenParams.js │ ├── RsaOaepParams.js │ ├── RsaPssParams.js │ └── ShaKeyAlgorithm.js ├── errors │ ├── CurrentlyNotSupportedError.js │ ├── DataError.js │ ├── InvalidAccessError.js │ ├── KeyFormatNotSupportedError.js │ ├── NotSupportedError.js │ ├── OperationError.js │ ├── QuotaExceededError.js │ ├── TypeMismatchError.js │ └── index.js ├── index.js └── keys │ ├── CryptoKey.js │ ├── CryptoKeyPair.js │ ├── JsonWebKey.js │ └── recognizedKeyUsages.js └── test ├── CryptoSpec.js ├── EcdsaKeyPairsForTesting.js ├── RsaKeyPairForPSSTesting.js ├── RsaKeyPairForTesting.js ├── SubtleCryptoSpec.js ├── algorithms ├── AES-CBCSpec.js ├── AES-CTRSpec.js ├── AES-GCMSpec.js ├── AES-KWSpec.js ├── ECDSASpec.js ├── EDDSASpec.js ├── HMACSpec.js ├── RSA-OAEPspec.js ├── RSA-PSSSpec.js ├── RSASSA-PKCS1-v1_5_Spec.js ├── RegisteredAlgorithmsSpec.js ├── SHASpec.js └── SupportedAlgorithmsSpec.js ├── dictionaries ├── AlgorithmSpec.js ├── HmacKeyAlgorithmSpec.js ├── KeyAlgorithmSpec.js ├── RsaHashedKeyAlgorithmSpec.js └── ShaKeyAlgorithmSpec.js ├── keys ├── CryptoKeyPairSpec.js ├── CryptoKeySpec.js ├── JsonWebKeySpec.js └── recognizedKeyUsagesSpec.js └── mocha.opts /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file and trim whitespace 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{js,json,yml}] 11 | charset = utf-8 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # Jsdoc build output 61 | docs 62 | 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: "8.6.0" 6 | 7 | before_install: npm i -g npm 8 | 9 | install: npm i 10 | 11 | script: npm run coverage 12 | 13 | # after_script: ./node_modules/.bin/codecov 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Anvil Research, Inc. http://anvil.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTICE 2 | 3 | We’re archiving Anvil Connect and all related packages. This code is entirely MIT Licensed. You’re free to do with it what you want. That said, we are recommending _**against**_ using it, due to the potential for security issues arising from unmaintained software. For more information, see the announcement at [anvil.io](https://anvil.io). 4 | 5 | # W3C Web Cryptography API _(@trust/webcrypto)_ 6 | 7 | [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 8 | [![Build Status](https://travis-ci.org/anvilresearch/webcrypto.svg?branch=master)](https://travis-ci.org/anvilresearch/webcrypto) 9 | 10 | > W3C Web Cryptography API for Node.js 11 | 12 | W3C's [Web Cryptography API][webcrypto] defines a standard interface for performing 13 | cryptographic operations in JavaScript, such as key generation, hashing, signing, and 14 | encryption. This package implements the API for Node.js, in order to support universal 15 | crypto-dependent code required by protocols such as [JOSE][jose] and 16 | [OpenID Connect][oidc]. 17 | 18 | [webcrypto]: https://www.w3.org/TR/WebCryptoAPI/ 19 | [jose]: https://datatracker.ietf.org/wg/jose/documents/ 20 | [oidc]: http://openid.net/connect/ 21 | 22 | ## Table of Contents 23 | 24 | * [Security](#security) 25 | * [Background](#background) 26 | * [Install](#install) 27 | * [Usage](#usage) 28 | * [Develop](#develop) 29 | * [Supported Algorithms](#supported-algorithms) 30 | * [API](#api) 31 | * [Contribute](#contribute) 32 | * [MIT License](#mit-license) 33 | 34 | ## Security 35 | 36 | TBD 37 | 38 | ## Background 39 | 40 | The purpose of this package is to enable development of universal JavaScript 41 | libraries that depend on the availability of cryptographic primitives in order 42 | to implement cryptographic protocols. The long term goal of the project is to 43 | encourage or provide a [native, if not core][wtf] Web Cryptography module. 44 | 45 | [wtf]: https://github.com/nodejs/node/issues/2833 46 | 47 | ## Install 48 | 49 | `@trust/webcrypto` requires recent versions of [node][node] and [npm][npm] to run. For key generation operations, it also requires [OpenSSL][openssl] to be installed on the system. 50 | 51 | [node]: https://nodejs.org 52 | [npm]: https://www.npmjs.com/ 53 | [openssl]: https://www.openssl.org/ 54 | 55 | 56 | ```bash 57 | $ npm install @trust/webcrypto --save 58 | ``` 59 | 60 | ### Not for use in Webpack 61 | 62 | This library is not for use in Webpack. 63 | 64 | The whole point of this library is that it's an exact duplicate of the browser's WebCrypto API, 65 | for the server side. 66 | 67 | For Webpacked web applications, 68 | you don't need it (and can't use it). 69 | If this module is transitively included by another dependency, 70 | you have to exclude it by adding it to the [`externals` section in the Webpack config](https://webpack.js.org/configuration/externals/), 71 | such as this: 72 | 73 | ``` 74 | externals: { 75 | '@trust/webcrypto': 'crypto' 76 | } 77 | ``` 78 | 79 | ## Usage 80 | 81 | ```javascript 82 | const crypto = require('@trust/webcrypto') 83 | ``` 84 | 85 | ## Develop 86 | 87 | ### Install 88 | 89 | ```bash 90 | $ git clone git@github.com:anvilresearch/webcrypto.git 91 | $ cd webcrypto 92 | $ npm install 93 | ``` 94 | 95 | ### Test 96 | 97 | ```bash 98 | $ npm test 99 | ``` 100 | 101 | ## Supported Algorithms 102 | 103 | | Algorithm name | encrypt | decrypt | sign | verify | digest | generateKey | deriveKey | deriveBits | importKey | exportKey | wrapKey | unwrapKey | 104 | |------------------|---|---|---|---|---|---|---|---|---|---|---|---| 105 | |RSASSA-PKCS1-v1_5 | | | ✔ | ✔ | | ✔ | | | ✔ | ✔ | | | 106 | |RSA-PSS | | | ✔ | ✔ | | ✔ | | | ✔ | ✔ | | | 107 | |RSA-OAEP | ✔ | ✔ | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ | 108 | |ECDSA | | | ⚐ | ⚐ | | ⚐ | | | ✔ | ✔ | | | 109 | |EDDSA | | | ⚐ | ⚐ | | ⚐ | | | ✔ | ✔ | | | 110 | |ECDH | | | | | | _ | _ | _ | _ | _ | | | 111 | |AES-CTR | ⚐ | ⚐ | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ | 112 | |AES-CBC | ✔ | ✔ | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ | 113 | |AES-GCM | ✔ | ✔ | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ | 114 | |AES-KW | | | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ | 115 | |HMAC | | | ✔ | ✔ | | ✔ | | | ✔ | ✔ | | | 116 | |SHA-1 | | | | | ✔ | | | | | | | | 117 | |SHA-256 | | | | | ✔ | | | | | | | | 118 | |SHA-384 | | | | | ✔ | | | | | | | | 119 | |SHA-512 | | | | | ✔ | | | | | | | | 120 | |HKDF | | | | | | | _ | _ | _ | | | | 121 | |PBKDF2 | | | | | | | _ | _ | _ | | | | 122 | 123 | Key: 124 | 125 | ` ✔ ` Implemented 126 | ` _ ` Currently not implemented 127 | ` ⚐ ` Partially implemented, only certain paramaters supported. 128 | 129 | ## Partial Support 130 | Only the following paramaters are supported for the corresponding algorithm. 131 | 132 | | Algorithm name | Supported paramater | 133 | | -------------- | ------------------- | 134 | | ECDSA | `K-256 (secp256k1)`, `P-256`, `P-384`, `P-512` | 135 | | EDDSA | `ed25519` | 136 | | AES-CTR | `sha-1` | 137 | 138 | 139 | ## API 140 | 141 | See [W3C Web Cryptography API][webcrypto] specification and diafygi's [webcrypto-examples][examples]. 142 | 143 | [examples]: https://github.com/diafygi/webcrypto-examples 144 | 145 | ## Contribute 146 | 147 | ### Issues 148 | 149 | * Please file [issues](https://github.com/anvilresearch/webcrypto/issues) :) 150 | * When writing a bug report, include relevant details such as platform, version, relevant data, and stack traces 151 | * Ensure to check for existing issues before opening new ones 152 | * Read the documentation before asking questions 153 | * It is strongly recommended to open an issue before hacking and submitting a PR 154 | * We reserve the right to close an issue for excessive bikeshedding 155 | 156 | ### Pull requests 157 | 158 | #### Policy 159 | 160 | * We're not presently accepting *unsolicited* pull requests 161 | * Create an issue to discuss proposed features before submitting a pull request 162 | * Create an issue to propose changes of code style or introduce new tooling 163 | * Ensure your work is harmonious with the overall direction of the project 164 | * Ensure your work does not duplicate existing effort 165 | * Keep the scope compact; avoid PRs with more than one feature or fix 166 | * Code review with maintainers is required before any merging of pull requests 167 | * New code must respect the style guide and overall architecture of the project 168 | * Be prepared to defend your work 169 | 170 | #### Style guide 171 | 172 | * ES6 173 | * Standard JavaScript 174 | * jsdocs 175 | 176 | #### Code reviews 177 | 178 | * required before merging PRs 179 | * reviewers MUST run and test the code under review 180 | 181 | ### Collaborating 182 | 183 | #### Weekly project meeting 184 | 185 | * Thursdays from 1:00 PM to 2:00 Eastern US time at [TBD] 186 | * Join remotely with Google Hangouts 187 | 188 | #### Pair programming 189 | 190 | * Required for new contributors 191 | * Work directly with one or more members of the core development team 192 | 193 | ### Code of conduct 194 | 195 | * @trust/webcrypto follows the [Contributor Covenant](http://contributor-covenant.org/version/1/3/0/) Code of Conduct. 196 | 197 | ### Contributors 198 | 199 | * Christian Smith [@christiansmith](https://github.com/christiansmith) 200 | * Dmitri Zagidulin [@dmitrizagidulin](https://github.com/dmitrizagidulin) 201 | * Greg Linklater [@EternalDeiwos](https://github.com/EternalDeiwos) 202 | * JC Bailey [@thelunararmy](https://github.com/thelunararmy) 203 | * Ioan Budea [@johnny90](https://github.com/johnny90) 204 | * Abdulrahman Alotaibi [@adminq80](https://github.com/adminq80) 205 | * Linus Unnebäck [@LinusU](https://github.com/LinusU) 206 | * Len Boyette [@kevlened](https://github.com/kevlened) 207 | * Tom Bonner [@Glitch0011](https://github.com/Glitch0011) 208 | ## MIT License 209 | 210 | Copyright (c) 2016 Anvil Research, Inc. 211 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare var crypto: Crypto 2 | export = crypto; 3 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "include": [ 4 | "./src" 5 | ] 6 | }, 7 | "opts": { 8 | "destination": "./docs/build", 9 | "readme": "./README.md" 10 | }, 11 | "plugins": [ 12 | "plugins/markdown" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trust/webcrypto", 3 | "version": "0.9.2", 4 | "description": "WebCrypto API for Node.js", 5 | "main": "src/index.js", 6 | "types": "index.d.ts", 7 | "directories": { 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "test": "nyc _mocha test", 12 | "jsdoc": "jsdoc -c jsdoc.json -r", 13 | "coverage": "nyc --reporter=lcov _mocha test" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+ssh://git@github.com/anvilresearch/webcrypto.git" 18 | }, 19 | "keywords": [ 20 | "WebCrypto" 21 | ], 22 | "author": "Anvil Research, Inc.", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/anvilresearch/webcrypto/issues" 26 | }, 27 | "homepage": "https://github.com/anvilresearch/webcrypto#README", 28 | "dependencies": { 29 | "@trust/keyto": "^0.3.4", 30 | "base64url": "^3.0.0", 31 | "elliptic": "^6.4.0", 32 | "node-rsa": "^0.4.0", 33 | "text-encoding": "^0.6.1" 34 | }, 35 | "devDependencies": { 36 | "chai": "^3.5.0", 37 | "codecov": "^3.0.2", 38 | "jsdoc": "^3.4.3", 39 | "mocha": "^5.2.0", 40 | "nyc": "^11.2.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Crypto.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | const legacyCrypto = require('crypto') 5 | const SubtleCrypto = require('./SubtleCrypto') 6 | const {QuotaExceededError, TypeMismatchError} = require('./errors') 7 | 8 | /** 9 | * integerTypes 10 | */ 11 | const integerTypes = [ 12 | Int8Array, 13 | Uint8Array, 14 | Int16Array, 15 | Uint16Array, 16 | Int32Array, 17 | Uint32Array 18 | ] 19 | 20 | /** 21 | * integerGetByConstructor 22 | */ 23 | const integerGetByConstructor = { 24 | 'Int8Array': 'getInt8', 25 | 'Uint8Array': 'getUint8', 26 | 'Int16Array': 'getInt16', 27 | 'Uint16Array': 'getUint16', 28 | 'Int32Array': 'getInt32', 29 | 'Uint32Array': 'getUint32' 30 | } 31 | 32 | /** 33 | * Crypto interface 34 | */ 35 | class Crypto { 36 | 37 | /** 38 | * getRandomValues 39 | */ 40 | getRandomValues (typedArray) { 41 | if (!integerTypes.some(type => typedArray instanceof type)) { 42 | throw new TypeMismatchError() 43 | } 44 | 45 | let byteLength = typedArray.byteLength 46 | 47 | if (byteLength > 65536) { 48 | throw new QuotaExceededError() 49 | } 50 | 51 | let type = typedArray.constructor 52 | let method = integerGetByConstructor[type.name] 53 | let totalBytes = byteLength * typedArray.length 54 | let buffer = legacyCrypto.randomBytes(totalBytes) 55 | let arrayBuffer = new Uint8Array(buffer) 56 | let dataView = new DataView(arrayBuffer.buffer) 57 | 58 | for (let byteIndex = 0; byteIndex < totalBytes; byteIndex += byteLength) { 59 | let integer = dataView[method](byteIndex) 60 | let arrayIndex = byteIndex / byteLength 61 | typedArray[arrayIndex] = integer 62 | } 63 | 64 | return typedArray 65 | } 66 | 67 | /** 68 | * subtle 69 | */ 70 | get subtle () { 71 | return new SubtleCrypto() 72 | } 73 | 74 | } 75 | 76 | /** 77 | * Export 78 | */ 79 | module.exports = Crypto 80 | -------------------------------------------------------------------------------- /src/algorithms/AES-CBC.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Package dependencies 3 | */ 4 | const crypto = require('crypto') 5 | const base64url = require('base64url') 6 | const {TextEncoder, TextDecoder} = require('text-encoding') 7 | 8 | /** 9 | * Local dependencies 10 | */ 11 | const KeyAlgorithm = require('../dictionaries/KeyAlgorithm') 12 | const AesKeyAlgorithm = require('../dictionaries/AesKeyAlgorithm') 13 | const Algorithm = require ('../algorithms/Algorithm') 14 | const CryptoKey = require('../keys/CryptoKey') 15 | const JsonWebKey = require('../keys/JsonWebKey') 16 | 17 | 18 | /** 19 | * Errors 20 | */ 21 | const { 22 | DataError, 23 | OperationError, 24 | InvalidAccessError, 25 | KeyFormatNotSupportedError 26 | } = require('../errors') 27 | 28 | /** 29 | * AES-CBC 30 | */ 31 | class AES_CBC extends Algorithm { 32 | 33 | /** 34 | * dictionaries 35 | */ 36 | static get dictionaries () { 37 | return [ 38 | KeyAlgorithm, 39 | AesKeyAlgorithm 40 | ] 41 | } 42 | 43 | /** 44 | * members 45 | */ 46 | static get members () { 47 | return { 48 | name: String, 49 | modulusLength: Number, 50 | publicExponent: 'BufferSource' 51 | } 52 | } 53 | 54 | /** 55 | * encrypt 56 | * 57 | * @description 58 | * Encrypts an AES-CBC digital signature 59 | * 60 | * @param {AesKeyAlgorithm} algorithm 61 | * @param {CryptoKey} key 62 | * @param {BufferSource} data 63 | * 64 | * @returns {Array} 65 | */ 66 | encrypt (algorithm, key, data) { 67 | // 1. Ensure correct iv length 68 | if (algorithm.iv.byteLength !== 16) { 69 | throw new OperationError('IV Length must be exactly 16 bytes') 70 | } 71 | 72 | // 2. Add padding to erronuous length text as described here: 73 | // https://tools.ietf.org/html/rfc2315#section-10.3 74 | let paddedPlaintext = data 75 | 76 | // 3. Do the encryption 77 | let cipherName 78 | if (key.algorithm.name === 'AES-CBC' && [128,192,256].includes(key.algorithm.length)){ 79 | cipherName = 'AES-' + key.algorithm.length + '-CBC' 80 | } else { 81 | throw new DataError('Invalid AES-CBC and length pair.') 82 | } 83 | let cipher = crypto.createCipheriv(cipherName,key.handle,Buffer.from(algorithm.iv)) 84 | let ciphertext = cipher.update(Buffer.from(data)) 85 | 86 | // 4. Return result 87 | return Uint8Array.from(Buffer.concat([ciphertext,cipher.final()])).buffer 88 | } 89 | 90 | /** 91 | * decrypt 92 | * 93 | * @description 94 | * Decrypts an AES-CBC digital signature 95 | * 96 | * @param {AesKeyAlgorithm} algorithm 97 | * @param {CryptoKey} key 98 | * @param {BufferSource} data 99 | * 100 | * @returns {Array} 101 | */ 102 | decrypt (algorithm, key, data) { 103 | // 1. Ensure correct iv length 104 | if (algorithm.iv.byteLength !== 16){ 105 | throw new OperationError('IV Length must be exactly 16 bytes') 106 | } 107 | 108 | // 2. Perform the decryption 109 | let cipherName 110 | if (key.algorithm.name === 'AES-CBC' && [128,192,256].includes(key.algorithm.length)){ 111 | cipherName = 'AES-' + key.algorithm.length + '-CBC' 112 | } else { 113 | throw new DataError('Invalid AES-CBC and length pair.') 114 | } 115 | let decipher = crypto.createDecipheriv(cipherName,key.handle,Buffer.from(algorithm.iv)) 116 | let ciphertext = decipher.update(Buffer.from(data)) 117 | let plaintext = Array.from(Buffer.concat([ciphertext,decipher.final()])) 118 | 119 | // 3-5. Text de-padding performed by crypto.decipher 120 | 121 | // 6. Return resulting ArrayBuffer 122 | return Uint8Array.from(plaintext).buffer 123 | } 124 | 125 | /** 126 | * generateKey 127 | * 128 | * @description 129 | * Generate an AES-CBC key pair 130 | * 131 | * @param {AesCbcParams} params 132 | * @returns {CryptoKeyPair} 133 | */ 134 | generateKey (params, extractable, usages) { 135 | // 1. Validate usages 136 | usages.forEach(usage => { 137 | if (usage !== 'encrypt' && usage !== 'decrypt' && usage !== 'wrapKey' && usage !== 'unwrapKey') { 138 | throw new SyntaxError('Key usages can only include "encrypt", "decrypt", "wrapKey" or "unwrapKey"') 139 | } 140 | }) 141 | 142 | // 2. Validate length 143 | if (![128,192,256].includes(params.length)) { 144 | throw new OperationError('Member length must be 128, 192, or 256.') 145 | } 146 | 147 | // 3. Generate AES Key 148 | let symmetricKey 149 | try { 150 | symmetricKey = crypto.randomBytes(params.length/8) 151 | // 4. Validate key generation 152 | } catch (error) { 153 | throw new OperationError(error.message) 154 | } 155 | 156 | // 6. Set new AesKeyAlgorithm 157 | let algorithm = new AES_CBC(params) 158 | 159 | // 5. Define new CryptoKey names key 160 | let key = new CryptoKey({ 161 | type: 'secret', 162 | algorithm, 163 | extractable, 164 | usages, 165 | handle: symmetricKey 166 | }) 167 | 168 | // 12. Return Key 169 | return key 170 | } 171 | 172 | /** 173 | * importKey 174 | * 175 | * @description 176 | * 177 | * @param {string} format 178 | * @param {string|JsonWebKey} keyData 179 | * @param {KeyAlgorithm} algorithm 180 | * @param {Boolean} extractable 181 | * @param {Array} keyUsages 182 | * 183 | * @returns {CryptoKey} 184 | */ 185 | importKey (format, keyData, algorithm, extractable, keyUsages) { 186 | let data, jwk 187 | 188 | // 1. Validate keyUsages 189 | keyUsages.forEach(usage => { 190 | if (usage !== 'encrypt' 191 | && usage !== 'decrypt' 192 | && usage !== 'wrapKey' 193 | && usage !== 'unwrapKey') { 194 | throw new SyntaxError('Key usages can only include "encrypt", "decrypt", "wrapKey" or "unwrapKey"') 195 | } 196 | }) 197 | 198 | // 2.1 "raw" format 199 | if (format === 'raw'){ 200 | // 2.1.1 Let data be the octet string contained in keyData 201 | data = Buffer.from(keyData) 202 | 203 | // 2.1.2 Ensure data length is 128, 192 or 256 204 | if (![16,24,32].includes(data.length)){ 205 | throw new DataError('Length of data bits must be 128, 192 or 256.') 206 | } 207 | } 208 | 209 | // 2.2 "jwk" format 210 | else if (format === 'jwk'){ 211 | // 2.2.1 Ensure data is JsonWebKey dictionary 212 | if (typeof keyData === 'object' && !Array.isArray(keyData)){ 213 | jwk = new JsonWebKey(keyData) 214 | } else { 215 | throw new DataError('Invalid jwk format') 216 | } 217 | 218 | // 2.2.2 Validate "kty" field heuristic 219 | if (jwk.kty !== "oct"){ 220 | throw new DataError('kty property must be "oct"') 221 | } 222 | 223 | // 2.2.3 Ensure jwk meets these requirements: 224 | // https://tools.ietf.org/html/rfc7518#section-6.4 225 | if (!jwk.k){ 226 | throw new DataError('k property must not be empty') 227 | } 228 | 229 | // 2.2.4 Assign data 230 | data = base64url.toBuffer(jwk.k) 231 | 232 | // 2.2.5 Validate data lengths 233 | if (data.length === 16) { 234 | if (jwk.alg && jwk.alg !== 'A128CBC'){ 235 | throw new DataError('Algorithm "A128CBC" must be 128 bits in length') 236 | } 237 | } else if (data.length === 24) { 238 | if (jwk.alg && jwk.alg !== 'A192CBC'){ 239 | throw new DataError('Algorithm "A192CBC" must be 192 bits in length') 240 | } 241 | } else if (data.length === 32) { 242 | if (jwk.alg && jwk.alg !== 'A256CBC'){ 243 | throw new DataError('Algorithm "A256CBC" must be 256 bits in length') 244 | } 245 | } else { 246 | throw new DataError('Algorithm and data length mismatch') 247 | } 248 | 249 | // 2.2.6 Validate "use" field 250 | if (keyUsages && jwk.use && jwk.use !== 'enc'){ 251 | throw new DataError('Key use must be "enc"') 252 | } 253 | 254 | // 2.2.7 Validate "key_ops" field 255 | if (jwk.key_ops){ 256 | jwk.key_ops.forEach(op => { 257 | if (op !== 'encrypt' 258 | && op !== 'decrypt' 259 | && op !== 'wrapKey' 260 | && op !== 'unwrapKey') { 261 | throw new DataError('Key operation can only include "encrypt", "decrypt", "wrapKey" or "unwrapKey"') 262 | } 263 | }) 264 | } 265 | 266 | // 2.2.8 validate "ext" field 267 | if (jwk.ext === false && extractable === true){ 268 | throw new DataError('Cannot be extractable when "ext" is set to false') 269 | } 270 | } 271 | 272 | // 2.3 Otherwise... 273 | else { 274 | throw new KeyFormatNotSupportedError(format) 275 | } 276 | 277 | // 3. Generate new key 278 | let key = new CryptoKey({ 279 | type: 'secret', 280 | extractable, 281 | usages: keyUsages, 282 | handle: data 283 | }) 284 | 285 | // 4-6. Generate algorithm 286 | let aesAlgorithm = new AES_CBC( 287 | { name: 'AES-CBC', 288 | length: data.length * 8 289 | }) 290 | 291 | // 7. Set algorithm to internal algorithm property of key 292 | key.algorithm = aesAlgorithm 293 | 294 | // 8. Return key 295 | return key 296 | } 297 | 298 | /** 299 | * exportKey 300 | * 301 | * @description 302 | * 303 | * @param {string} format 304 | * @param {CryptoKey} key 305 | * 306 | * @returns {*} 307 | */ 308 | exportKey (format, key) { 309 | let result, data 310 | 311 | // 1. Validate handle slot 312 | if (!key.handle) { 313 | throw new OperationError('Missing key material') 314 | } 315 | 316 | // 2.1 "raw" format 317 | if (format === 'raw'){ 318 | // 2.1.1 Let data be the raw octets of the key 319 | data = key.handle 320 | // 2.1.2 Let result be containing data 321 | result = Buffer.from(data) 322 | } 323 | 324 | // 2.2 "jwk" format 325 | else if (format === 'jwk'){ 326 | // 2.2.1 Validate JsonWebKey 327 | let jwk = new JsonWebKey() 328 | 329 | // 2.2.2 Set kty property 330 | jwk.kty = 'oct' 331 | 332 | // 2.2.3 Set k property 333 | jwk.k = base64url(key.handle) 334 | data = key.handle 335 | 336 | // 2.2.4 Validate length 337 | if (data.length === 16) { 338 | jwk.alg = 'A128CBC' 339 | } else if (data.length === 24) { 340 | jwk.alg = 'A192CBC' 341 | } else if (data.length === 32) { 342 | jwk.alg = 'A256CBC' 343 | } 344 | // 2.2.5 Set keyops property 345 | jwk.key_ops = key.usages 346 | 347 | // 2.2.6 Set ext property 348 | jwk.ext = key.extractable 349 | 350 | // 2.2.7 Set result to the result of converting jwk to an ECMAScript object 351 | result = jwk 352 | } 353 | 354 | // 2.3 Otherwise... 355 | else { 356 | throw new KeyFormatNotSupportedError(format) 357 | } 358 | 359 | // 3. Return result 360 | return result 361 | } 362 | } 363 | 364 | /** 365 | * Export 366 | */ 367 | module.exports = AES_CBC -------------------------------------------------------------------------------- /src/algorithms/AES-CTR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Package dependencies 3 | */ 4 | const crypto = require('crypto') 5 | const base64url = require('base64url') 6 | const {TextEncoder, TextDecoder} = require('text-encoding') 7 | 8 | /** 9 | * Local dependencies 10 | */ 11 | const KeyAlgorithm = require('../dictionaries/KeyAlgorithm') 12 | const AesKeyAlgorithm = require('../dictionaries/AesKeyAlgorithm') 13 | const Algorithm = require ('../algorithms/Algorithm') 14 | const CryptoKey = require('../keys/CryptoKey') 15 | const JsonWebKey = require('../keys/JsonWebKey') 16 | 17 | 18 | /** 19 | * Errors 20 | */ 21 | const { 22 | DataError, 23 | OperationError, 24 | InvalidAccessError, 25 | KeyFormatNotSupportedError 26 | } = require('../errors') 27 | 28 | /** 29 | * AES-CTR 30 | */ 31 | class AES_CTR extends Algorithm { 32 | 33 | /** 34 | * dictionaries 35 | */ 36 | static get dictionaries () { 37 | return [ 38 | KeyAlgorithm, 39 | AesKeyAlgorithm 40 | ] 41 | } 42 | 43 | /** 44 | * members 45 | */ 46 | static get members () { 47 | return { 48 | name: String, 49 | modulusLength: Number, 50 | publicExponent: 'BufferSource' 51 | } 52 | } 53 | 54 | /** 55 | * encrypt 56 | * 57 | * @description 58 | * Encrypts an AES-CTR digital signature 59 | * 60 | * @param {AesKeyAlgorithm} algorithm 61 | * @param {CryptoKey} key 62 | * @param {BufferSource} data 63 | * 64 | * @returns {Array} 65 | */ 66 | encrypt (algorithm, key, data) { 67 | // 1. Ensure correct counter length 68 | if (algorithm.counter === undefined || algorithm.counter.byteLength !== 16){ 69 | throw new OperationError('Counter must be exactly 16 bytes') 70 | } 71 | 72 | // 2. Ensure correct length size 73 | if (algorithm.length === undefined || algorithm.length === 0 || algorithm.length > 128){ 74 | throw new OperationError('Length must be non zero and less than or equal to 128') 75 | } 76 | 77 | // 3. Do the encryption 78 | let cipherName 79 | if (key.algorithm.name === 'AES-CTR' && [128,192,256].includes(key.algorithm.length)){ 80 | cipherName = 'AES-' + key.algorithm.length + '-CTR' 81 | } else { 82 | throw new DataError('Invalid AES-CTR and length pair.') 83 | } 84 | let cipher = crypto.createCipheriv(cipherName,key.handle,Buffer.from(algorithm.counter)) 85 | 86 | // 4. Return result 87 | return Uint8Array.from(Buffer.concat([cipher.update(data),cipher.final()])).buffer 88 | } 89 | 90 | /** 91 | * decrypt 92 | * 93 | * @description 94 | * Decrypts an AES-CTR digital signature 95 | * 96 | * @param {AesKeyAlgorithm} algorithm 97 | * @param {CryptoKey} key 98 | * @param {BufferSource} data 99 | * 100 | * @returns {Array} 101 | */ 102 | decrypt (algorithm, key, data) { 103 | // 1. Ensure correct counter length 104 | if (algorithm.counter === undefined || algorithm.counter.byteLength !== 16) { 105 | throw new OperationError('Counter must be exactly 16 bytes') 106 | } 107 | 108 | // 2. Ensure correct length size 109 | if (algorithm.length === undefined || algorithm.length === 0 || algorithm.length > 128){ 110 | throw new OperationError('Length must be non zero and less than or equal to 128') 111 | } 112 | 113 | // 3. Perform the decryption 114 | let cipherName 115 | if (key.algorithm.name === 'AES-CTR' && [128,192,256].includes(key.algorithm.length)){ 116 | cipherName = 'AES-' + key.algorithm.length + '-CTR' 117 | } else { 118 | throw new DataError('Invalid AES-CTR and length pair.') 119 | } 120 | let decipher = crypto.createDecipheriv(cipherName,key.handle,algorithm.counter) 121 | let plaintext = Array.from(Buffer.concat([decipher.update(Buffer.from(data)),decipher.final()])) 122 | 123 | // 4. Return resulting ArrayBuffer 124 | return Uint8Array.from(plaintext).buffer 125 | } 126 | 127 | /** 128 | * generateKey 129 | * 130 | * @description 131 | * Generate an AES-CTR key pair 132 | * 133 | * @param {AesCtrParams} params 134 | * @returns {CryptoKeyPair} 135 | */ 136 | generateKey (params, extractable, usages) { 137 | // 1. Validate usages 138 | usages.forEach(usage => { 139 | if (usage !== 'encrypt' && usage !== 'decrypt' && usage !== 'wrapKey' && usage !== 'unwrapKey') { 140 | throw new SyntaxError('Key usages can only include "encrypt", "decrypt", "wrapKey" or "unwrapKey"') 141 | } 142 | }) 143 | 144 | // 2. Validate length 145 | if (![128,192,256].includes(params.length)) { 146 | throw new OperationError('Member length must be 128, 192, or 256.') 147 | } 148 | 149 | // 3. Generate AES Key 150 | let symmetricKey 151 | try { 152 | symmetricKey = crypto.randomBytes(params.length/8) 153 | // 4. Validate key generation 154 | } catch (error) { 155 | throw new OperationError(error.message) 156 | } 157 | 158 | // Set new AesKeyAlgorithm 159 | let algorithm = new AES_CTR(params) 160 | 161 | // 5-11. Define new CryptoKey names key 162 | let key = new CryptoKey({ 163 | type: 'secret', 164 | algorithm, 165 | extractable, 166 | usages, 167 | handle: symmetricKey 168 | }) 169 | 170 | // 12. Return Key 171 | return key 172 | } 173 | 174 | /** 175 | * importKey 176 | * 177 | * @description 178 | * 179 | * @param {string} format 180 | * @param {string|JsonWebKey} keyData 181 | * @param {KeyAlgorithm} algorithm 182 | * @param {Boolean} extractable 183 | * @param {Array} keyUsages 184 | * 185 | * @returns {CryptoKey} 186 | */ 187 | importKey (format, keyData, algorithm, extractable, keyUsages) { 188 | let data, jwk 189 | 190 | // 1. Validate keyUsages 191 | keyUsages.forEach(usage => { 192 | if (usage !== 'encrypt' 193 | && usage !== 'decrypt' 194 | && usage !== 'wrapKey' 195 | && usage !== 'unwrapKey') { 196 | throw new SyntaxError('Key usages can only include "encrypt", "decrypt", "wrapKey" or "unwrapKey"') 197 | } 198 | }) 199 | 200 | // 2.1 "raw" format 201 | if (format === 'raw'){ 202 | // 2.1.1 Let data be the octet string contained in keyData 203 | data = Buffer.from(keyData) 204 | 205 | // 2.1.2 Ensure data length is 128, 192 or 256 206 | if (![16,24,32].includes(data.length)){ 207 | throw new DataError('Length of data bits must be 128, 192 or 256.') 208 | } 209 | } 210 | 211 | // 2.2 "jwk" format 212 | else if (format === 'jwk'){ 213 | // 2.2.1 Ensure data is JsonWebKey dictionary 214 | if (typeof keyData === 'object' && !Array.isArray(keyData)){ 215 | jwk = new JsonWebKey(keyData) 216 | } else { 217 | throw new DataError('Invalid jwk format') 218 | } 219 | 220 | // 2.2.2 Validate "kty" field heuristic 221 | if (jwk.kty !== "oct"){ 222 | throw new DataError('kty property must be "oct"') 223 | } 224 | 225 | // 2.2.3 Ensure jwk meets these requirements: 226 | // https://tools.ietf.org/html/rfc7518#section-6.4 227 | if (!jwk.k){ 228 | throw new DataError('k property must not be empty') 229 | } 230 | 231 | // 2.2.4 Assign data 232 | data = base64url.toBuffer(jwk.k) 233 | 234 | // 2.2.5 Validate data lengths 235 | if (data.length === 16) { 236 | if (jwk.alg && jwk.alg !== 'A128CTR'){ 237 | throw new DataError('Algorithm "A128CTR" must be 128 bits in length') 238 | } 239 | } else if (data.length === 24) { 240 | if (jwk.alg && jwk.alg !== 'A192CTR'){ 241 | throw new DataError('Algorithm "A192CTR" must be 192 bits in length') 242 | } 243 | } else if (data.length === 32) { 244 | if (jwk.alg && jwk.alg !== 'A256CTR'){ 245 | throw new DataError('Algorithm "A256CTR" must be 256 bits in length') 246 | } 247 | } else { 248 | throw new DataError('Algorithm and data length mismatch') 249 | } 250 | 251 | // 2.2.6 Validate "use" field 252 | if (keyUsages && jwk.use && jwk.use !== 'enc'){ 253 | throw new DataError('Key use must be "enc"') 254 | } 255 | 256 | // 2.2.7 Validate "key_ops" field 257 | if (jwk.key_ops){ 258 | jwk.key_ops.forEach(op => { 259 | if (op !== 'encrypt' 260 | && op !== 'decrypt' 261 | && op !== 'wrapKey' 262 | && op !== 'unwrapKey') { 263 | throw new DataError('Key operation can only include "encrypt", "decrypt", "wrapKey" or "unwrapKey"') 264 | } 265 | }) 266 | } 267 | 268 | // 2.2.8 validate "ext" field 269 | if (jwk.ext === false && extractable === true){ 270 | throw new DataError('Cannot be extractable when "ext" is set to false') 271 | } 272 | } 273 | 274 | // 2.3 Otherwise... 275 | else { 276 | throw new KeyFormatNotSupportedError(format) 277 | } 278 | 279 | // 3. Generate new key 280 | let key = new CryptoKey({ 281 | type: 'secret', 282 | extractable, 283 | usages: keyUsages, 284 | handle: data 285 | }) 286 | 287 | // 4-6. Generate algorithm 288 | let aesAlgorithm = new AES_CTR({ 289 | name: 'AES-CTR', 290 | length: data.length * 8 291 | }) 292 | 293 | // 7. Set algorithm to internal algorithm property of key 294 | key.algorithm = aesAlgorithm 295 | 296 | // 8. Return key 297 | return key 298 | } 299 | 300 | /** 301 | * exportKey 302 | * 303 | * @description 304 | * 305 | * @param {string} format 306 | * @param {CryptoKey} key 307 | * 308 | * @returns {*} 309 | */ 310 | exportKey (format, key) { 311 | let result, data 312 | 313 | // 1. Validate handle slot 314 | if (!key.handle) { 315 | throw new OperationError('Missing key material') 316 | } 317 | 318 | // 2.1 "raw" format 319 | if (format === 'raw'){ 320 | // 2.1.1 Let data be the raw octets of the key 321 | data = key.handle 322 | // 2.1.2 Let result be containing data 323 | result = Buffer.from(data) 324 | } 325 | 326 | // 2.2 "jwk" format 327 | else if (format === 'jwk'){ 328 | // 2.2.1 Validate JsonWebKey 329 | let jwk = new JsonWebKey(key) 330 | 331 | // 2.2.2 Set kty property 332 | jwk.kty = 'oct' 333 | 334 | // 2.2.3 Set k property 335 | jwk.k = base64url(key.handle) 336 | data = key.handle 337 | 338 | // 2.2.4 Validate length 339 | if (data.length === 16) { 340 | jwk.alg = 'A128CTR' 341 | } else if (data.length === 24) { 342 | jwk.alg = 'A192CTR' 343 | } else if (data.length === 32) { 344 | jwk.alg = 'A256CTR' 345 | } 346 | // 2.2.5 Set keyops property 347 | jwk.key_ops = key.usages 348 | 349 | // 2.2.6 Set ext property 350 | jwk.ext = key.extractable 351 | 352 | // 2.2.7 Set result to the result of converting jwk to an ECMAScript object 353 | result = jwk 354 | } 355 | 356 | // 2.3 Otherwise... 357 | else { 358 | throw new KeyFormatNotSupportedError(format) 359 | } 360 | 361 | // 3. Return result 362 | return result 363 | } 364 | } 365 | 366 | /** 367 | * Export 368 | */ 369 | module.exports = AES_CTR 370 | 371 | -------------------------------------------------------------------------------- /src/algorithms/AES-GCM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Package dependencies 3 | */ 4 | const crypto = require('crypto') 5 | const base64url = require('base64url') 6 | const {TextEncoder, TextDecoder} = require('text-encoding') 7 | 8 | /** 9 | * Local dependencies 10 | */ 11 | const KeyAlgorithm = require('../dictionaries/KeyAlgorithm') 12 | const AesKeyAlgorithm = require('../dictionaries/AesKeyAlgorithm') 13 | const Algorithm = require ('../algorithms/Algorithm') 14 | const CryptoKey = require('../keys/CryptoKey') 15 | const JsonWebKey = require('../keys/JsonWebKey') 16 | 17 | 18 | /** 19 | * Errors 20 | */ 21 | const { 22 | DataError, 23 | OperationError, 24 | InvalidAccessError, 25 | KeyFormatNotSupportedError, 26 | CurrentlyNotSupportedError 27 | } = require('../errors') 28 | 29 | /** 30 | * AES-GCM 31 | */ 32 | class AES_GCM extends Algorithm { 33 | 34 | /** 35 | * dictionaries 36 | */ 37 | static get dictionaries () { 38 | return [ 39 | KeyAlgorithm, 40 | AesKeyAlgorithm 41 | ] 42 | } 43 | 44 | /** 45 | * members 46 | */ 47 | static get members () { 48 | return { 49 | name: String, 50 | modulusLength: Number, 51 | publicExponent: 'BufferSource' 52 | } 53 | } 54 | 55 | /** 56 | * encrypt 57 | * 58 | * @description 59 | * Encrypts an AES-GCM digital signature 60 | * 61 | * @param {AesKeyAlgorithm} algorithm 62 | * @param {CryptoKey} key 63 | * @param {BufferSource} data 64 | * 65 | * @returns {Array} 66 | */ 67 | encrypt (algorithm, key, data) { 68 | // 1. Ensure correct data length 69 | if (data.byteLength === undefined || data.byteLength > 549755813632) { // 2^39-256 70 | throw new OperationError('Data must have a length less than 549755813632.') 71 | } 72 | 73 | // 2. Ensure correct iv length 74 | if (algorithm.iv.byteLength === undefined || algorithm.iv.byteLength > 18446744073709551615) { // 2^64-1 75 | throw new OperationError('IV Length must be less than 18446744073709551615 in length.') 76 | } 77 | 78 | // 3. Ensure correct additionalData 79 | if (algorithm.additionalData !== undefined 80 | && (algorithm.additionalData.length === undefined 81 | || algorithm.additionalData.length > 18446744073709551615)) { // 2^64-1 82 | throw new OperationError('AdditionalData must be less than 18446744073709551615 in length.') 83 | } 84 | 85 | // 4. Verify tagLength 86 | // Note: node only support tag length up to 128, so there is some discrepancy between 87 | // this, and the spec outline: https://www.w3.org/TR/WebCryptoAPI/#aes-gcm 88 | let tagLength 89 | if (algorithm.tagLength === undefined){ 90 | tagLength = 128 91 | } else if ([32,64,96,104,112,120].includes(algorithm.tagLength)) { 92 | throw new CurrentlyNotSupportedError('Node currently only supports 128 tagLength.', '128') 93 | } else if (algorithm.tagLength !== 128) { 94 | throw new OperationError('TagLength is an invalid size.') 95 | } else { 96 | tagLength = algorithm.tagLength 97 | } 98 | 99 | // 5. Assign additionalData 100 | let additionalData 101 | if (algorithm.additionalData !== undefined){ 102 | additionalData = Buffer.from(algorithm.additionalData) 103 | } else { 104 | additionalData = Buffer.from('') 105 | } 106 | 107 | // 6. Do the encryption 108 | let cipherName 109 | if (key.algorithm.name === 'AES-GCM' && [128,192,256].includes(key.algorithm.length)){ 110 | cipherName = 'aes-' + key.algorithm.length + '-gcm' 111 | } else { 112 | throw new DataError('Invalid AES-GCM and length pair.') 113 | } 114 | let cipher = crypto.createCipheriv(cipherName,key.handle,Buffer.from(algorithm.iv)) 115 | cipher.setAAD(additionalData) 116 | let ciphertext = cipher.update(Buffer.from(data)) 117 | ciphertext = Buffer.concat([ciphertext,cipher.final()]) 118 | 119 | // 7. Concat C and T 120 | let authTag = cipher.getAuthTag() 121 | ciphertext = Buffer.concat([ciphertext,authTag]) 122 | 123 | // 8. Return result 124 | return Uint8Array.from(ciphertext).buffer 125 | } 126 | 127 | /** 128 | * decrypt 129 | * 130 | * @description 131 | * Decrypts an AES-GCM digital signature 132 | * 133 | * @param {AesKeyAlgorithm} algorithm 134 | * @param {CryptoKey} key 135 | * @param {BufferSource} data 136 | * 137 | * @returns {Array} 138 | */ 139 | decrypt (algorithm, key, data) { 140 | // 1. Verify tagLength 141 | let tagLength 142 | if (algorithm.tagLength === undefined){ 143 | tagLength = 128 144 | } else if ([32,64,96,104,112,120].includes(algorithm.tagLength)) { 145 | throw new CurrentlyNotSupportedError('Node currently only supports 128 tagLength.', '128') 146 | } else if (algorithm.tagLength !== 128) { 147 | throw new OperationError('TagLength is an invalid size.') 148 | } else { 149 | tagLength = algorithm.tagLength 150 | } 151 | 152 | // 2. Verify data length 153 | if ((data.length * 8) < tagLength){ 154 | throw new OperationError('Data length cannot be less than tagLength.') 155 | } 156 | 157 | // 3. Ensure correct iv length 158 | if (algorithm.iv.byteLength === undefined || algorithm.iv.byteLength > 18446744073709551615) { // 2^64-1 159 | throw new OperationError('IV Length must be less than 18446744073709551615 in length.') 160 | } 161 | 162 | // 4 & 7. Ensure correct additionalData 163 | let additionalData 164 | if (algorithm.additionalData !== undefined){ 165 | if (algorithm.additionalData.length === undefined 166 | || algorithm.additionalData.length > 18446744073709551615) { // 2^64-1 167 | throw new OperationError('AdditionalData must be less than 18446744073709551615 in length.') 168 | } else { 169 | additionalData = Buffer.from(algorithm.additionalData) 170 | } 171 | } else{ 172 | additionalData = Buffer.from('') 173 | } 174 | 175 | // 5. Get the AuthTag 176 | data = Buffer.from(data) 177 | let tagLengthBytes = tagLength/8 178 | let tag = data.slice(-tagLengthBytes) 179 | 180 | // 6. Get the actualCiphertext 181 | let actualCiphertext = data.slice(0,-tagLengthBytes) 182 | 183 | // 8. Perform the decryption 184 | let cipherName 185 | if (key.algorithm.name === 'AES-GCM' && [128,192,256].includes(key.algorithm.length)){ 186 | cipherName = 'aes-' + key.algorithm.length + '-gcm' 187 | } else { 188 | throw new DataError('Invalid AES-GCM and length pair.') 189 | } 190 | let decipher = crypto.createDecipheriv(cipherName,key.handle,Buffer.from(algorithm.iv)) 191 | decipher.setAAD(additionalData) 192 | decipher.setAuthTag(tag) 193 | let plaintext = decipher.update(Buffer.from(actualCiphertext)) 194 | plaintext = Buffer.concat([plaintext,decipher.final()]) 195 | 196 | // 9. Return resulting ArrayBuffer 197 | return Uint8Array.from(plaintext).buffer 198 | } 199 | 200 | /** 201 | * generateKey 202 | * 203 | * @description 204 | * Generate an AES-GCM key pair 205 | * 206 | * @param {AesGcmParams} params 207 | * @returns {CryptoKeyPair} 208 | */ 209 | generateKey (params, extractable, usages) { 210 | // 1. Validate usages 211 | usages.forEach(usage => { 212 | if (usage !== 'encrypt' && usage !== 'decrypt' && usage !== 'wrapKey' && usage !== 'unwrapKey') { 213 | throw new SyntaxError('Key usages can only include "encrypt", "decrypt", "wrapKey" or "unwrapKey"') 214 | } 215 | }) 216 | 217 | // 2. Validate length 218 | if (![128,192,256].includes(params.length)) { 219 | throw new OperationError('Member length must be 128, 192, or 256.') 220 | } 221 | 222 | // 3. Generate AES Key 223 | let symmetricKey 224 | try { 225 | symmetricKey = crypto.randomBytes(params.length/8) 226 | // 4. Validate key generation 227 | } catch (error) { 228 | throw new OperationError(error.message) 229 | } 230 | 231 | // 6. Set new AesKeyAlgorithm 232 | let algorithm = new AES_GCM(params) 233 | 234 | // 5. Define new CryptoKey names key 235 | let key = new CryptoKey({ 236 | type: 'secret', 237 | algorithm, 238 | extractable, 239 | usages, 240 | handle: symmetricKey 241 | }) 242 | 243 | // 12. Return Key 244 | return key 245 | } 246 | 247 | /** 248 | * importKey 249 | * 250 | * @description 251 | * 252 | * @param {string} format 253 | * @param {string|JsonWebKey} keyData 254 | * @param {KeyAlgorithm} algorithm 255 | * @param {Boolean} extractable 256 | * @param {Array} keyUsages 257 | * 258 | * @returns {CryptoKey} 259 | */ 260 | importKey (format, keyData, algorithm, extractable, keyUsages) { 261 | let data, jwk 262 | 263 | // 1. Validate keyUsages 264 | keyUsages.forEach(usage => { 265 | if (usage !== 'encrypt' 266 | && usage !== 'decrypt' 267 | && usage !== 'wrapKey' 268 | && usage !== 'unwrapKey') { 269 | throw new SyntaxError('Key usages can only include "encrypt", "decrypt", "wrapKey" or "unwrapKey"') 270 | } 271 | }) 272 | 273 | // 2.1 "raw" format 274 | if (format === 'raw'){ 275 | // 2.1.1 Let data be the octet string contained in keyData 276 | data = Buffer.from(keyData) 277 | 278 | // 2.1.2 Ensure data length is 128, 192 or 256 279 | if (![16,24,32].includes(data.length)){ 280 | throw new DataError('Length of data bits must be 128, 192 or 256.') 281 | } 282 | } 283 | 284 | // 2.2 "jwk" format 285 | else if (format === 'jwk'){ 286 | // 2.2.1 Ensure data is JsonWebKey dictionary 287 | if (typeof keyData === 'object' && !Array.isArray(keyData)){ 288 | jwk = new JsonWebKey(keyData) 289 | } else { 290 | throw new DataError('Invalid jwk format') 291 | } 292 | 293 | // 2.2.2 Validate "kty" field heuristic 294 | if (jwk.kty !== "oct"){ 295 | throw new DataError('kty property must be "oct"') 296 | } 297 | 298 | // 2.2.3 Ensure jwk meets these requirements: 299 | // https://tools.ietf.org/html/rfc7518#section-6.4 300 | if (!jwk.k){ 301 | throw new DataError('k property must not be empty') 302 | } 303 | 304 | // 2.2.4 Assign data 305 | data = base64url.toBuffer(jwk.k) 306 | 307 | // 2.2.5 Validate data lengths 308 | if (data.length === 16) { 309 | if (jwk.alg && jwk.alg !== 'A128GCM'){ 310 | throw new DataError('Algorithm "A128GCM" must be 128 bits in length') 311 | } 312 | } else if (data.length === 24) { 313 | if (jwk.alg && jwk.alg !== 'A192GCM'){ 314 | throw new DataError('Algorithm "A192GCM" must be 192 bits in length') 315 | } 316 | } else if (data.length === 32) { 317 | if (jwk.alg && jwk.alg !== 'A256GCM'){ 318 | throw new DataError('Algorithm "A256GCM" must be 256 bits in length') 319 | } 320 | } else { 321 | throw new DataError('Algorithm and data length mismatch') 322 | } 323 | 324 | // 2.2.6 Validate "use" field 325 | if (keyUsages && jwk.use && jwk.use !== 'enc'){ 326 | throw new DataError('Key use must be "enc"') 327 | } 328 | 329 | // 2.2.7 Validate "key_ops" field 330 | if (jwk.key_ops){ 331 | jwk.key_ops.forEach(op => { 332 | if (op !== 'encrypt' 333 | && op !== 'decrypt' 334 | && op !== 'wrapKey' 335 | && op !== 'unwrapKey') { 336 | throw new DataError('Key operation can only include "encrypt", "decrypt", "wrapKey" or "unwrapKey"') 337 | } 338 | }) 339 | } 340 | 341 | // 2.2.8 validate "ext" field 342 | if (jwk.ext === false && extractable === true){ 343 | throw new DataError('Cannot be extractable when "ext" is set to false') 344 | } 345 | } 346 | 347 | // 2.3 Otherwise... 348 | else { 349 | throw new KeyFormatNotSupportedError(format) 350 | } 351 | 352 | // 3. Generate new key 353 | let key = new CryptoKey({ 354 | type: 'secret', 355 | extractable, 356 | usages: keyUsages, 357 | handle: data 358 | }) 359 | 360 | // 4-6. Generate algorithm 361 | let aesAlgorithm = new AES_GCM( 362 | { name: 'AES-GCM', 363 | length: data.length * 8 364 | }) 365 | 366 | // 7. Set algorithm to internal algorithm property of key 367 | key.algorithm = aesAlgorithm 368 | 369 | // 8. Return key 370 | return key 371 | } 372 | 373 | /** 374 | * exportKey 375 | * 376 | * @description 377 | * 378 | * @param {string} format 379 | * @param {CryptoKey} key 380 | * 381 | * @returns {*} 382 | */ 383 | exportKey (format, key) { 384 | let result, data 385 | 386 | // 1. Validate handle slot 387 | if (!key.handle) { 388 | throw new OperationError('Missing key material') 389 | } 390 | 391 | // 2.1 "raw" format 392 | if (format === 'raw'){ 393 | // 2.1.1 Let data be the raw octets of the key 394 | data = key.handle 395 | // 2.1.2 Let result be containing data 396 | result = Buffer.from(data) 397 | } 398 | 399 | // 2.2 "jwk" format 400 | else if (format === 'jwk'){ 401 | // 2.2.1 Validate JsonWebKey 402 | let jwk = new JsonWebKey() 403 | 404 | // 2.2.2 Set kty property 405 | jwk.kty = 'oct' 406 | 407 | // 2.2.3 Set k property 408 | jwk.k = base64url(key.handle) 409 | data = key.handle 410 | 411 | // 2.2.4 Validate length 412 | if (data.length === 16) { 413 | jwk.alg = 'A128GCM' 414 | } else if (data.length === 24) { 415 | jwk.alg = 'A192GCM' 416 | } else if (data.length === 32) { 417 | jwk.alg = 'A256GCM' 418 | } 419 | // 2.2.5 Set keyops property 420 | jwk.key_ops = key.usages 421 | 422 | // 2.2.6 Set ext property 423 | jwk.ext = key.extractable 424 | 425 | // 2.2.7 Set result to the result of converting jwk to an ECMAScript object 426 | result = jwk 427 | } 428 | 429 | // 2.3 Otherwise... 430 | else { 431 | throw new KeyFormatNotSupportedError(format) 432 | } 433 | 434 | // 3. Return result 435 | return result 436 | } 437 | } 438 | 439 | /** 440 | * Export 441 | */ 442 | module.exports = AES_GCM 443 | -------------------------------------------------------------------------------- /src/algorithms/AES-KW.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Package dependencies 3 | */ 4 | const crypto = require('crypto') 5 | const base64url = require('base64url') 6 | const {TextEncoder, TextDecoder} = require('text-encoding') 7 | 8 | /** 9 | * Local dependencies 10 | */ 11 | const KeyAlgorithm = require('../dictionaries/KeyAlgorithm') 12 | const AesKeyAlgorithm = require('../dictionaries/AesKeyAlgorithm') 13 | const Algorithm = require ('../algorithms/Algorithm') 14 | const CryptoKey = require('../keys/CryptoKey') 15 | const JsonWebKey = require('../keys/JsonWebKey') 16 | const supportedAlgorithms = require('../algorithms') 17 | 18 | /** 19 | * Errors 20 | */ 21 | const { 22 | DataError, 23 | OperationError, 24 | InvalidAccessError, 25 | KeyFormatNotSupportedError, 26 | CurrentlyNotSupportedError 27 | } = require('../errors') 28 | 29 | /** 30 | * AES-KW 31 | */ 32 | class AES_KW extends Algorithm { 33 | 34 | /** 35 | * dictionaries 36 | */ 37 | static get dictionaries () { 38 | return [ 39 | KeyAlgorithm, 40 | AesKeyAlgorithm 41 | ] 42 | } 43 | 44 | /** 45 | * members 46 | */ 47 | static get members () { 48 | return { 49 | name: String, 50 | modulusLength: Number, 51 | publicExponent: 'BufferSource' 52 | } 53 | } 54 | 55 | /** 56 | * wrapKey 57 | * 58 | * @description 59 | * 60 | * @param {string} format 61 | * @param {Any} key 62 | * @param {CryptoKey} wrappingKey 63 | * @param {KeyAlgorithm} wrappingAlgorithm 64 | * 65 | * @returns {Array} 66 | */ 67 | wrapKey (format, key, wrappingKey, wrappingAlgorithm) { 68 | // Currently only raw is supported 69 | if (format.toLowerCase() !== 'raw'){ 70 | throw new CurrentlyNotSupportedError(format,'raw') 71 | } 72 | 73 | // Determine what format the key is a jwk or simple a handle 74 | let data 75 | if (key instanceof CryptoKey){ 76 | data = key.algorithm.exportKey("raw",key) 77 | } else if (Buffer.isBuffer(key)){ 78 | data = key 79 | } else { 80 | throw new OperationError('Key must be a CryptoKey or BufferSource') 81 | } 82 | 83 | // 1. Ensure the data is a multiple of 8 (not just 64) 84 | if (!data.length || data.length % 8 !== 0){ 85 | throw new OperationError('Invalid key length. Must be multiple of 8.') 86 | } 87 | 88 | // 2. Do the wrap 89 | let ciphertext, cipher 90 | try { 91 | let cipherName 92 | if (wrappingKey.algorithm.name === 'AES-KW' && [128,192,256].includes(wrappingKey.algorithm.length)){ 93 | cipherName = 'id-aes' + wrappingKey.algorithm.length + '-wrap' 94 | } else { 95 | throw new DataError('Invalid AES-KW and length pair.') 96 | } 97 | let iv = Buffer.from('A6A6A6A6A6A6A6A6', 'hex') 98 | cipher = crypto.createCipheriv(cipherName,wrappingKey.handle,iv) 99 | ciphertext = cipher.update(data) 100 | } catch (error) { 101 | throw new OperationError(error.message) 102 | } 103 | 104 | // 3. Return result 105 | return Uint8Array.from(Buffer.concat([ciphertext,cipher.final()])).buffer 106 | } 107 | 108 | /** 109 | * unwrapKey 110 | * 111 | * @description 112 | * 113 | * @param {KeyFormat} format 114 | * @param {BufferSource} wrappedKey 115 | * @param {CryptoKey} unwrappingKey 116 | * @param {AlgorithmIdentifier} unwrapAlgorithm 117 | * @param {AlgorithmIdentifier} unwrappedKeyAlgorithm 118 | * @param {Boolean} extractable 119 | * @param {Array} keyUsages 120 | * 121 | * @returns {Array} 122 | */ 123 | unwrapKey (format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) { 124 | // Currently only raw is supported 125 | if (format.toLowerCase() !== 'raw'){ 126 | throw new CurrentlyNotSupportedError(format,'raw') 127 | } 128 | 129 | // 1-2. Do the unwrap operation 130 | let plaintext 131 | try { 132 | let cipherName 133 | if (unwrappingKey.algorithm.name === 'AES-KW' && [128,192,256].includes(unwrappingKey.algorithm.length)){ 134 | cipherName = 'id-aes' + unwrappingKey.algorithm.length + '-wrap' 135 | } else { 136 | throw new DataError('Invalid AES-KW and length pair.') 137 | } 138 | let iv = Buffer.from('A6A6A6A6A6A6A6A6', 'hex') 139 | let decipher = crypto.createDecipheriv(cipherName,unwrappingKey.handle,iv) 140 | let deciphertext = decipher.update(Buffer.from(wrappedKey)) 141 | plaintext = Array.from(Buffer.concat([deciphertext,decipher.final()])) 142 | } catch (error) { 143 | throw new OperationError(error.message) 144 | } 145 | 146 | // 3. Return the resulting CryptoKey object 147 | return plaintext 148 | } 149 | 150 | /** 151 | * generateKey 152 | * 153 | * @description 154 | * Generate an AES-KW key pair 155 | * 156 | * @param {AesKwParams} params 157 | * @returns {CryptoKeyPair} 158 | */ 159 | generateKey (params, extractable, usages) { 160 | // 1. Validate usages 161 | usages.forEach(usage => { 162 | if (usage !== 'wrapKey' && usage !== 'unwrapKey') { 163 | throw new SyntaxError('Key usages can only include "wrapKey" or "unwrapKey"') 164 | } 165 | }) 166 | 167 | // 2. Validate length 168 | if (![128,192,256].includes(params.length)) { 169 | throw new OperationError('Member length must be 128, 192, or 256.') 170 | } 171 | 172 | // 3. Generate AES Key 173 | let symmetricKey 174 | try { 175 | symmetricKey = crypto.randomBytes(params.length/8) 176 | // 4. Validate key generation 177 | } catch (error) { 178 | throw new OperationError(error.message) 179 | } 180 | 181 | // 6. Set new AesKeyAlgorithm 182 | let algorithm = new AES_KW(params) 183 | 184 | // 5. Define new CryptoKey names key 185 | let key = new CryptoKey({ 186 | type: 'secret', 187 | algorithm, 188 | extractable, 189 | usages, 190 | handle: symmetricKey 191 | }) 192 | 193 | // 12. Return Key 194 | return key 195 | } 196 | 197 | /** 198 | * importKey 199 | *} 200 | * @description 201 | * 202 | * @param {string} format 203 | * @param {string|JsonWebKey} keyData 204 | * @param {KeyAlgorithm} algorithm 205 | * @param {Boolean} extractable 206 | * @param {Array} keyUsages 207 | * 208 | * @returns {CryptoKey} 209 | */ 210 | importKey (format, keyData, algorithm, extractable, keyUsages) { 211 | let data, jwk 212 | 213 | // 1. Validate keyUsages 214 | keyUsages.forEach(usage => { 215 | if (usage !== 'wrapKey' 216 | && usage !== 'unwrapKey') { 217 | throw new SyntaxError('Key usages can only include "wrapKey" or "unwrapKey"') 218 | } 219 | }) 220 | 221 | // 2.1 "raw" format 222 | if (format === 'raw'){ 223 | // 2.1.1 Let data be the octet string contained in keyData 224 | data = Buffer.from(keyData) 225 | 226 | // 2.1.2 Ensure data length is 128, 192 or 256 227 | if (![16,24,32].includes(data.length)){ 228 | throw new DataError('Length of data bits must be 128, 192 or 256.') 229 | } 230 | } 231 | 232 | // 2.2 "jwk" format 233 | else if (format === 'jwk'){ 234 | // 2.2.1 Ensure data is JsonWebKey dictionary 235 | if (typeof keyData === 'object' && !Array.isArray(keyData)){ 236 | jwk = new JsonWebKey(keyData) 237 | } else { 238 | throw new DataError('Invalid jwk format') 239 | } 240 | 241 | // 2.2.2 Validate "kty" field heuristic 242 | if (jwk.kty !== "oct"){ 243 | throw new DataError('kty property must be "oct"') 244 | } 245 | 246 | // 2.2.3 Ensure jwk meets these requirements: 247 | // https://tools.ietf.org/html/rfc7518#section-6.4 248 | if (!jwk.k){ 249 | throw new DataError('k property must not be empty') 250 | } 251 | 252 | // 2.2.4 Assign data 253 | data = base64url.toBuffer(jwk.k) 254 | 255 | // 2.2.5 Validate data lengths 256 | if (data.length === 16) { 257 | if (jwk.alg && jwk.alg !== 'A128KW'){ 258 | throw new DataError('Algorithm "A128KW" must be 128 bits in length') 259 | } 260 | } else if (data.length === 24) { 261 | if (jwk.alg && jwk.alg !== 'A192KW'){ 262 | throw new DataError('Algorithm "A192KW" must be 192 bits in length') 263 | } 264 | } else if (data.length === 32) { 265 | if (jwk.alg && jwk.alg !== 'A256KW'){ 266 | throw new DataError('Algorithm "A256KW" must be 256 bits in length') 267 | } 268 | } else { 269 | throw new DataError('Algorithm and data length mismatch') 270 | } 271 | 272 | // 2.2.6 Validate "use" field 273 | if (keyUsages && jwk.use && jwk.use !== 'enc'){ 274 | throw new DataError('Key use must be "enc"') 275 | } 276 | 277 | // 2.2.7 Validate "key_ops" field 278 | if (jwk.key_ops){ 279 | jwk.key_ops.forEach(op => { 280 | if (op !== 'encrypt' 281 | && op !== 'decrypt' 282 | && op !== 'wrapKey' 283 | && op !== 'unwrapKey') { 284 | throw new DataError('Key operation can only include "encrypt", "decrypt", "wrapKey" or "unwrapKey"') 285 | } 286 | }) 287 | } 288 | 289 | // 2.2.8 validate "ext" field 290 | if (jwk.ext === false && extractable === true){ 291 | throw new DataError('Cannot be extractable when "ext" is set to false') 292 | } 293 | } 294 | 295 | // 2.3 Otherwise... 296 | else { 297 | throw new KeyFormatNotSupportedError(format) 298 | } 299 | 300 | // 3. Generate new key 301 | let key = new CryptoKey({ 302 | type: 'secret', 303 | extractable, 304 | usages: keyUsages, 305 | handle: data 306 | }) 307 | 308 | // 4-6. Generate algorithm 309 | let aesAlgorithm = new AES_KW( 310 | { name: 'AES-KW', 311 | length: data.length * 8 312 | }) 313 | 314 | // 7. Set algorithm to internal algorithm property of key 315 | key.algorithm = aesAlgorithm 316 | 317 | // 8. Return key 318 | return key 319 | } 320 | 321 | /** 322 | * exportKey 323 | * 324 | * @description 325 | * 326 | * @param {string} format 327 | * @param {CryptoKey} key 328 | * 329 | * @returns {*} 330 | */ 331 | exportKey (format, key) { 332 | let result, data 333 | 334 | // 1. Validate handle slot 335 | if (!key.handle) { 336 | throw new OperationError('Missing key material') 337 | } 338 | 339 | // 2.1 "raw" format 340 | if (format === 'raw'){ 341 | // 2.1.1 Let data be the raw octets of the key 342 | data = key.handle 343 | // 2.1.2 Let result be containing data 344 | result = Buffer.from(data) 345 | } 346 | 347 | // 2.2 "jwk" format 348 | else if (format === 'jwk'){ 349 | // 2.2.1 Validate JsonWebKey 350 | let jwk = new JsonWebKey() 351 | 352 | // 2.2.2 Set kty property 353 | jwk.kty = 'oct' 354 | 355 | // 2.2.3 Set k property 356 | jwk.k = base64url(key.handle) 357 | data = key.handle 358 | 359 | // 2.2.4 Validate length 360 | if (data.length === 16) { 361 | jwk.alg = 'A128KW' 362 | } else if (data.length === 24) { 363 | jwk.alg = 'A192KW' 364 | } else if (data.length === 32) { 365 | jwk.alg = 'A256KW' 366 | } 367 | // 2.2.5 Set keyops property 368 | jwk.key_ops = key.usages 369 | 370 | // 2.2.6 Set ext property 371 | jwk.ext = key.extractable 372 | 373 | // 2.2.7 Set result to the result of converting jwk to an ECMAScript object 374 | result = jwk 375 | } 376 | 377 | // 2.3 Otherwise... 378 | else { 379 | throw new KeyFormatNotSupportedError(format) 380 | } 381 | 382 | // 3. Return result 383 | return result 384 | } 385 | } 386 | 387 | /** 388 | * Export 389 | */ 390 | module.exports = AES_KW -------------------------------------------------------------------------------- /src/algorithms/Algorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Algorithm 3 | */ 4 | class Algorithm { 5 | 6 | /** 7 | * constructor 8 | * 9 | * @description 10 | * The Algorithm object is a dictionary object [WebIDL] which is used 11 | * to specify an algorithm and any additional parameters required to 12 | * fully specify the desired operation. 13 | * 14 | * @param {string|Object} algorithm 15 | */ 16 | constructor (algorithm) { 17 | if (typeof algorithm === 'string') { 18 | this.name = algorithm 19 | } else { 20 | Object.assign(this, algorithm) 21 | if (typeof this.name !== 'string') { 22 | throw new Error('Algorithm name must be a string') 23 | } 24 | } 25 | } 26 | } 27 | 28 | /** 29 | * Export 30 | */ 31 | module.exports = Algorithm 32 | -------------------------------------------------------------------------------- /src/algorithms/ECDSA.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Package dependencies 3 | */ 4 | const base64url = require('base64url') 5 | const crypto = require('crypto') 6 | const {spawnSync} = require('child_process') 7 | const {TextEncoder, TextDecoder} = require('text-encoding') 8 | const keyto = require('@trust/keyto') 9 | 10 | /** 11 | * Local dependencies 12 | */ 13 | const Algorithm = require ('../algorithms/Algorithm') 14 | const CryptoKey = require('../keys/CryptoKey') 15 | const CryptoKeyPair = require('../keys/CryptoKeyPair') 16 | const JsonWebKey = require('../keys/JsonWebKey') 17 | const KeyAlgorithm = require('../dictionaries/KeyAlgorithm') 18 | const EcKeyAlgorithm = require('../dictionaries/EcKeyAlgorithm') 19 | 20 | 21 | /** 22 | * Errors 23 | */ 24 | const { 25 | DataError, 26 | OperationError, 27 | InvalidAccessError, 28 | KeyFormatNotSupportedError, 29 | CurrentlyNotSupportedError 30 | } = require('../errors') 31 | 32 | 33 | /** 34 | * ECDSA 35 | */ 36 | class ECDSA extends Algorithm { 37 | 38 | /** 39 | * dictionaries 40 | */ 41 | static get dictionaries () { 42 | return [ 43 | KeyAlgorithm, 44 | EcKeyAlgorithm 45 | ] 46 | } 47 | 48 | /** 49 | * members 50 | */ 51 | static get members () { 52 | return { 53 | name: String, 54 | namedCurve: String, 55 | hash: 'HashAlgorithmIdentifier' 56 | } 57 | } 58 | 59 | 60 | /** 61 | * sign 62 | * 63 | * @description 64 | * Create a digital signature 65 | * 66 | * @param {CryptoKey} key 67 | * @param {BufferSource} data 68 | * 69 | * @returns {ArrayBuffer} 70 | */ 71 | sign (key, data) { 72 | let result 73 | // 1. Ensure the key is a private type only 74 | if (key.type !== 'private') { 75 | throw new InvalidAccessError('Signing requires a private key') 76 | } 77 | // 2-5. Ommitted due to support by Crypto 78 | // 6. Attempt to sign using Crypto lib 79 | try { 80 | let osslCurveName = EcKeyAlgorithm.mapping.find(alg => alg.namedCurve === key.algorithm.namedCurve) 81 | data = Buffer.from(data) 82 | 83 | let signer = crypto.createSign(osslCurveName.hash) 84 | signer.update(data) 85 | 86 | result = signer.sign(key.handle) 87 | } catch (error) { 88 | throw new OperationError(error.message) 89 | } 90 | // 7. Return resulting buffer 91 | return result.buffer 92 | }//sign 93 | 94 | 95 | /** 96 | * verify 97 | * 98 | * @description 99 | * Verifies a digital signature 100 | * 101 | * @param {CryptoKey} key 102 | * @param {BufferSource} signature 103 | * @param {BufferSource} data 104 | * 105 | * @returns {Boolean} 106 | */ 107 | verify (key, signature, data) { 108 | let result 109 | // 1. Ensure the key is a public type only 110 | if (key.type !== 'public') { 111 | throw new InvalidAccessError('Verifying requires a public key') 112 | } 113 | // 2-5. Ommitted due to support by Crypto 114 | // 6. Attempt to verify using Crypto lib 115 | try { 116 | let osslCurveName = EcKeyAlgorithm.mapping.find(alg => alg.namedCurve === key.algorithm.namedCurve) 117 | data = Buffer.from(data) 118 | signature = Buffer.from(signature) 119 | 120 | let verifier = crypto.createVerify(osslCurveName.hash) 121 | verifier.update(data) 122 | 123 | result = verifier.verify(key.handle, signature) 124 | } catch (error) { 125 | throw new OperationError(error.message) 126 | } 127 | return result 128 | }//verify 129 | 130 | 131 | /** 132 | * generateKey 133 | * 134 | * @description 135 | * Generate an ECDSA key pair 136 | * 137 | * @param {EcKeyGenParams} params 138 | * @returns {CryptoKeyPair} 139 | */ 140 | generateKey (params, extractable, usages) { 141 | // 1. Validate usages 142 | usages.forEach(usage => { 143 | if (usage !== 'sign' && usage !== 'verify') { 144 | throw new SyntaxError('Key usages can only include "sign", or "verify"') 145 | } 146 | }) 147 | 148 | // 2. Generate a keypair 149 | let keypair = {} 150 | let { namedCurve } = params 151 | 152 | if (!namedCurve) { 153 | throw new DataError('namedCurve is a required parameter for ECDSA') 154 | } 155 | 156 | if (!EcKeyAlgorithm.mapping.map(alg => alg.namedCurve).includes(namedCurve)) { 157 | throw new DataError('namedCurve is not valid') 158 | } 159 | 160 | let osslCurveName = EcKeyAlgorithm.mapping.find(alg => alg.namedCurve === namedCurve) 161 | 162 | try { 163 | // TODO may need to remove -noout if ec params is needed 164 | let privateKey = spawnSync('openssl', ['ecparam','-name',osslCurveName.name,'-genkey','-noout']).stdout 165 | let publicKey = spawnSync('openssl', ['ec', '-pubout'], { input: privateKey }).stdout 166 | try { 167 | keypair.privateKey = privateKey.toString('ascii').trim() 168 | keypair.publicKey = publicKey.toString('ascii').trim() 169 | } catch(error){ 170 | throw new OperationError(error.message) 171 | } 172 | } catch (error) { 173 | // 3. If any operation fails then throw error 174 | throw new OperationError(error.message) 175 | } 176 | 177 | // 4. Set algorithm be a new ECDSA 178 | let algorithm = new ECDSA(params) 179 | 180 | // 5-6. Set name to ECDSA 181 | // Defined in class header so it will be passed down via params 182 | 183 | // 7-11. Create publicKey object 184 | let publicKey = new CryptoKey({ 185 | type: 'public', 186 | algorithm, 187 | extractable: true, 188 | usages: ['verify'], 189 | handle: keypair.publicKey 190 | }) 191 | 192 | // 12-16. Create privateKey object 193 | let privateKey = new CryptoKey({ 194 | type: 'private', 195 | algorithm, 196 | extractable, 197 | usages: ['sign'], 198 | handle: keypair.privateKey 199 | }) 200 | 201 | // 17-20. Create and return a new CryptoKeyPair 202 | return new CryptoKeyPair({publicKey,privateKey}) 203 | }//generateKey 204 | 205 | /** 206 | * importKey 207 | * 208 | * @description 209 | * 210 | * @param {string} format 211 | * @param {string|JsonWebKey} keyData 212 | * @param {KeyAlgorithm} algorithm 213 | * @param {Boolean} extractable 214 | * @param {Array} keyUsages 215 | * 216 | * @returns {CryptoKey} 217 | */ 218 | importKey (format, keyData, algorithm, extractable, keyUsages) { 219 | let key, hash, normalizedHash, jwk, privateKeyInfo 220 | // 1-2. Check formatting 221 | // 2.1. "spki" format 222 | if (format === 'spki') { 223 | throw new CurrentlyNotSupportedError(format,'jwk') 224 | } 225 | 226 | // 2.2. "pkcs8" format 227 | else if (format === 'pkcs8') { 228 | throw new CurrentlyNotSupportedError(format,'jwk') 229 | } 230 | 231 | // 2.3. "jwk" format 232 | else if (format === 'jwk') { 233 | // 2.3.1 Ensure data is JsonWebKey dictionary 234 | if (typeof keyData === 'object' && !Array.isArray(keyData)){ 235 | jwk = new JsonWebKey(keyData) 236 | } else { 237 | throw new DataError('Invalid jwk format') 238 | } 239 | 240 | // 2.3.2. Ensure 'd' field and keyUsages match up 241 | if (jwk.d !== undefined && keyUsages.some(usage => usage !== 'sign')) { 242 | throw new SyntaxError('Key usages must include "sign"') 243 | } 244 | 245 | if (jwk.d === undefined && !keyUsages.some(usage => usage === 'verify')) { 246 | throw new SyntaxError('Key usages must include "verify"') 247 | } 248 | 249 | // 2.3.3 Validate 'kty' field 250 | if (jwk.kty !== 'EC'){ 251 | throw new DataError('Key type must be "EC".') 252 | } 253 | 254 | // 2.3.4. Validate 'use' field 255 | if (keyUsages !== undefined && jwk.use !== undefined && jwk.use !== 'sig'){ 256 | throw new DataError('Key use must be "sig".') 257 | } 258 | 259 | // 2.3.5. Validate 'key_ops' field 260 | if (jwk.key_ops !== undefined) { 261 | jwk.key_ops.forEach(op => { 262 | if (op !== 'sign' 263 | && op !== 'verify' ) { 264 | throw new DataError('Key operation can only include "sign", or "verify".') 265 | } 266 | }) 267 | } 268 | 269 | // 2.3.6. Validate 'ext' field 270 | if (jwk.ext !== undefined && jwk.ext === false && extractable === true){ 271 | throw new DataError('Cannot be extractable when "ext" is set to false') 272 | } 273 | 274 | // 2.3.7. Set namedCurve 275 | let namedCurve = jwk.crv 276 | 277 | // 2.3.8. Ommitted due to redundancy 278 | 279 | // 2.3.9.1. If namedCurve is equal to 'secp256k1' then... 280 | if (EcKeyAlgorithm.mapping.map(alg => alg.namedCurve).includes(namedCurve)){ 281 | // 2.3.9.1.1-3 Ommited due to redundancy 282 | // 2.3.9.1.4.1. Validate 'd' property 283 | if (jwk.d) { 284 | // 2.3.9.1.4.1.1. TODO jwk validation here... 285 | // 2.3.9.1.4.1.2-3 Generate new private CryptoKeyObject 286 | key = new CryptoKey({ 287 | type: 'private', 288 | extractable, 289 | usages: ['sign'], 290 | handle: keyto.from(jwk, 'jwk').toString('pem', 'private_pkcs1') 291 | }) 292 | } 293 | // 2.3.9.1.4.2. Otherwise... 294 | else { 295 | // 2.3.9.1.4.2.1. TODO jwk validation here... 296 | // 2.3.9.1.4.2.2-3 Generate new public CryptoKeyObject 297 | key = new CryptoKey({ 298 | type: 'public', 299 | extractable: true, 300 | usages: ['verify'], 301 | handle: keyto.from(jwk, 'jwk').toString('pem', 'public_pkcs8') 302 | }) 303 | } 304 | } 305 | // 2.3.9.2. Otherwise... 306 | else { 307 | // 2.3.9.2.1. TODO Implement further key import steps from other specs 308 | // 2.3.9.2.1. Throw error because there are currently no further specs 309 | throw new DataError ('Not a valid jwk specification') 310 | } 311 | // 2.3.10. Ommitted due to redudancy 312 | // 2.3.11-14 Set new alg object 313 | key.algorithm = new ECDSA(algorithm) 314 | } 315 | // 2.4. "raw" format 316 | else if (format === 'raw') { 317 | throw new CurrentlyNotSupportedError(format,'jwk') 318 | } 319 | // 2.5. Otherwise bad format 320 | else { 321 | throw new KeyFormatNotSupportedError(format) 322 | } 323 | // 3. Return key 324 | return key 325 | }//importKey 326 | 327 | 328 | /** 329 | * exportKey 330 | * 331 | * @description 332 | * 333 | * @param {string} format 334 | * @param {CryptoKey} key 335 | * 336 | * @returns {*} 337 | */ 338 | exportKey (format, key) { 339 | // 1. Setup resulting var 340 | let result 341 | 342 | // 2. Validate handle slot 343 | if (!key.handle) { 344 | throw new OperationError('Missing key material') 345 | } 346 | 347 | // 3.1. "spki" format 348 | if (format === 'spki') { 349 | throw new CurrentlyNotSupportedError(format,"jwk' or 'raw") 350 | } 351 | // 3.2. "pkcs8" format 352 | else if (format === 'pkcs8') { 353 | throw new CurrentlyNotSupportedError(format,"jwk' or 'raw") 354 | } 355 | // 2.3. "jwk" format 356 | else if (format === 'jwk') { 357 | // 2.3.1-3 Create new jwk 358 | let jwk = keyto.from(key.handle, 'pem').toJwk(key.type) 359 | 360 | // 2.3.4. Set "key_ops" field 361 | jwk.key_ops = key.usages 362 | 363 | // 2.3.5. Set "ext" field 364 | jwk.ext = key.extractable 365 | 366 | // 2.3.6. Set result to jwk object 367 | result = jwk 368 | } 369 | // 3.4. "raw" format 370 | else if (format === 'raw') { 371 | // 3.4.1. Validate that the internal use is public 372 | if (key.type !== 'public'){ 373 | throw new InvalidAccessError('Can only access public key data.') 374 | } 375 | // 3.4.2. Omitted due to redundancy 376 | // 3.4.3. Let resulting data be Buffer containing data 377 | result = Buffer.from(key.handle) 378 | } 379 | // 3.5. Otherwise bad format 380 | else { 381 | throw new KeyFormatNotSupportedError(format) 382 | } 383 | 384 | // 4. Result result 385 | return result 386 | } 387 | 388 | }//ECDSA 389 | 390 | /** 391 | * Export 392 | */ 393 | module.exports = ECDSA 394 | -------------------------------------------------------------------------------- /src/algorithms/HMAC.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Package dependencies 3 | */ 4 | const base64url = require('base64url') 5 | const crypto = require('crypto') 6 | 7 | /** 8 | * Local dependencies 9 | */ 10 | const Algorithm = require ('../algorithms/Algorithm') 11 | const CryptoKey = require('../keys/CryptoKey') 12 | const JsonWebKey = require('../keys/JsonWebKey') 13 | const KeyAlgorithm = require('../dictionaries/KeyAlgorithm') 14 | const HmacKeyAlgorithm = require('../dictionaries/HmacKeyAlgorithm') 15 | 16 | /** 17 | * Errors 18 | */ 19 | const { 20 | DataError, 21 | OperationError, 22 | NotSupportedError, 23 | KeyFormatNotSupportedError 24 | } = require('../errors') 25 | 26 | /** 27 | * HMAC 28 | */ 29 | class HMAC extends Algorithm { 30 | 31 | /** 32 | * dictionaries 33 | */ 34 | static get dictionaries () { 35 | return [ 36 | KeyAlgorithm, 37 | HmacKeyAlgorithm 38 | ] 39 | } 40 | 41 | /** 42 | * members 43 | */ 44 | static get members () { 45 | return {} 46 | } 47 | 48 | /** 49 | * sign 50 | * 51 | * @description 52 | * Create a MAC 53 | * 54 | * @param {CryptoKey} key 55 | * @param {BufferSource} data 56 | * 57 | * @return {string} 58 | */ 59 | sign (key, data) { 60 | let alg = key.algorithm.hash.name.replace('-', '').toLowerCase() 61 | let hmac = crypto.createHmac(alg, key.handle) 62 | hmac.update(Buffer.from(data)) 63 | return new Uint8Array(hmac.digest()).buffer 64 | } 65 | 66 | /** 67 | * verify 68 | * 69 | * @description 70 | * Verify a MAC 71 | * 72 | * @param {CryptoKey} key 73 | * @param {BufferSource} signature 74 | * @param {BufferSource} data 75 | * 76 | * @returns {Boolean} 77 | */ 78 | verify (key, signature, data) { 79 | let mac = Buffer.from(this.sign(key, data)) 80 | return mac.equals(Buffer.from(signature)) 81 | } 82 | 83 | /** 84 | * generateKey 85 | * 86 | * @description 87 | * Generate HMAC key 88 | * 89 | * @param {HmacKeyGenParams} params 90 | * @param {Boolean} extractable 91 | * @param {Array} usages 92 | * 93 | * @returns {CryptoKey} 94 | */ 95 | generateKey (params, extractable, usages) { 96 | usages.forEach(usage => { 97 | if (usage !== 'sign' && usage !== 'verify') { 98 | throw new SyntaxError( 99 | 'Key usages can only include "sign" and "verify"' 100 | ) 101 | } 102 | }) 103 | 104 | let length 105 | 106 | if (params.length === undefined) { 107 | length = params.hash.name.match(/[0-9]+/)[0] 108 | } else if (params.length > 0) { 109 | length = params.length 110 | } else { 111 | throw new OperationError('Invalid HMAC length') 112 | } 113 | 114 | let generatedKey 115 | 116 | try { 117 | generatedKey = crypto.randomBytes(parseInt(length)) 118 | } catch (e) { 119 | throw new OperationError('Failed to generate HMAC key') 120 | } 121 | 122 | let key = new CryptoKey({ 123 | type: 'secret', 124 | algorithm: new HMAC({ 125 | name: 'HMAC', 126 | hash: new KeyAlgorithm({ 127 | name: params.hash.name 128 | }) 129 | }), 130 | extractable, 131 | usages, 132 | handle: generatedKey 133 | }) 134 | 135 | return key 136 | } 137 | 138 | /** 139 | * importKey 140 | * 141 | * @description 142 | * 143 | * @param {string} format 144 | * @param {string|JsonWebKey} keyData 145 | * @param {KeyAlgorithm} algorithm 146 | * @param {Boolean} extractable 147 | * @param {Array} keyUsages 148 | * 149 | * @returns {CryptoKey} 150 | */ 151 | importKey (format, keyData, algorithm, extractable, keyUsages) { 152 | keyUsages.forEach(usage => { 153 | if (usage !== 'sign' && usage !== 'verify') { 154 | throw new SyntaxError( 155 | 'Key usages can only include "sign" and "verify"' 156 | ) 157 | } 158 | }) 159 | 160 | let hash = new KeyAlgorithm({ name: 'HMAC' }) 161 | let data 162 | 163 | if (format === 'raw') { 164 | data = new Buffer(keyData) 165 | 166 | if (algorithm.hash) { 167 | hash = algorithm.hash 168 | } else { 169 | throw new TypeError('HmacKeyGenParams: hash: Missing or not an AlgorithmIdentifier') 170 | } 171 | 172 | } else if (format === 'jwk') { 173 | let jwk = new JsonWebKey(keyData) 174 | 175 | if (jwk.kty !== 'oct') { 176 | throw new DataError() 177 | } 178 | 179 | data = base64url.toBuffer(jwk.k) 180 | 181 | if (algorithm.hash !== undefined) { 182 | hash = algorithm.hash 183 | 184 | if (hash.name === 'SHA-1') { 185 | if (jwk.alg && jwk.alg !== 'HS1') { 186 | throw new DataError() 187 | } 188 | } else if (hash.name === 'SHA-256') { 189 | if (jwk.alg && jwk.alg !== 'HS256') { 190 | throw new DataError() 191 | } 192 | } else if (hash.name === 'SHA-384') { 193 | if (jwk.alg && jwk.alg !== 'HS384') { 194 | throw new DataError() 195 | } 196 | } else if (hash.name === 'SHA-512') { 197 | if (jwk.alg && jwk.alg !== 'HS512') { 198 | throw new DataError() 199 | } 200 | // TODO 201 | // "another applicable specification" 202 | //} else if () { 203 | // ... 204 | } else { 205 | throw new DataError() 206 | } 207 | } else { 208 | if (jwk.alg === undefined) { 209 | throw new DataError() 210 | } 211 | 212 | if (jwk.alg === 'HS1') { 213 | hash.name = 'SHA-1' 214 | } else if (jwk.alg === 'HS256') { 215 | hash.name = 'SHA-256' 216 | } else if (jwk.alg === 'HS384') { 217 | hash.name = 'SHA-384' 218 | } else if (jwk.alg === 'HS512') { 219 | hash.name = 'SHA-512' 220 | } else { 221 | // TODO 222 | // "other applicable specifications" 223 | } 224 | } 225 | 226 | if (jwk.use !== undefined && jwk.use !== 'sign') { 227 | throw new DataError() 228 | } 229 | 230 | if (jwk.key_ops !== undefined) { 231 | //if (jwk.key_ops ...?) { 232 | // throw new DataError() 233 | //} 234 | 235 | keyUsages.forEach(usage => { 236 | if (!jwk.key_ops.includes(usage)) { 237 | throw new DataError() 238 | } 239 | }) 240 | } 241 | 242 | if (jwk.ext === false && extractable === true) { 243 | throw new DataError() 244 | } 245 | 246 | } else { 247 | throw new KeyFormatNotSupportedError(format) 248 | } 249 | 250 | let length = data.length * 8 251 | 252 | if (length === 0) { 253 | throw new DataError('HMAC key data must not be empty') 254 | } 255 | 256 | if (algorithm.length !== undefined) { 257 | if (algorithm.length > length) { 258 | throw new DataError() 259 | } else if (algorithm.length <= length - 8) { 260 | throw new DataError() 261 | } else { 262 | length = algorithm.length 263 | } 264 | } 265 | 266 | let key = new CryptoKey({ 267 | type: 'secret', 268 | algorithm: new HMAC({ 269 | name: 'HMAC', 270 | length, 271 | hash 272 | }), 273 | extractable, 274 | usages: keyUsages, 275 | handle: data 276 | }) 277 | 278 | return key 279 | } 280 | 281 | /** 282 | * exportKey 283 | * 284 | * @description 285 | * 286 | * @param {string} format 287 | * @param {CryptoKey} key 288 | * 289 | * @returns {*} 290 | */ 291 | exportKey (format, key) { 292 | if (!(key instanceof CryptoKey) || key.handle === undefined) { 293 | throw new OperationError('argument must be CryptoKey') 294 | } 295 | 296 | let result 297 | 298 | if (format === 'raw') { 299 | let data = key.handle 300 | result = Buffer.from(data) 301 | 302 | } else if (format === 'jwk') { 303 | let jwk = new JsonWebKey({ 304 | kty: 'oct', 305 | k: base64url(key.handle) 306 | }) 307 | 308 | let algorithm = key.algorithm 309 | let hash = algorithm.hash 310 | 311 | if (hash.name === 'SHA-1') { 312 | jwk.alg = 'HS1' 313 | } else if (hash.name === 'SHA-256') { 314 | jwk.alg = 'HS256' 315 | } else if (hash.name === 'SHA-384') { 316 | jwk.alg = 'HS384' 317 | } else if (hash.name === 'SHA-512') { 318 | jwk.alg = 'HS512' 319 | } else { 320 | // TODO 321 | // "other applicable specifications" 322 | } 323 | 324 | jwk.key_ops = key.usages 325 | jwk.ext = key.extractable 326 | 327 | result = jwk 328 | } else { 329 | throw new KeyFormatNotSupportedError(format) 330 | } 331 | 332 | return result 333 | } 334 | } 335 | 336 | /** 337 | * Export 338 | */ 339 | module.exports = HMAC 340 | -------------------------------------------------------------------------------- /src/algorithms/RSA-OAEP.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Package dependencies 3 | */ 4 | const crypto = require('crypto') 5 | const base64url = require('base64url') 6 | const keyto = require('@trust/keyto') 7 | const {spawnSync} = require('child_process') 8 | const {TextEncoder, TextDecoder} = require('text-encoding') 9 | 10 | /** 11 | * Local dependencies 12 | */ 13 | const KeyAlgorithm = require('../dictionaries/KeyAlgorithm') 14 | const AesKeyAlgorithm = require('../dictionaries/AesKeyAlgorithm') 15 | const Algorithm = require ('../algorithms/Algorithm') 16 | const CryptoKey = require('../keys/CryptoKey') 17 | const CryptoKeyPair = require('../keys/CryptoKeyPair') 18 | const JsonWebKey = require('../keys/JsonWebKey') 19 | const RsaKeyAlgorithm = require('../dictionaries/RsaKeyAlgorithm') 20 | const RsaHashedKeyAlgorithm = require('../dictionaries/RsaHashedKeyAlgorithm') 21 | const supportedAlgorithms = require('../algorithms') 22 | 23 | /** 24 | * Errors 25 | */ 26 | const { 27 | DataError, 28 | OperationError, 29 | InvalidAccessError, 30 | KeyFormatNotSupportedError, 31 | CurrentlyNotSupportedError 32 | } = require('../errors') 33 | 34 | /** 35 | * RSA-OAEP 36 | */ 37 | class RSA_OAEP extends Algorithm { 38 | 39 | 40 | /** 41 | * Constructor 42 | */ 43 | constructor (algorithm) { 44 | super(algorithm) 45 | if (typeof algorithm === "object" && algorithm !== null ){ 46 | if (!algorithm.hash || !algorithm.hash.name){ 47 | throw new Error('Algorithm requires a valid hash object.') 48 | } 49 | } else { 50 | throw new Error('Algorithm must be an object with a name and hash.') 51 | } 52 | 53 | } 54 | 55 | /** 56 | * dictionaries 57 | */ 58 | static get dictionaries () { 59 | return [ 60 | KeyAlgorithm, 61 | AesKeyAlgorithm 62 | ] 63 | } 64 | 65 | /** 66 | * members 67 | */ 68 | static get members () { 69 | return { 70 | name: String, 71 | modulusLength: Number, 72 | publicExponent: 'BufferSource' 73 | } 74 | } 75 | 76 | /** 77 | * encrypt 78 | * 79 | * @description 80 | * Encrypts an RSA-OAEP digital signature 81 | * 82 | * @param {RsaOaepParams} algorithm 83 | * @param {CryptoKey} key 84 | * @param {BufferSource} data 85 | * 86 | * @returns {Array} 87 | */ 88 | encrypt (algorithm, key, data) { 89 | let result 90 | // 1. Ensure the key is a public type only 91 | if (key.type !== 'public') { 92 | throw new InvalidAccessError('Encrypt requires a public key') 93 | } 94 | 95 | // 2. Assign label 96 | // TODO Investigate use for label within context 97 | let label 98 | if (algorithm.label !== undefined){ 99 | label = algorithm.label 100 | } else { 101 | label = "" 102 | } 103 | 104 | // TODO Remove this error once additional Node support is available. 105 | if (key.algorithm.hash.name !== 'SHA-1'){ 106 | throw new CurrentlyNotSupportedError(key.algorithm.hash.name,'SHA-1') 107 | } 108 | 109 | // 3-5. Attempt to encrypt using crypto lib 110 | try { 111 | data = Buffer.from(data) 112 | result = crypto.publicEncrypt( 113 | { 114 | key: key.handle, 115 | padding: crypto.constants.RSA_PKCS1_OAEP_PADDING 116 | }, 117 | data) 118 | } catch (error) { 119 | throw new OperationError(error.message) 120 | } 121 | // 7. Return resulting buffer 122 | return result.buffer 123 | } 124 | 125 | /** 126 | * decrypt 127 | * 128 | * @description 129 | * Decrypts an RSA-OAEP digital signature 130 | * 131 | * @param {AesKeyAlgorithm} algorithm 132 | * @param {CryptoKey} key 133 | * @param {BufferSource} data 134 | * 135 | * @returns {Array} 136 | */ 137 | decrypt (algorithm, key, data) { 138 | let result 139 | // 1. Ensure the key is a private type only 140 | if (key.type !== 'private') { 141 | throw new InvalidAccessError('Decrypt requires a private key') 142 | } 143 | 144 | // 2. Assign label 145 | let label 146 | if (algorithm.label !== undefined){ 147 | label = algorithm.label 148 | } else { 149 | label = "" 150 | } 151 | 152 | // TODO Remove this error once additional Node support is available. 153 | if (key.algorithm.hash.name !== 'SHA-1'){ 154 | throw new CurrentlyNotSupportedError(key.algorithm.hash.name,'SHA-1') 155 | } 156 | 157 | // 3-5. Attempt to decrypt using crypto lib 158 | try { 159 | data = Buffer.from(data) 160 | result = crypto.privateDecrypt({ 161 | key: key.handle, 162 | padding: crypto.constants.RSA_PKCS1_OAEP_PADDING 163 | }, 164 | data) 165 | } catch (error) { 166 | throw new OperationError(error.message) 167 | } 168 | // 7. Return resulting buffer 169 | return result.buffer 170 | } 171 | 172 | /** 173 | * generateKey 174 | * 175 | * @description 176 | * Generate an RSA-OAEP key pair 177 | * 178 | * @param {RsaOaepParams} params 179 | * @returns {CryptoKeyPair} 180 | */ 181 | generateKey (params, extractable, usages) { 182 | // 1. Validate usages 183 | usages.forEach(usage => { 184 | if (usage !== 'encrypt' && usage !== 'decrypt' && usage !== 'wrapKey' && usage !== 'unwrapKey') { 185 | throw new SyntaxError('Key usages can only include "encrypt", "decrypt", "wrapKey" or "unwrapKey"') 186 | } 187 | }) 188 | 189 | // 2. Generate RSA key pair 190 | let keypair = {} 191 | try { 192 | let {modulusLength,publicExponent} = params 193 | 194 | // Get the keypairs from openssl spawns 195 | let privateKey = spawnSync('openssl', ['genrsa', modulusLength || 4096]).stdout 196 | let publicKey = spawnSync('openssl', ['rsa', '-pubout'], { input: privateKey }).stdout 197 | 198 | // Convert to ascii strings 199 | keypair.privateKey = privateKey.toString('ascii') 200 | keypair.publicKey = publicKey.toString('ascii') 201 | 202 | } catch (error) { 203 | throw new OperationError(error.message) 204 | } 205 | 206 | // 4-8. Set new RSA-OAEP object with params carried over 207 | let algorithm = new RSA_OAEP(params) 208 | 209 | // 9-13. Create publicKey object 210 | let publicKey = new CryptoKey({ 211 | type: 'public', 212 | algorithm, 213 | extractable: true, 214 | usages: ['encrypt','wrapKey'], 215 | handle: keypair.publicKey 216 | }) 217 | 218 | // 14-18. Create privateKey object 219 | let privateKey = new CryptoKey({ 220 | type: 'private', 221 | algorithm, 222 | extractable: extractable, 223 | usages: ['decrypt','unwrapKey'], 224 | handle: keypair.privateKey 225 | }) 226 | 227 | // 19-22. Return Key CryptoKeyPair 228 | return new CryptoKeyPair({publicKey,privateKey}) 229 | } 230 | 231 | /** 232 | * importKey 233 | * 234 | * @description 235 | * 236 | * @param {string} format 237 | * @param {string|JsonWebKey} keyData 238 | * @param {KeyAlgorithm} algorithm 239 | * @param {Boolean} extractable 240 | * @param {Array} keyUsages 241 | * 242 | * @returns {CryptoKey} 243 | */ 244 | importKey (format, keyData, algorithm, extractable, keyUsages) { 245 | let data, key, jwk, hash, normalizedHash 246 | // 1. Assignment of keyData is done in function param 247 | // 2.1. "spki" format 248 | if (format === 'spki') { 249 | // TODO Add support for spki 250 | throw new CurrentlyNotSupportedError(format,'jwk') 251 | } 252 | // 2.2. "pkcs8" format 253 | else if (format === 'pkcs8') { 254 | // TODO Add support for pkcs8 255 | throw new CurrentlyNotSupportedError(format,'jwk') 256 | } 257 | // 2.3. "jwk" format 258 | else if (format === 'jwk') { 259 | // 2.3.1. Create new JWK using data 260 | jwk = new JsonWebKey(keyData) 261 | 262 | // 2.3.2. Validate present 'd' field and allowed usages 263 | if (jwk.d) 264 | { 265 | keyUsages.forEach(usage => { 266 | if (usage !== 'decrypt' && usage !== 'unwrapKey') { 267 | throw new SyntaxError('Key usages can only include "decrypt" or "unwrapKey"') 268 | } 269 | }) 270 | } 271 | 272 | // 2.3.3. Validate absent 'd' field and allowed usages 273 | if (jwk.d === undefined) { 274 | keyUsages.forEach(usage => { 275 | if (usage !== 'encrypt' && usage !== 'wrapKey') { 276 | throw new SyntaxError('Key usages can only include "encrypt" or "wrapKey"') 277 | } 278 | }) 279 | } 280 | 281 | // 2.3.4. Validate 'kty' field and allowed string match 282 | if (jwk.kty !== 'RSA') { 283 | throw new DataError('Key type must be RSA') 284 | } 285 | 286 | // 2.3.5. Validate present 'use' field and allowed string match 287 | if (jwk.use !== undefined && jwk.use !== 'sig') { 288 | throw new DataError('Key use must be "sig"') 289 | } 290 | 291 | // 2.3.6. Validate present 'key_ops' field 292 | if (jwk.key_ops !== undefined) { 293 | jwk.key_ops.forEach(op => { 294 | if (op !== 'encrypt' 295 | && op !== 'decrypt' 296 | && op !== 'wrapKey' 297 | && op !== 'unwrapKey' ) { 298 | throw new DataError('Key operation can only include "encrypt", "decrypt", "wrapKey" or "unwrapKey".') 299 | } 300 | }) 301 | } 302 | 303 | // 2.3.7. Validate present 'ext' field 304 | if (jwk.ext !== undefined && jwk.ext === false && extractable === true){ 305 | throw new DataError('Cannot be extractable when "ext" is set to false') 306 | } 307 | 308 | // 2.3.8.1. 'alg' field is not present 309 | if (jwk.alg === undefined){ 310 | // Leave undefined 311 | } 312 | // 2.3.8.2. 'alg' field is "RSA-OAEP" 313 | else if (jwk.alg === 'RSA-OAEP'){ 314 | hash = 'SHA-1' 315 | } 316 | // 2.3.8.3. 'alg' field is "RSA-OAEP-256" 317 | else if (jwk.alg === 'RSA-OAEP-256'){ 318 | hash = 'SHA-256' 319 | } 320 | // 2.3.8.4. 'alg' field is "RSA-OAEP-384" 321 | else if (jwk.alg === 'RSA-OAEP-384'){ 322 | hash = 'SHA-384' 323 | } 324 | // 2.3.8.5. 'alg' field is "RSA-OAEP-512" 325 | else if (jwk.alg === 'RSA-OAEP-512'){ 326 | hash = 'SHA-512' 327 | } 328 | // 2.3.8.6. Otherwise... 329 | else { 330 | // TODO Perform alternative key import steps defined by other applicable specifications 331 | throw new CurrentlyNotSupportedError(jwk.alg,'RSA-OAEP') 332 | } 333 | 334 | // 2.3.9. If hash not undefined then... 335 | if (hash !== undefined){ 336 | // 2.3.9.1. Normalize the hash with alg set to 'hash', and op to 'digest' 337 | normalizedHash = supportedAlgorithms.normalize('digest', hash) 338 | 339 | // 2.3.9.2. Validate hash member of normalizedAlgorithm 340 | if (!normalizedHash || normalizedHash.name !== this.hash.name) { 341 | throw new DataError("Unknown hash or mismatched hash name.") 342 | } 343 | } 344 | 345 | // 2.3.10. Validate 'd' field... 346 | if (jwk.d) { 347 | // 2.3.10.1.1. TODO jwk validation here... 348 | // 2.3.10.1.2-5 Generate new private CryptoKeyObject 349 | key = new CryptoKey({ 350 | type: 'private', 351 | extractable, 352 | usages: ['decrypt'], 353 | handle: keyto.from(jwk, 'jwk').toString('pem', 'private_pkcs1') 354 | }) 355 | } 356 | // 2.3.10.2. Otherwise... 357 | else { 358 | // 2.3.10.2.1 TODO jwk validation here... 359 | // 2.3.10.2.2-5 Generate new public CryptoKeyObject 360 | key = new CryptoKey({ 361 | type: 'public', 362 | extractable: true, 363 | usages: ['encrypt'], 364 | handle: keyto.from(jwk, 'jwk').toString('pem', 'public_pkcs8') 365 | }) 366 | } 367 | } 368 | // 2.4. Otherwise... 369 | else { 370 | throw new KeyFormatNotSupportedError(format) 371 | } 372 | 373 | // 3-7. Create RsaHashedKeyAlgorithm 374 | let alg = new RSA_OAEP({ 375 | name: 'RSA-OAEP', 376 | modulusLength: (new Buffer(jwk.n, 'base64').length / 2) * 8, 377 | publicExponent: new Uint8Array([0x01, 0x00, 0x01]), 378 | hash: normalizedHash 379 | }) 380 | 381 | // 8. Set key.algorthm to alg 382 | key.algorithm = alg 383 | 384 | // 9. Return key 385 | return key 386 | } 387 | 388 | /** 389 | * exportKey 390 | * 391 | * @description 392 | * 393 | * @param {string} format 394 | * @param {CryptoKey} key 395 | * 396 | * @returns {*} 397 | */ 398 | exportKey (format, key) { 399 | // 1. Setup resulting var 400 | let result 401 | 402 | // 2. Validate handle slot 403 | if (!key.handle) { 404 | throw new OperationError('Missing key material') 405 | } 406 | 407 | // 3.1. "spki" format 408 | if (format === 'spki') { 409 | throw new CurrentlyNotSupportedError(format,"jwk") 410 | } 411 | // 3.2. "pkcs8" format 412 | else if (format === 'pkcs8') { 413 | throw new CurrentlyNotSupportedError(format,"jwk") 414 | } 415 | // 2.3. "jwk" format 416 | else if (format === 'jwk') { 417 | // 2.3.1. Create new jwk 418 | let jwk = keyto.from(key.handle, 'pem').toJwk(key.type) 419 | 420 | // 2.3.2. Setting 'kty' value 421 | jwk.kty = "RSA" 422 | 423 | // 2.3.3. Determine alg from hash 424 | let hash = key.algorithm.hash.name 425 | 426 | // 2.3.3.1. Hash is "SHA-1" 427 | if (hash === 'SHA-1') { 428 | jwk.alg = 'RSA-OAEP' 429 | } 430 | // 2.3.3.2. Hash is "SHA-256" 431 | else if (hash === 'SHA-256') { 432 | jwk.alg = 'RSA-OAEP-256' 433 | } 434 | // 2.3.3.3. Hash is "SHA-384" 435 | else if (hash === 'SHA-384') { 436 | jwk.alg = 'RSA-OAEP-384' 437 | } 438 | // 2.3.3.4. Hash is "SHA-512" 439 | else if (hash === 'SHA-512') { 440 | jwk.alg = 'RSA-OAEP-512' 441 | } 442 | // 2.3.3.5. Hash is other value 443 | else { 444 | // TODO other applicable specifications 445 | throw new CurrentlyNotSupportedError(format,"SHA-1") 446 | } 447 | // 2.3.4-5. Assign corresponding field from JWA specification 448 | Object.assign(jwk, keyto.from(key.handle, 'pem').toJwk(key.type)) 449 | 450 | // 2.3.6. Set "key_ops" field 451 | jwk.key_ops = key.usages 452 | 453 | // 2.3.7. Set "ext" field 454 | jwk.ext = key.extractable 455 | 456 | // 2.3.8. Set result to jwk object 457 | result = jwk 458 | } 459 | // 3.4. Otherwise bad format 460 | else { 461 | throw new KeyFormatNotSupportedError(format) 462 | } 463 | 464 | // 4. Result result 465 | return result 466 | } 467 | } 468 | 469 | /** 470 | * Export 471 | */ 472 | module.exports = RSA_OAEP -------------------------------------------------------------------------------- /src/algorithms/RSA-PSS.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Package dependencies 3 | */ 4 | 5 | const crypto = require('crypto') 6 | const base64url = require('base64url').default 7 | const keyto = require('@trust/keyto') 8 | const {spawnSync} = require('child_process') 9 | const {TextEncoder, TextDecoder} = require('text-encoding') 10 | 11 | /** 12 | * Local dependencies 13 | */ 14 | const Algorithm = require ('../algorithms/Algorithm') 15 | const CryptoKey = require('../keys/CryptoKey') 16 | const CryptoKeyPair = require('../keys/CryptoKeyPair') 17 | const JsonWebKey = require('../keys/JsonWebKey') 18 | const KeyAlgorithm = require('../dictionaries/KeyAlgorithm') 19 | const RsaKeyAlgorithm = require('../dictionaries/RsaKeyAlgorithm') 20 | const RsaHashedKeyAlgorithm = require('../dictionaries/RsaHashedKeyAlgorithm') 21 | const supportedAlgorithms = require('../algorithms') 22 | 23 | /** 24 | * Errors 25 | */ 26 | const { 27 | DataError, 28 | OperationError, 29 | InvalidAccessError, 30 | KeyFormatNotSupportedError, 31 | CurrentlyNotSupportedError 32 | } = require('../errors') 33 | 34 | /** 35 | * RSA_PSS 36 | */ 37 | class RSA_PSS extends Algorithm { 38 | 39 | /** 40 | * dictionaries 41 | */ 42 | static get dictionaries () { 43 | return [ 44 | KeyAlgorithm, 45 | RsaKeyAlgorithm, 46 | RsaHashedKeyAlgorithm 47 | ] 48 | } 49 | 50 | /** 51 | * members 52 | */ 53 | static get members () { 54 | return { 55 | name: String, 56 | modulusLength: Number, 57 | publicExponent: 'BufferSource', 58 | hash: 'HashAlgorithmIdentifier', 59 | } 60 | } 61 | 62 | /** 63 | * sign 64 | * 65 | * @description 66 | * Create an RSA-PSS digital signature 67 | * 68 | * @param {CryptoKey} key 69 | * @param {BufferSource} data 70 | * 71 | * @returns {ArrayBuffer} 72 | */ 73 | sign (key, data) { 74 | // 1. Ensure key type is 'private' only 75 | if (key.type !== 'private') { 76 | throw new InvalidAccessError('Signing requires a private key') 77 | } 78 | 79 | // Ensure saltLength exists 80 | if (this.saltLength === undefined){ 81 | throw new OperationError('saltLength must be a valid integer') 82 | } 83 | 84 | 85 | // Parametrize hash 86 | let hashName 87 | if (this.hash.name === 'SHA-1'){ 88 | hashName = 'sha1' 89 | } else if (this.hash.name === 'SHA-256'){ 90 | hashName = 'sha256' 91 | } else if (this.hash.name === 'SHA-384'){ 92 | hashName = 'sha384' 93 | } else if (this.hash.name === 'SHA-512'){ 94 | hashName = 'sha512' 95 | } else { 96 | throw new OperationError('Algorithm hash is an unknown format.') 97 | } 98 | 99 | // 2-5. Perform key signing and return result 100 | try { 101 | let pem = key.handle 102 | data = new TextDecoder().decode(data) 103 | let signer = crypto.createSign(hashName) 104 | signer.update(data) 105 | return signer.sign({ 106 | key: pem, 107 | saltLength: this.saltLength, 108 | padding: crypto.constants.RSA_PKCS1_PSS_PADDING 109 | }).buffer 110 | } catch (error) { 111 | throw new OperationError(error.message) 112 | } 113 | } 114 | 115 | /** 116 | * verify 117 | * 118 | * @description 119 | * 120 | * @param {CryptoKey} key 121 | * @param {BufferSource} signature 122 | * @param {BufferSource} data 123 | * 124 | * @returns {Boolean} 125 | */ 126 | verify (key, signature, data) { 127 | // 1. Ensure key type is 'public' only 128 | if (key.type !== 'public') { 129 | throw new InvalidAccessError('Verifying requires a public key') 130 | } 131 | 132 | // Ensure saltLength exists 133 | if (this.saltLength === undefined){ 134 | throw new OperationError('saltLength must be a valid integer') 135 | } 136 | 137 | // Parametrize hash 138 | let hashName 139 | if (this.hash.name === 'SHA-1'){ 140 | hashName = 'sha1' 141 | } else if (this.hash.name === 'SHA-256'){ 142 | hashName = 'sha256' 143 | } else if (this.hash.name === 'SHA-384'){ 144 | hashName = 'sha384' 145 | } else if (this.hash.name === 'SHA-512'){ 146 | hashName = 'sha512' 147 | } else { 148 | throw new OperationError('Algorithm hash is an unknown format.') 149 | } 150 | 151 | // 2-4. Perform verification and return result 152 | try { 153 | let pem = key.handle 154 | 155 | data = Buffer.from(data) 156 | signature = Buffer.from(signature) 157 | 158 | let verifier = crypto.createVerify(hashName) 159 | verifier.update(data) 160 | 161 | return verifier.verify({ 162 | key: pem, 163 | saltLength: this.saltLength, 164 | padding: crypto.constants.RSA_PKCS1_PSS_PADDING 165 | }, signature) 166 | } catch (error) { 167 | throw new OperationError(error.message) 168 | } 169 | } 170 | 171 | /** 172 | * generateKey 173 | * 174 | * @description 175 | * Generate an RSA key pair 176 | * 177 | * @param {RsaHashedKeyGenParams} params 178 | * @returns {CryptoKeyPair} 179 | */ 180 | generateKey (params, extractable, usages) { 181 | 182 | // 1. Verify usages 183 | usages.forEach(usage => { 184 | if (usage !== 'sign' && usage !== 'verify') { 185 | throw new SyntaxError('Key usages can only include "sign" and "verify"') 186 | } 187 | }) 188 | 189 | let keypair = {} 190 | 191 | // 2. Generate RSA keypair 192 | try { 193 | let {modulusLength,publicExponent} = params 194 | // TODO 195 | // - fallback on node-rsa if OpenSSL is not available on the system 196 | let privateKey = spawnSync('openssl', ['genrsa', modulusLength || 4096]).stdout 197 | let publicKey = spawnSync('openssl', ['rsa', '-pubout'], { input: privateKey }).stdout 198 | try { 199 | keypair.privateKey = privateKey.toString('ascii') 200 | keypair.publicKey = publicKey.toString('ascii') 201 | } catch (error){ 202 | throw new OperationError(error.message) 203 | } 204 | // 3. Throw operation error if anything fails 205 | } catch (error) { 206 | throw new OperationError(error.message) 207 | } 208 | 209 | // 4-9. Create and assign algorithm object 210 | let algorithm = new RSA_PSS(params) 211 | 212 | // 10-13. Instantiate publicKey 213 | let publicKey = new CryptoKey({ 214 | type: 'public', 215 | algorithm, 216 | extractable: true, 217 | usages: ['verify'], 218 | handle: keypair.publicKey 219 | }) 220 | 221 | // 14-18. Instantiate privateKey 222 | let privateKey = new CryptoKey({ 223 | type: 'private', 224 | algorithm, 225 | extractable: extractable, 226 | usages: ['sign'], 227 | handle: keypair.privateKey 228 | }) 229 | 230 | // 19-22. Create and return a new keypair 231 | return new CryptoKeyPair({publicKey,privateKey}) 232 | } 233 | 234 | /** 235 | * importKey 236 | * 237 | * @description 238 | * 239 | * @param {string} format 240 | * @param {string|JsonWebKey} keyData 241 | * @param {KeyAlgorithm} algorithm 242 | * @param {Boolean} extractable 243 | * @param {Array} keyUsages 244 | * 245 | * @returns {CryptoKey} 246 | */ 247 | importKey (format, keyData, algorithm, extractable, keyUsages) { 248 | let key, hash, normalizedHash, jwk 249 | // 1. Performed in function parameters 250 | // 2.1. "spki" format 251 | if (format === 'spki') { 252 | throw new CurrentlyNotSupportedError(format,'jwk') 253 | } 254 | // 2.2. "pkcs8" format 255 | else if (format === 'pkcs8') { 256 | throw new CurrentlyNotSupportedError(format,'jwk') 257 | } 258 | // 2.3. "jwk" format 259 | else if (format === 'jwk') { 260 | // 2.3.1. Cast keyData to JWK object 261 | jwk = new JsonWebKey(keyData) 262 | 263 | // 2.3.2. Verify 'd' field 264 | if (jwk.d && keyUsages.some(usage => usage !== 'sign')) { 265 | throw new SyntaxError('Key usages must include "sign"') 266 | } 267 | if (jwk.d === undefined && !keyUsages.some(usage => usage === 'verify')) { 268 | throw new SyntaxError('Key usages must include "verify"') 269 | } 270 | 271 | // 2.3.3. Verify 'kty' field 272 | if (jwk.kty !== 'RSA') { 273 | throw new DataError('Key type must be RSA') 274 | } 275 | 276 | // 2.3.4. Verify 'use' field 277 | if (jwk.use !== undefined && jwk.use !== 'sig') { 278 | throw new DataError('Key use must be "sig"') 279 | } 280 | 281 | // 2.3.5. Validate present 'use' field and allowed string match 282 | if (jwk.use !== undefined && jwk.use !== 'sig') { 283 | throw new DataError('Key use must be "sig"') 284 | } 285 | 286 | // 2.3.6. Validate present 'key_ops' field 287 | if (jwk.key_ops !== undefined) { 288 | jwk.key_ops.forEach(op => { 289 | if (op !== 'sign' 290 | && op !== 'verify') { 291 | throw new DataError('Key operation can only include "sign", and "verify".') 292 | } 293 | }) 294 | } 295 | 296 | // 2.3.7-8. Determine hash name 297 | if (jwk.alg === undefined) { 298 | // keep undefined 299 | } else if (jwk.alg === 'PS1') { 300 | hash = 'SHA-1' 301 | } else if (jwk.alg === 'PS256') { 302 | hash = 'SHA-256' 303 | } else if (jwk.alg === 'PS384') { 304 | hash = 'SHA-384' 305 | } else if (jwk.alg === 'PS512') { 306 | hash = 'SHA-512' 307 | } else { 308 | throw new DataError( 309 | 'Key alg must be "PS1", "PS256", "PS384", or "PS512"' 310 | ) 311 | } 312 | 313 | // 2.3.9. Ommited due to redundancy 314 | if (hash !== undefined) { 315 | normalizedHash = supportedAlgorithms.normalize('digest', hash) 316 | } 317 | 318 | // 2.3.10. Verify 'd' field 319 | if (jwk.d) { 320 | key = new CryptoKey({ 321 | type: 'private', 322 | extractable: extractable, 323 | usages: ['sign'], 324 | handle: keyto.from(jwk, 'jwk').toString('pem', 'private_pkcs1') 325 | }) 326 | } else { 327 | key = new CryptoKey({ 328 | type: 'public', 329 | extractable: true, 330 | usages: ['verify'], 331 | handle: keyto.from(jwk, 'jwk').toString('pem', 'public_pkcs8') 332 | }) 333 | } 334 | } else { 335 | throw new KeyFormatNotSupportedError(format) 336 | } 337 | // 3-7. Setup RSA PSS object 338 | let alg = new RSA_PSS({ 339 | name: 'RSA-PSS', 340 | modulusLength: base64url.toBuffer(jwk.n).length * 8, 341 | publicExponent: new Uint8Array(base64url.toBuffer(jwk.e)), 342 | hash: normalizedHash 343 | }) 344 | 345 | // 8. Set algorithm of key to alg 346 | key.algorithm = alg 347 | 348 | // 9. Return key 349 | return key 350 | } 351 | 352 | 353 | /** 354 | * exportKey 355 | * 356 | * @description 357 | * 358 | * @param {string} format 359 | * @param {CryptoKey} key 360 | * 361 | * @returns {*} 362 | */ 363 | exportKey (format, key) { 364 | let result 365 | 366 | if (!key.handle) { 367 | throw new OperationError('Missing key material') 368 | } 369 | 370 | if (format === 'spki') { 371 | // TODO 372 | } else if (format === 'pkcs8') { 373 | // TODO 374 | } else if (format === 'jwk') { 375 | let jwk = new JsonWebKey({ kty: 'RSA' }) 376 | let hash = key.algorithm.hash.name 377 | 378 | if (hash === 'SHA-1') { 379 | jwk.alg = 'PS1' 380 | } else if (hash === 'SHA-256') { 381 | jwk.alg = 'PS256' 382 | } else if (hash === 'SHA-384') { 383 | jwk.alg = 'PS384' 384 | } else if (hash === 'SHA-512') { 385 | jwk.alg = 'PS512' 386 | } else { 387 | // TODO other applicable specifications 388 | } 389 | 390 | Object.assign(jwk, keyto.from(key.handle, 'pem').toJwk(key.type)) 391 | 392 | jwk.key_ops = key.usages 393 | jwk.ext = key.extractable 394 | 395 | // conversion to ECMAScript Object is implicit 396 | result = jwk 397 | } else { 398 | throw new KeyFormatNotSupportedError(format) 399 | } 400 | return result 401 | } 402 | } 403 | 404 | /** 405 | * Export 406 | */ 407 | module.exports = RSA_PSS -------------------------------------------------------------------------------- /src/algorithms/RSASSA-PKCS1-v1_5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Package dependencies 3 | */ 4 | const RSA = require('node-rsa') 5 | const crypto = require('crypto') 6 | const {spawnSync} = require('child_process') 7 | const keyto = require('@trust/keyto') 8 | const {TextEncoder, TextDecoder} = require('text-encoding') 9 | const base64url = require('base64url').default 10 | 11 | /** 12 | * Local dependencies 13 | */ 14 | const Algorithm = require ('../algorithms/Algorithm') 15 | const CryptoKey = require('../keys/CryptoKey') 16 | const CryptoKeyPair = require('../keys/CryptoKeyPair') 17 | const JsonWebKey = require('../keys/JsonWebKey') 18 | const KeyAlgorithm = require('../dictionaries/KeyAlgorithm') 19 | const RsaKeyAlgorithm = require('../dictionaries/RsaKeyAlgorithm') 20 | const RsaHashedKeyAlgorithm = require('../dictionaries/RsaHashedKeyAlgorithm') 21 | const supportedAlgorithms = require('../algorithms') 22 | 23 | /** 24 | * Errors 25 | */ 26 | const { 27 | DataError, 28 | OperationError, 29 | InvalidAccessError, 30 | KeyFormatNotSupportedError, 31 | CurrentlyNotSupportedError 32 | } = require('../errors') 33 | 34 | /** 35 | * RSASSA_PKCS1_v1_5 36 | */ 37 | class RSASSA_PKCS1_v1_5 extends Algorithm { 38 | 39 | /** 40 | * dictionaries 41 | */ 42 | static get dictionaries () { 43 | return [ 44 | KeyAlgorithm, 45 | RsaKeyAlgorithm, 46 | RsaHashedKeyAlgorithm 47 | ] 48 | } 49 | 50 | /** 51 | * members 52 | */ 53 | static get members () { 54 | return { 55 | name: String, 56 | modulusLength: Number, 57 | publicExponent: 'BufferSource', 58 | hash: 'HashAlgorithmIdentifier' 59 | } 60 | } 61 | 62 | /** 63 | * sign 64 | * 65 | * @description 66 | * Create an RSA digital signature 67 | * 68 | * @param {CryptoKey} key 69 | * @param {BufferSource} data 70 | * 71 | * @returns {ArrayBuffer} 72 | */ 73 | sign (key, data) { 74 | // 1. Ensure key type is 'private' only 75 | if (key.type !== 'private') { 76 | throw new InvalidAccessError('Signing requires a private key') 77 | } 78 | 79 | // Parametrize hash 80 | let hashName 81 | if (this.hash.name === 'SHA-1'){ 82 | hashName = 'RSA-SHA1' 83 | } else if (this.hash.name === 'SHA-256'){ 84 | hashName = 'RSA-SHA256' 85 | } else if (this.hash.name === 'SHA-384'){ 86 | hashName = 'RSA-SHA384' 87 | } else if (this.hash.name === 'SHA-512'){ 88 | hashName = 'RSA-SHA512' 89 | } else { 90 | throw new OperationError('Algorithm hash is an unknown format.') 91 | } 92 | 93 | // 2-5. Perform key signing and return result 94 | try { 95 | let pem = key.handle 96 | data = new TextDecoder().decode(data) 97 | let signer = crypto.createSign(hashName) 98 | signer.update(data) 99 | return signer.sign(pem).buffer 100 | } catch (error) { 101 | throw new OperationError(error.message) 102 | } 103 | } 104 | 105 | /** 106 | * verify 107 | * 108 | * @description 109 | * 110 | * @param {CryptoKey} key 111 | * @param {BufferSource} signature 112 | * @param {BufferSource} data 113 | * 114 | * @returns {Boolean} 115 | */ 116 | verify (key, signature, data) { 117 | // 1. Ensure key type is 'public' only 118 | if (key.type !== 'public') { 119 | throw new InvalidAccessError('Verifying requires a public key') 120 | } 121 | 122 | // Parametrize hash 123 | let hashName 124 | if (this.hash.name === 'SHA-1'){ 125 | hashName = 'RSA-SHA1' 126 | } else if (this.hash.name === 'SHA-256'){ 127 | hashName = 'RSA-SHA256' 128 | } else if (this.hash.name === 'SHA-384'){ 129 | hashName = 'RSA-SHA384' 130 | } else if (this.hash.name === 'SHA-512'){ 131 | hashName = 'RSA-SHA512' 132 | } else { 133 | throw new OperationError('Algorithm hash is an unknown format.') 134 | } 135 | 136 | // 2-4. Perform verification and return result 137 | try { 138 | let pem = key.handle 139 | 140 | data = Buffer.from(data) 141 | signature = Buffer.from(signature) 142 | 143 | let verifier = crypto.createVerify(hashName) 144 | verifier.update(data) 145 | 146 | return verifier.verify(pem, signature) 147 | } catch (error) { 148 | throw new OperationError(error.message) 149 | } 150 | } 151 | 152 | /** 153 | * generateKey 154 | * 155 | * @description 156 | * Generate an RSA key pair 157 | * 158 | * @param {RsaHashedKeyGenParams} params 159 | * @returns {CryptoKeyPair} 160 | */ 161 | generateKey (params, extractable, usages) { 162 | 163 | // 1. Verify usages 164 | usages.forEach(usage => { 165 | if (usage !== 'sign' && usage !== 'verify') { 166 | throw new SyntaxError('Key usages can only include "sign" and "verify"') 167 | } 168 | }) 169 | 170 | let keypair = {} 171 | 172 | // 2. Generate RSA keypair 173 | try { 174 | let {modulusLength,publicExponent} = params 175 | // TODO 176 | // - fallback on node-rsa if OpenSSL is not available on the system 177 | let privateKey = spawnSync('openssl', ['genrsa', modulusLength || 4096]).stdout 178 | let publicKey = spawnSync('openssl', ['rsa', '-pubout'], { input: privateKey }).stdout 179 | try { 180 | keypair.privateKey = privateKey.toString('ascii') 181 | keypair.publicKey = publicKey.toString('ascii') 182 | } catch (error){ 183 | throw new OperationError(error.message) 184 | } 185 | // 3. Throw operation error if anything fails 186 | } catch (error) { 187 | throw new OperationError(error.message) 188 | } 189 | 190 | // 4-9. Create and assign algorithm object 191 | let algorithm = new RSASSA_PKCS1_v1_5(params) 192 | 193 | // 10-13. Instantiate publicKey 194 | let publicKey = new CryptoKey({ 195 | type: 'public', 196 | algorithm, 197 | extractable: true, 198 | usages: ['verify'], 199 | handle: keypair.publicKey 200 | }) 201 | 202 | // 14-18. Instantiate privateKey 203 | let privateKey = new CryptoKey({ 204 | type: 'private', 205 | algorithm, 206 | extractable: extractable, 207 | usages: ['sign'], 208 | handle: keypair.privateKey 209 | }) 210 | 211 | // 19-22. Create and return a new keypair 212 | return new CryptoKeyPair({publicKey,privateKey}) 213 | } 214 | 215 | /** 216 | * importKey 217 | * 218 | * @description 219 | * 220 | * @param {string} format 221 | * @param {string|JsonWebKey} keyData 222 | * @param {KeyAlgorithm} algorithm 223 | * @param {Boolean} extractable 224 | * @param {Array} keyUsages 225 | * 226 | * @returns {CryptoKey} 227 | */ 228 | importKey (format, keyData, algorithm, extractable, keyUsages) { 229 | let key, hash, normalizedHash, jwk 230 | // 1. Performed in function parameters 231 | // 2.1. "spki" format 232 | if (format === 'spki') { 233 | throw new CurrentlyNotSupportedError(format,'jwk') 234 | } 235 | // 2.2. "pkcs8" format 236 | else if (format === 'pkcs8') { 237 | throw new CurrentlyNotSupportedError(format,'jwk') 238 | } 239 | // 2.3. "jwk" format 240 | else if (format === 'jwk') { 241 | // 2.3.1. Cast keyData to JWK object 242 | jwk = new JsonWebKey(keyData) 243 | 244 | // 2.3.2. Verify 'd' field 245 | if (jwk.d && keyUsages.some(usage => usage !== 'sign')) { 246 | throw new SyntaxError('Key usages must include "sign"') 247 | } 248 | if (jwk.d === undefined && !keyUsages.some(usage => usage === 'verify')) { 249 | throw new SyntaxError('Key usages must include "verify"') 250 | } 251 | 252 | // 2.3.3. Verify 'kty' field 253 | if (jwk.kty !== 'RSA') { 254 | throw new DataError('Key type must be RSA') 255 | } 256 | 257 | // 2.3.4. Verify 'use' field 258 | if (jwk.use !== undefined && jwk.use !== 'sig') { 259 | throw new DataError('Key use must be "sig"') 260 | } 261 | 262 | // 2.3.5. Validate present 'use' field and allowed string match 263 | if (jwk.use !== undefined && jwk.use !== 'sig') { 264 | throw new DataError('Key use must be "sig"') 265 | } 266 | 267 | // 2.3.6. Validate present 'key_ops' field 268 | if (jwk.key_ops !== undefined) { 269 | jwk.key_ops.forEach(op => { 270 | if (op !== 'sign' 271 | && op !== 'verify') { 272 | throw new DataError('Key operation can only include "sign", and "verify".') 273 | } 274 | }) 275 | } 276 | 277 | // 2.3.7-8. Determine hash name 278 | if (jwk.alg === undefined) { 279 | // keep undefined 280 | } else if (jwk.alg === 'RS1') { 281 | hash = 'SHA-1' 282 | } else if (jwk.alg === 'RS256') { 283 | hash = 'SHA-256' 284 | } else if (jwk.alg === 'RS384') { 285 | hash = 'SHA-384' 286 | } else if (jwk.alg === 'RS512') { 287 | hash = 'SHA-512' 288 | } else { 289 | throw new DataError( 290 | 'Key alg must be "RS1", "RS256", "RS384", or "RS512"' 291 | ) 292 | } 293 | 294 | // 2.3.9. Ommited due to redundancy, uncomment if needed 295 | if (hash !== undefined) { 296 | normalizedHash = supportedAlgorithms.normalize('digest', hash) 297 | 298 | //if (normalizedHash !== normalizedAlgorithm.hash) { 299 | // throw new DataError() 300 | //} 301 | } 302 | 303 | 304 | // 2.3.10. Verify 'd' field 305 | if (jwk.d) { 306 | key = new CryptoKey({ 307 | type: 'private', 308 | extractable: extractable, 309 | usages: ['sign'], 310 | handle: keyto.from(jwk, 'jwk').toString('pem', 'private_pkcs1') 311 | }) 312 | } else { 313 | key = new CryptoKey({ 314 | type: 'public', 315 | extractable: true, 316 | usages: ['verify'], 317 | handle: keyto.from(jwk, 'jwk').toString('pem', 'public_pkcs8') 318 | }) 319 | } 320 | } else { 321 | throw new KeyFormatNotSupportedError(format) 322 | } 323 | // 3-7. Setup RSSASSA object 324 | let alg = new RSASSA_PKCS1_v1_5({ 325 | name: 'RSASSA-PKCS1-v1_5', 326 | modulusLength: base64url.toBuffer(jwk.n).length * 8, 327 | publicExponent: new Uint8Array(base64url.toBuffer(jwk.e)), 328 | hash: normalizedHash 329 | }) 330 | 331 | // 8. Set algorithm of key to alg 332 | key.algorithm = alg 333 | 334 | // 9. Return key 335 | return key 336 | } 337 | 338 | 339 | /** 340 | * exportKey 341 | * 342 | * @description 343 | * 344 | * @param {string} format 345 | * @param {CryptoKey} key 346 | * 347 | * @returns {*} 348 | */ 349 | exportKey (format, key) { 350 | let result 351 | 352 | // TODO 353 | // - should we type check key here? 354 | if (!key.handle) { 355 | throw new OperationError('Missing key material') 356 | } 357 | 358 | if (format === 'spki') { 359 | // TODO 360 | } else if (format === 'pkcs8') { 361 | // TODO 362 | } else if (format === 'jwk') { 363 | let jwk = new JsonWebKey({ kty: 'RSA' }) 364 | let hash = key.algorithm.hash.name 365 | 366 | if (hash === 'SHA-1') { 367 | jwk.alg = 'RS1' 368 | } else if (hash === 'SHA-256') { 369 | jwk.alg = 'RS256' 370 | } else if (hash === 'SHA-384') { 371 | jwk.alg = 'RS384' 372 | } else if (hash === 'SHA-512') { 373 | jwk.alg = 'RS512' 374 | } else { 375 | // TODO other applicable specifications 376 | } 377 | 378 | Object.assign(jwk, keyto.from(key.handle, 'pem').toJwk(key.type)) 379 | 380 | jwk.key_ops = key.usages 381 | jwk.ext = key.extractable 382 | 383 | // conversion to ECMAScript Object is implicit 384 | result = jwk 385 | } else { 386 | throw new KeyFormatNotSupportedError(format) 387 | } 388 | 389 | return result 390 | } 391 | } 392 | 393 | /** 394 | * Export 395 | */ 396 | module.exports = RSASSA_PKCS1_v1_5 397 | -------------------------------------------------------------------------------- /src/algorithms/RegisteredAlgorithms.js: -------------------------------------------------------------------------------- 1 | /** 2 | * RegisteredAlgorithms 3 | */ 4 | class RegisteredAlgorithms { 5 | 6 | /** 7 | * Constructor 8 | * 9 | * @param {Object} mapping 10 | */ 11 | constructor (mapping) { 12 | Object.assign(this, mapping) 13 | } 14 | 15 | /** 16 | * getCaseInsensitive 17 | * 18 | * @param {string} algName 19 | * @returns {string} 20 | */ 21 | getCaseInsensitive (algName) { 22 | for (let key in this) { 23 | if (key.match(new RegExp(`^${algName}$`, 'i'))) { 24 | return key 25 | } 26 | } 27 | } 28 | } 29 | 30 | /** 31 | * Export 32 | */ 33 | module.exports = RegisteredAlgorithms 34 | -------------------------------------------------------------------------------- /src/algorithms/SHA.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | const Algorithm = require ('../algorithms/Algorithm') 6 | const crypto = require('crypto') 7 | const KeyAlgorithm = require('../dictionaries/KeyAlgorithm') 8 | const ShaKeyAlgorithm = require('../dictionaries/ShaKeyAlgorithm') 9 | const {OperationError} = require('../errors') 10 | 11 | /** 12 | * SHA 13 | */ 14 | class SHA extends Algorithm { 15 | /** 16 | * dictionaries 17 | */ 18 | static get dictionaries () { 19 | return [ 20 | KeyAlgorithm, 21 | ShaKeyAlgorithm 22 | ] 23 | } 24 | 25 | /** 26 | * members 27 | */ 28 | static get members () { 29 | return {} 30 | } 31 | 32 | /** 33 | * digest 34 | * 35 | * @description 36 | * 37 | * @param {AlgorithmIdentifier} algorithm 38 | * @param {BufferSource} data 39 | * 40 | * @returns {Uint8Array} 41 | */ 42 | digest (algorithm, data) { 43 | let result 44 | let {name} = algorithm 45 | 46 | data = Buffer.from(data) 47 | 48 | if (name === 'SHA-1') { 49 | let hash = crypto.createHash('sha1') 50 | hash.update(data) 51 | result = hash.digest() 52 | 53 | } else if (name === 'SHA-256') { 54 | let hash = crypto.createHash('sha256') 55 | hash.update(data) 56 | result = hash.digest() 57 | 58 | } else if (name === 'SHA-384') { 59 | let hash = crypto.createHash('sha384') 60 | hash.update(data) 61 | result = hash.digest() 62 | 63 | } else if (name === 'SHA-512') { 64 | let hash = crypto.createHash('sha512') 65 | hash.update(data) 66 | result = hash.digest() 67 | 68 | } else { 69 | throw new OperationError(`${name} is not a supported algorithm`) 70 | } 71 | 72 | return Uint8Array.from(result).buffer 73 | } 74 | } 75 | 76 | /** 77 | * Export 78 | */ 79 | module.exports = SHA 80 | -------------------------------------------------------------------------------- /src/algorithms/SupportedAlgorithms.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('../dictionaries/Algorithm') 5 | const KeyAlgorithm = require('../dictionaries/KeyAlgorithm') 6 | const RegisteredAlgorithms = require('./RegisteredAlgorithms') 7 | const {NotSupportedError} = require('../errors') 8 | 9 | /** 10 | * Supported Operations 11 | */ 12 | const operations = [ 13 | 'encrypt', 14 | 'decrypt', 15 | 'sign', 16 | 'verify', 17 | 'deriveBits', 18 | 'digest', // THIS WASN'T IN THE LIST. PROBABLY GETTING SOMETHING WRONG HERE 19 | 'wrapKey', 20 | 'unwrapKey', 21 | 'generateKey', 22 | 'importKey', 23 | 'exportKey', 24 | 'getLength' 25 | ] 26 | 27 | /** 28 | * SupportedAlgorithms 29 | */ 30 | class SupportedAlgorithms { 31 | 32 | /** 33 | * Constructor 34 | */ 35 | constructor () { 36 | operations.forEach(op => { 37 | this[op] = new RegisteredAlgorithms() 38 | }) 39 | } 40 | 41 | /** 42 | * Supported Operations 43 | */ 44 | static get operations () { 45 | return operations 46 | } 47 | 48 | /** 49 | * Define Algorithm 50 | */ 51 | define (alg, op, type) { 52 | let registeredAlgorithms = this[op] 53 | registeredAlgorithms[alg] = type 54 | } 55 | 56 | /** 57 | * Normalize 58 | */ 59 | normalize (op, alg) { 60 | if (typeof alg === 'string') { 61 | return this.normalize(op, new KeyAlgorithm({ name: alg })) 62 | } 63 | 64 | if (typeof alg === 'object') { 65 | let registeredAlgorithms = this[op] 66 | let initialAlg 67 | 68 | try { 69 | initialAlg = new Algorithm(alg) 70 | } catch (error) { 71 | return error 72 | } 73 | 74 | let algName = initialAlg.name 75 | algName = registeredAlgorithms.getCaseInsensitive(algName) 76 | 77 | if (algName === undefined) { 78 | return new NotSupportedError(alg.name) 79 | } 80 | 81 | let desiredType, normalizedAlgorithm 82 | 83 | try { 84 | desiredType = require(registeredAlgorithms[algName]) 85 | normalizedAlgorithm = new desiredType(alg) 86 | normalizedAlgorithm.name = algName 87 | } catch (error) { 88 | return error 89 | } 90 | 91 | let dictionaries = desiredType.dictionaries 92 | 93 | for (let i = 0; i < dictionaries.length; i++) { 94 | let dictionary = dictionaries[i] 95 | let members = dictionary.members 96 | 97 | for (let key in members) { 98 | let member = members[key] 99 | let idlValue = normalizedAlgorithm[key] 100 | 101 | try { 102 | if (member === 'BufferSource' && idlValue !== undefined) { 103 | normalizedAlgorithm[key] = idlValue.slice() 104 | } 105 | 106 | if (member === 'HashAlgorithmIdentifier') { 107 | let hashAlgorithm = this.normalize('digest', idlValue) 108 | if (hashAlgorithm instanceof Error) { return hashAlgorithm } 109 | normalizedAlgorithm[key] = hashAlgorithm 110 | } 111 | 112 | if (member === 'AlgorithmIdentifier') { 113 | normalizedAlgorithm[key] = this.normalize(WTF, idlValue) 114 | } 115 | } catch (error) { 116 | return error 117 | } 118 | } 119 | } 120 | 121 | return normalizedAlgorithm 122 | } 123 | } 124 | } 125 | 126 | /** 127 | * Export 128 | */ 129 | module.exports = SupportedAlgorithms 130 | -------------------------------------------------------------------------------- /src/algorithms/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const SupportedAlgorithms = require('./SupportedAlgorithms') 5 | 6 | /** 7 | * Register Supported Algorithms 8 | */ 9 | const supportedAlgorithms = new SupportedAlgorithms() 10 | 11 | /** 12 | * encrypt 13 | */ 14 | supportedAlgorithms.define('RSA-OAEP', 'encrypt', '../algorithms/RSA-OAEP') 15 | supportedAlgorithms.define('AES-CTR', 'encrypt', '../algorithms/AES-CTR') 16 | supportedAlgorithms.define('AES-CBC', 'encrypt', '../algorithms/AES-CBC') 17 | supportedAlgorithms.define('AES-GCM', 'encrypt', '../algorithms/AES-GCM') 18 | //supportedAlgorithms.define('AES-CFB', 'encrypt', ) 19 | 20 | /** 21 | * decrypt 22 | */ 23 | supportedAlgorithms.define('RSA-OAEP', 'decrypt', '../algorithms/RSA-OAEP') 24 | supportedAlgorithms.define('AES-CTR', 'decrypt', '../algorithms/AES-CTR') 25 | supportedAlgorithms.define('AES-CBC', 'decrypt', '../algorithms/AES-CBC') 26 | supportedAlgorithms.define('AES-GCM', 'decrypt', '../algorithms/AES-GCM') 27 | //supportedAlgorithms.define('AES-CFB', 'decrypt', ) 28 | 29 | /** 30 | * sign 31 | */ 32 | supportedAlgorithms.define('RSASSA-PKCS1-v1_5', 'sign', '../algorithms/RSASSA-PKCS1-v1_5') 33 | supportedAlgorithms.define('RSA-PSS', 'sign', '../algorithms/RSA-PSS') 34 | supportedAlgorithms.define('ECDSA', 'sign', '../algorithms/ECDSA') 35 | supportedAlgorithms.define('EDDSA', 'sign', '../algorithms/EDDSA') 36 | //supportedAlgorithms.define('AES-CMAC', 'sign', ) 37 | supportedAlgorithms.define('HMAC', 'sign', '../algorithms/HMAC') 38 | 39 | /** 40 | * verify 41 | */ 42 | supportedAlgorithms.define('RSASSA-PKCS1-v1_5', 'verify', '../algorithms/RSASSA-PKCS1-v1_5') 43 | supportedAlgorithms.define('RSA-PSS', 'verify', '../algorithms/RSA-PSS') 44 | supportedAlgorithms.define('ECDSA', 'verify', '../algorithms/ECDSA') 45 | supportedAlgorithms.define('EDDSA', 'verify', '../algorithms/EDDSA') 46 | //supportedAlgorithms.define('AES-CMAC', 'verify', ) 47 | supportedAlgorithms.define('HMAC', 'verify', '../algorithms/HMAC') 48 | 49 | /** 50 | * digest 51 | */ 52 | supportedAlgorithms.define('SHA-1', 'digest', '../algorithms/SHA') 53 | supportedAlgorithms.define('SHA-256', 'digest', '../algorithms/SHA') 54 | supportedAlgorithms.define('SHA-384', 'digest', '../algorithms/SHA') 55 | supportedAlgorithms.define('SHA-512', 'digest', '../algorithms/SHA') 56 | 57 | /** 58 | * deriveKey 59 | */ 60 | //supportedAlgorithms.define('ECDH', 'deriveKey', ) 61 | //supportedAlgorithms.define('DH', 'deriveKey', ) 62 | //supportedAlgorithms.define('CONCAT', 'deriveKey', ) 63 | //supportedAlgorithms.define('HKDF-CTR', 'deriveKey', ) 64 | //supportedAlgorithms.define('PBKDF2', 'deriveKey', ) 65 | 66 | /** 67 | * deriveBits 68 | */ 69 | //supportedAlgorithms.define('ECDH', 'deriveBits', ) 70 | //supportedAlgorithms.define('DH', 'deriveBits', ) 71 | //supportedAlgorithms.define('CONCAT', 'deriveBits', ) 72 | //supportedAlgorithms.define('HKDF-CTR', 'deriveBits', ) 73 | //supportedAlgorithms.define('PBKDF2', 'deriveBits', ) 74 | 75 | /** 76 | * generateKey 77 | */ 78 | supportedAlgorithms.define('RSASSA-PKCS1-v1_5', 'generateKey', '../algorithms/RSASSA-PKCS1-v1_5') 79 | supportedAlgorithms.define('RSA-PSS', 'generateKey', '../algorithms/RSA-PSS') 80 | supportedAlgorithms.define('RSA-OAEP', 'generateKey', '../algorithms/RSA-OAEP') 81 | supportedAlgorithms.define('ECDSA', 'generateKey', '../algorithms/ECDSA') 82 | supportedAlgorithms.define('EDDSA', 'generateKey', '../algorithms/EDDSA') 83 | //supportedAlgorithms.define('ECDH', 'generateKey', ) 84 | supportedAlgorithms.define('AES-CTR', 'generateKey', '../algorithms/AES-CTR') 85 | supportedAlgorithms.define('AES-CBC', 'generateKey', '../algorithms/AES-CBC') 86 | //supportedAlgorithms.define('AES-CMAC', 'generateKey', ) 87 | supportedAlgorithms.define('AES-GCM', 'generateKey', '../algorithms/AES-GCM') 88 | //supportedAlgorithms.define('AES-CFB', 'generateKey', ) 89 | supportedAlgorithms.define('AES-KW', 'generateKey', '../algorithms/AES-KW') 90 | supportedAlgorithms.define('HMAC', 'generateKey', '../algorithms/HMAC') 91 | //supportedAlgorithms.define('DH', 'generateKey', ) 92 | //supportedAlgorithms.define('PBKDF2', 'generateKey', ) 93 | 94 | /** 95 | * importKey 96 | */ 97 | supportedAlgorithms.define('RSASSA-PKCS1-v1_5', 'importKey', '../algorithms/RSASSA-PKCS1-v1_5') 98 | supportedAlgorithms.define('RSA-PSS', 'importKey', '../algorithms/RSA-PSS') 99 | supportedAlgorithms.define('RSA-OAEP', 'importKey', '../algorithms/RSA-OAEP') 100 | supportedAlgorithms.define('ECDSA', 'importKey', '../algorithms/ECDSA') 101 | supportedAlgorithms.define('EDDSA', 'importKey', '../algorithms/EDDSA') 102 | //supportedAlgorithms.define('ECDH', 'importKey', ) 103 | supportedAlgorithms.define('AES-CTR', 'importKey', '../algorithms/AES-CTR') 104 | supportedAlgorithms.define('AES-CBC', 'importKey', '../algorithms/AES-CBC') 105 | //supportedAlgorithms.define('AES-CMAC', 'importKey', ) 106 | supportedAlgorithms.define('AES-GCM', 'importKey', '../algorithms/AES-GCM') 107 | //supportedAlgorithms.define('AES-CFB', 'importKey', ) 108 | supportedAlgorithms.define('AES-KW', 'importKey', '../algorithms/AES-KW') 109 | supportedAlgorithms.define('HMAC', 'importKey', '../algorithms/HMAC') 110 | //supportedAlgorithms.define('DH', 'importKey', ) 111 | //supportedAlgorithms.define('CONCAT', 'importKey', ) 112 | //supportedAlgorithms.define('HKDF-CTR', 'importKey', ) 113 | //supportedAlgorithms.define('PBKDF2', 'importey', ) 114 | 115 | /** 116 | * exportKey 117 | */ 118 | supportedAlgorithms.define('RSASSA-PKCS1-v1_5', 'exportKey', '../algorithms/RSASSA-PKCS1-v1_5') 119 | supportedAlgorithms.define('RSA-PSS', 'exportKey', '../algorithms/RSA-PSS') 120 | supportedAlgorithms.define('RSA-OAEP', 'exportKey', '../algorithms/RSA-OAEP') 121 | supportedAlgorithms.define('EDDSA', 'exportKey', '../algorithms/EDDSA') 122 | supportedAlgorithms.define('ECDSA', 'exportKey', '../algorithms/ECDSA') 123 | //supportedAlgorithms.define('ECDH', 'exportKey', ) 124 | supportedAlgorithms.define('AES-CTR', 'exportKey', '../algorithms/AES-CTR') 125 | supportedAlgorithms.define('AES-CBC', 'exportKey', '../algorithms/AES-CBC') 126 | //supportedAlgorithms.define('AES-CMAC', 'exportKey', ) 127 | supportedAlgorithms.define('AES-GCM', 'exportKey', '../algorithms/AES-GCM') 128 | //supportedAlgorithms.define('AES-CFB', 'exportKey', ) 129 | supportedAlgorithms.define('AES-KW', 'exportKey', '../algorithms/AES-KW') 130 | supportedAlgorithms.define('HMAC', 'exportKey', '../algorithms/HMAC') 131 | //supportedAlgorithms.define('DH', 'exportKey', ) 132 | 133 | /** 134 | * wrapKey 135 | */ 136 | supportedAlgorithms.define('RSA-OAEP', 'wrapKey', '../algorithms/RSA-OAEP') 137 | supportedAlgorithms.define('AES-CTR', 'wrapKey', '../algorithms/AES-CTR') 138 | supportedAlgorithms.define('AES-CBC', 'wrapKey', '../algorithms/AES-CBC') 139 | supportedAlgorithms.define('AES-GCM', 'wrapKey', '../algorithms/AES-GCM') 140 | //supportedAlgorithms.define('AES-CFB', 'wrapKey', ) 141 | supportedAlgorithms.define('AES-KW', 'wrapKey', '../algorithms/AES-KW') 142 | 143 | /** 144 | * unwrapKey 145 | */ 146 | supportedAlgorithms.define('RSA-OAEP', 'unwrapKey', '../algorithms/RSA-OAEP') 147 | supportedAlgorithms.define('AES-CTR', 'unwrapKey', '../algorithms/AES-CTR') 148 | supportedAlgorithms.define('AES-CBC', 'unwrapKey', '../algorithms/AES-CBC') 149 | supportedAlgorithms.define('AES-GCM', 'unwrapKey', '../algorithms/AES-GCM') 150 | //supportedAlgorithms.define('AES-CFB', 'unwrapKey', ) 151 | supportedAlgorithms.define('AES-KW', 'unwrapKey', '../algorithms/AES-KW') 152 | 153 | /** 154 | * Export 155 | */ 156 | module.exports = supportedAlgorithms -------------------------------------------------------------------------------- /src/dictionaries/AES-KW.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvilresearch/webcrypto/210653f1bee449fec86214dc2fa4258fff775b4c/src/dictionaries/AES-KW.js -------------------------------------------------------------------------------- /src/dictionaries/AesCbcParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * AesCbcParams 8 | */ 9 | class AesCbcParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = AesCbcParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/AesCfbParams.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Local dependencies 4 | */ 5 | const Algorithm = require('./Algorithm') 6 | 7 | /** 8 | * AesCfbParams 9 | */ 10 | class AesCfbParams extends Algorithm { 11 | 12 | /** 13 | * Constructor 14 | */ 15 | constructor () {} 16 | } 17 | 18 | /** 19 | * Export 20 | */ 21 | module.exports = AesCfbParams 22 | -------------------------------------------------------------------------------- /src/dictionaries/AesCmacParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * AesCmacParams 8 | */ 9 | class AesCmacParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = AesCmacParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/AesCtrParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * AesCtrParams 8 | */ 9 | class AesCtrParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = AesCtrParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/AesDerivedKeyParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * AesDerivedKeyParams 8 | */ 9 | class AesDerivedKeyParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = AesDerivedKeyParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/AesGcmParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * AesGcmParams 8 | */ 9 | class AesGcmParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = AesGcmParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/AesKeyAlgorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const KeyAlgorithm = require('./KeyAlgorithm') 5 | 6 | /** 7 | * AesKeyAlgorithm 8 | */ 9 | class AesKeyAlgorithm extends KeyAlgorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor (algorithm) { 15 | super(algorithm) 16 | //TODO Do more here. 17 | } 18 | } 19 | 20 | /** 21 | * Export 22 | */ 23 | module.exports = AesKeyAlgorithm 24 | -------------------------------------------------------------------------------- /src/dictionaries/AesKeyGenParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * AesKeyGenParams 8 | */ 9 | class AesKeyGenParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = AesKeyGenParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/Algorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Algorithm 3 | */ 4 | class Algorithm { 5 | 6 | /** 7 | * constructor 8 | * 9 | * @description 10 | * The Algorithm object is a dictionary object [WebIDL] which is used 11 | * to specify an algorithm and any additional parameters required to 12 | * fully specify the desired operation. 13 | * 14 | * @param {string|Object} algorithm 15 | */ 16 | constructor (algorithm) { 17 | if (typeof algorithm === 'string') { 18 | this.name = algorithm 19 | } else { 20 | Object.assign(this, algorithm) 21 | if (typeof this.name !== 'string') { 22 | throw new Error('Algorithm name must be a string') 23 | } 24 | } 25 | } 26 | } 27 | 28 | /** 29 | * Export 30 | */ 31 | module.exports = Algorithm 32 | -------------------------------------------------------------------------------- /src/dictionaries/ConcatParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * ConcatParams 8 | */ 9 | class ConcatParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = ConcatParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/DhImportKeyParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * DhImportKeyParams 8 | */ 9 | class DhImportKeyParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = DhImportKeyParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/DhKeyAlgorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const KeyAlgorithm = require('./KeyAlgorithm') 5 | 6 | /** 7 | * DhKeyAlgorithm 8 | */ 9 | class DhKeyAlgorithm extends KeyAlgorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = DhKeyAlgorithm 21 | -------------------------------------------------------------------------------- /src/dictionaries/DhKeyDeriveParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * DhKeyDeriveParams 8 | */ 9 | class DhKeyDeriveParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = DhKeyDeriveParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/DhKeyGenParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * DhKeyGenParams 8 | */ 9 | class DhKeyGenParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = DhKeyGenParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/EcKeyAlgorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const KeyAlgorithm = require('./KeyAlgorithm') 5 | 6 | /** 7 | * Mapping 8 | */ 9 | const mapping = [ 10 | { namedCurve: 'P-256', name: 'prime256v1', alg: 'ES256', hash: 'sha256' }, 11 | { namedCurve: 'P-384', name: 'secp384r1', alg: 'ES384', hash: 'sha384' }, 12 | { namedCurve: 'P-521', name: 'secp521r1', alg: 'ES512', hash: 'sha512' }, 13 | { namedCurve: 'K-256', name: 'secp256k1', alg: 'KS256', hash: 'sha256' }, 14 | ] 15 | 16 | /** 17 | * EcKeyAlgorithm 18 | */ 19 | class EcKeyAlgorithm extends KeyAlgorithm { 20 | 21 | /** 22 | * Constructor 23 | */ 24 | constructor (algorithm) { 25 | super(algorithm) 26 | } 27 | 28 | static get mapping () { 29 | return mapping 30 | } 31 | } 32 | 33 | /** 34 | * Export 35 | */ 36 | module.exports = EcKeyAlgorithm 37 | -------------------------------------------------------------------------------- /src/dictionaries/EcKeyGenParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * EcKeyGenParams 8 | */ 9 | class EcKeyGenParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor (params) { 15 | super(params) 16 | } 17 | 18 | static get mapping () { 19 | 20 | } 21 | } 22 | 23 | /** 24 | * Export 25 | */ 26 | module.exports = EcKeyGenParams 27 | -------------------------------------------------------------------------------- /src/dictionaries/EcKeyImportParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * EcKeyImportParams 8 | */ 9 | class EcKeyImportParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor (params) { 15 | super(params) 16 | } 17 | } 18 | 19 | /** 20 | * Export 21 | */ 22 | module.exports = EcKeyImportParams 23 | -------------------------------------------------------------------------------- /src/dictionaries/EcdhKeyDeriveParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * EcdhKeyDeriveParams 8 | */ 9 | class EcdhKeyDeriveParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = EcdhKeyDeriveParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/EcdsaParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * EcdsaParams 8 | */ 9 | class EcdsaParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = EcdsaParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/HkdfCtrParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * HkdfCtrParams 8 | */ 9 | class HkdfCtrParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = HkdfCtrParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/HmacImportParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * HmacImportParams 8 | */ 9 | class HmacImportParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = HmacImportParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/HmacKeyAlgorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Package dependencies 3 | */ 4 | const base64url = require('base64url') 5 | const crypto = require('crypto') 6 | 7 | /** 8 | * Local dependencies 9 | */ 10 | const CryptoKey = require('../keys/CryptoKey') 11 | const JsonWebKey = require('../keys/JsonWebKey') 12 | const KeyAlgorithm = require('./KeyAlgorithm') 13 | 14 | /** 15 | * Errors 16 | */ 17 | const { 18 | DataError, 19 | OperationError, 20 | NotSupportedError, 21 | KeyFormatNotSupportedError 22 | } = require('../errors') 23 | 24 | /** 25 | * HmacKeyAlgorithm 26 | */ 27 | class HmacKeyAlgorithm extends KeyAlgorithm { 28 | } 29 | 30 | /** 31 | * Export 32 | */ 33 | module.exports = HmacKeyAlgorithm 34 | -------------------------------------------------------------------------------- /src/dictionaries/HmacKeyGenParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * HmacKeyGenParams 8 | */ 9 | class HmacKeyGenParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = HmacKeyGenParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/KeyAlgorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const {NotSupportedError} = require('../errors') 5 | 6 | /** 7 | * KeyAlgorithm dictionary 8 | */ 9 | class KeyAlgorithm { 10 | 11 | /** 12 | * constructor 13 | * 14 | * @param {object} algorithm 15 | */ 16 | constructor (algorithm) { 17 | Object.assign(this, algorithm) 18 | 19 | // validate name 20 | if (this.name === undefined) { 21 | throw new Error('KeyAlgorithm must have a name') 22 | } 23 | } 24 | } 25 | 26 | 27 | 28 | /** 29 | * Export 30 | */ 31 | module.exports = KeyAlgorithm 32 | -------------------------------------------------------------------------------- /src/dictionaries/Pbkdf2Params.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * Pbkdf2Params 8 | */ 9 | class Pbkdf2Params extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = Pbkdf2Params 21 | -------------------------------------------------------------------------------- /src/dictionaries/RsaHashedImportParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * RsaHashedImportParams 3 | */ 4 | class RsaHashedImportParams { 5 | constructor (hash) { 6 | // validate and set hash 7 | //if (!(hash instanceof HashAlgorithmIdentifier)) { throw new Error() } 8 | this.hash = hash 9 | } 10 | } 11 | 12 | /** 13 | * Export 14 | */ 15 | module.exports = RsaHashedImportParams 16 | -------------------------------------------------------------------------------- /src/dictionaries/RsaHashedKeyAlgorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Package dependencies 3 | */ 4 | const RSA = require('node-rsa') 5 | const crypto = require('crypto') 6 | const {spawnSync} = require('child_process') 7 | const {TextEncoder, TextDecoder} = require('text-encoding') 8 | 9 | /** 10 | * Local dependencies 11 | */ 12 | const CryptoKey = require('../keys/CryptoKey') 13 | const CryptoKeyPair = require('../keys/CryptoKeyPair') 14 | const JsonWebKey = require('../keys/JsonWebKey') 15 | const KeyAlgorithm = require('./KeyAlgorithm') 16 | const RsaKeyAlgorithm = require('./RsaKeyAlgorithm') 17 | const supportedAlgorithms = require('../algorithms') 18 | 19 | /** 20 | * Errors 21 | */ 22 | const { 23 | DataError, 24 | OperationError, 25 | InvalidAccessError, 26 | KeyFormatNotSupportedError 27 | } = require('../errors') 28 | 29 | /** 30 | * RsaHashedKeyAlgorithm 31 | */ 32 | class RsaHashedKeyAlgorithm extends RsaKeyAlgorithm { 33 | 34 | } 35 | 36 | /** 37 | * Export 38 | */ 39 | module.exports = RsaHashedKeyAlgorithm 40 | -------------------------------------------------------------------------------- /src/dictionaries/RsaHashedKeyGenParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const KeyAlgorithm = require('./KeyAlgorithm') 5 | const RsaKeyGenParams = require('./RsaKeyGenParams') 6 | 7 | /** 8 | * RsaHashedKeyGenParams 9 | */ 10 | class RsaHashedKeyGenParams extends RsaKeyGenParams { 11 | 12 | /** 13 | * constructor 14 | */ 15 | constructor (algorithm) { 16 | super(algorithm) 17 | } 18 | 19 | /** 20 | * validate 21 | */ 22 | validate () { 23 | // validate hash is an object 24 | if (typeof this.hash !== 'object') { 25 | throw new Error( 26 | 'hash of RsaHashedKeyGenParams must be an object' 27 | ) 28 | } 29 | } 30 | } 31 | 32 | /** 33 | * Export 34 | */ 35 | module.exports = RsaHashedKeyGenParams 36 | -------------------------------------------------------------------------------- /src/dictionaries/RsaKeyAlgorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const KeyAlgorithm = require('./KeyAlgorithm') 5 | 6 | /** 7 | * RsaKeyAlgorithm 8 | */ 9 | class RsaKeyAlgorithm extends KeyAlgorithm { 10 | 11 | /** 12 | * constructor 13 | * 14 | * @param {object} algorithm 15 | */ 16 | constructor (algorithm) { 17 | // Call parent constructor then validate sundry tests 18 | super(algorithm) 19 | /* FIXME Implement to pass tests 20 | // validate type of modulusLength 21 | if (typeof this.modulusLength !== 'number') { 22 | throw new Error( 23 | 'modulusLength of RsaKeyAlgorithm must be a number' 24 | ) 25 | } 26 | 27 | // validate range of modulusLength 28 | if (this.modulusLength < 1024) { 29 | throw new Error( 30 | 'modulusLength of RsaKeyAlgorithm must be at least 1024' 31 | ) 32 | } 33 | 34 | // validate publicExponent 35 | if (!(this.publicExponent instanceof Uint8Array)) { 36 | throw new Error( 37 | 'publicExponent of RsaKeyAlgorithm must be a BigInteger' 38 | ) 39 | }*/ 40 | } 41 | } 42 | 43 | /** 44 | * Export 45 | */ 46 | module.exports = RsaKeyAlgorithm 47 | -------------------------------------------------------------------------------- /src/dictionaries/RsaKeyGenParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * RsaKeyGenParams 8 | */ 9 | class RsaKeyGenParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | * 14 | * @param {number} modulusLength 15 | * @param {BigInteger} publicExponent 16 | */ 17 | constructor (algorithm) { 18 | super(algorithm) 19 | } 20 | 21 | /** 22 | * validate 23 | */ 24 | validate () { 25 | // validate modulusLength 26 | if (typeof this.modulusLength !== 'number') { 27 | throw new Error() 28 | } 29 | 30 | if (this.modulusLength < 1024) { 31 | throw new Error() 32 | } 33 | 34 | // validate publicExponent 35 | if (!(this.publicExponent instanceof Uint8Array)) { 36 | throw new Error() 37 | } 38 | } 39 | } 40 | 41 | /** 42 | * Export 43 | */ 44 | module.exports = RsaKeyGenParams 45 | -------------------------------------------------------------------------------- /src/dictionaries/RsaOaepParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * RsaOaepParams 8 | */ 9 | class RsaOaepParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = RsaOaepParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/RsaPssParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const Algorithm = require('./Algorithm') 5 | 6 | /** 7 | * RsaPssParams 8 | */ 9 | class RsaPssParams extends Algorithm { 10 | 11 | /** 12 | * Constructor 13 | */ 14 | constructor () {} 15 | } 16 | 17 | /** 18 | * Export 19 | */ 20 | module.exports = RsaPssParams 21 | -------------------------------------------------------------------------------- /src/dictionaries/ShaKeyAlgorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | const crypto = require('crypto') 5 | const KeyAlgorithm = require('./KeyAlgorithm') 6 | const {OperationError} = require('../errors') 7 | 8 | /** 9 | * ShaKeyAlgorithm 10 | */ 11 | class ShaKeyAlgorithm extends KeyAlgorithm { 12 | } 13 | 14 | /** 15 | * Export 16 | */ 17 | module.exports = ShaKeyAlgorithm 18 | -------------------------------------------------------------------------------- /src/errors/CurrentlyNotSupportedError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const NotSupportedError = require('./NotSupportedError') 5 | 6 | /** 7 | * CurrentlyNotSupportedError 8 | */ 9 | class CurrentlyNotSupportedError extends NotSupportedError { 10 | constructor (format,available) { 11 | super() 12 | this.message = `Currently '${format}' is not a supported format. Please use '${available}' in the interim.` 13 | } 14 | } 15 | 16 | /** 17 | * Export 18 | */ 19 | module.exports = CurrentlyNotSupportedError 20 | -------------------------------------------------------------------------------- /src/errors/DataError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DataError 3 | */ 4 | class DataError extends Error { 5 | constructor (message) { 6 | super(message) 7 | } 8 | } 9 | 10 | /** 11 | * Export 12 | */ 13 | module.exports = DataError 14 | -------------------------------------------------------------------------------- /src/errors/InvalidAccessError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * InvalidAccessError 3 | */ 4 | class InvalidAccessError extends Error { 5 | constructor (message) { 6 | super(message) 7 | } 8 | } 9 | 10 | /** 11 | * Export 12 | */ 13 | module.exports = InvalidAccessError 14 | -------------------------------------------------------------------------------- /src/errors/KeyFormatNotSupportedError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Local dependencies 3 | */ 4 | const NotSupportedError = require('./NotSupportedError') 5 | 6 | /** 7 | * KeyFormatNotSupportedError 8 | */ 9 | class KeyFormatNotSupportedError extends NotSupportedError { 10 | constructor (format) { 11 | super() 12 | this.message = `${format} is not a supported key format` 13 | } 14 | } 15 | 16 | /** 17 | * Export 18 | */ 19 | module.exports = KeyFormatNotSupportedError 20 | -------------------------------------------------------------------------------- /src/errors/NotSupportedError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NotSupportedError 3 | */ 4 | class NotSupportedError extends Error { 5 | constructor (alg) { 6 | super() 7 | this.message = `${alg} is not a supported algorithm` 8 | } 9 | } 10 | 11 | /** 12 | * Export 13 | */ 14 | module.exports = NotSupportedError 15 | -------------------------------------------------------------------------------- /src/errors/OperationError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OperationError 3 | */ 4 | class OperationError extends Error { 5 | constructor (message) { 6 | super(message) 7 | } 8 | } 9 | 10 | /** 11 | * Export 12 | */ 13 | module.exports = OperationError 14 | -------------------------------------------------------------------------------- /src/errors/QuotaExceededError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QuotaExceededError 3 | */ 4 | class QuotaExceededError extends Error { 5 | constructor (message) { 6 | super(message) 7 | } 8 | } 9 | 10 | /** 11 | * Export 12 | */ 13 | module.exports = QuotaExceededError 14 | -------------------------------------------------------------------------------- /src/errors/TypeMismatchError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TypeMismatchError 3 | */ 4 | class TypeMismatchError extends Error { 5 | constructor (message) { 6 | super(message) 7 | } 8 | } 9 | 10 | /** 11 | * Export 12 | */ 13 | module.exports = TypeMismatchError 14 | -------------------------------------------------------------------------------- /src/errors/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | DataError: require('./DataError'), 3 | InvalidAccessError: require('./InvalidAccessError'), 4 | KeyFormatNotSupportedError: require('./KeyFormatNotSupportedError'), 5 | CurrentlyNotSupportedError: require('./CurrentlyNotSupportedError'), 6 | NotSupportedError: require('./NotSupportedError'), 7 | OperationError: require('./OperationError'), 8 | QuotaExceededError: require('./QuotaExceededError'), 9 | TypeMismatchError: require('./TypeMismatchError') 10 | } 11 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const Crypto = require('./Crypto') 2 | module.exports = new Crypto() 3 | -------------------------------------------------------------------------------- /src/keys/CryptoKey.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CryptoKey interface 3 | */ 4 | class CryptoKey { 5 | 6 | /** 7 | * Constructor 8 | */ 9 | constructor ({type, extractable, algorithm, usages, handle}) { 10 | this.type = type 11 | this.extractable = extractable 12 | this.algorithm = algorithm 13 | this.usages = usages 14 | 15 | // ensure values are not writeable 16 | Object.defineProperties(this, { 17 | // TODO 18 | // These properties can't be fixed immediately on creation of the 19 | // object because the implementation may build it up in stages. 20 | // At some point in the operations before returning a key we should 21 | // freeze the object to prevent further manipulation. 22 | 23 | //type: { 24 | // enumerable: true, 25 | // writeable: false, 26 | // value: type 27 | //}, 28 | //extractable: { 29 | // enumerable: true, 30 | // writeable: true, 31 | // value: extractable 32 | //}, 33 | //algorithm: { 34 | // enumerable: true, 35 | // writeable: false, 36 | // value: algorithm 37 | //}, 38 | //usages: { 39 | // enumerable: true, 40 | // writeable: true, 41 | // value: usages 42 | //}, 43 | 44 | // this is the "key material" used internally 45 | // it is not enumerable, but we need it to be 46 | // accessible by algorithm implementations 47 | handle: { 48 | enumerable: false, 49 | writeable: false, 50 | value: handle 51 | } 52 | }) 53 | 54 | //if (!Array.isArray(type) || KeyType.indexOf(type) === -1) { throw new Error('Invalid CryptoKey type') } 55 | // verify type of algorithm 56 | // verify type/enum of usages 57 | } 58 | 59 | /** 60 | * Structured clone algorithm 61 | * https://www.w3.org/TR/WebCryptoAPI/#cryptokey-interface-clone 62 | * 63 | * TODO 64 | * This requires review and consideration with respect to the 65 | * internal structured cloning algorithm. 66 | * https://www.w3.org/TR/WebCryptoAPI/#dfn-structured-clone 67 | * 68 | * @param {Object} input 69 | * @param {Object} memory 70 | * 71 | * @returns {CryptoKey} 72 | */ 73 | clone ({type,extractable,algorithm,usages,handle}, memory) { 74 | return new CryptoKey({type,extractable,algorithm,usages,handle}) 75 | } 76 | } 77 | 78 | /** 79 | * Export 80 | */ 81 | module.exports = CryptoKey 82 | -------------------------------------------------------------------------------- /src/keys/CryptoKeyPair.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CryptoKeyPair dictionary 3 | */ 4 | class CryptoKeyPair { 5 | constructor ({publicKey,privateKey}) { 6 | this.publicKey = publicKey 7 | this.privateKey = privateKey 8 | } 9 | } 10 | 11 | /** 12 | * Export 13 | */ 14 | module.exports = CryptoKeyPair 15 | 16 | -------------------------------------------------------------------------------- /src/keys/JsonWebKey.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JsonWebKey 3 | */ 4 | class JsonWebKey { 5 | 6 | /** 7 | * Constructor 8 | */ 9 | constructor (data) { 10 | Object.assign(this, data) 11 | } 12 | } 13 | 14 | /** 15 | * Export 16 | */ 17 | module.exports = JsonWebKey 18 | -------------------------------------------------------------------------------- /src/keys/recognizedKeyUsages.js: -------------------------------------------------------------------------------- 1 | /** 2 | * KeyUsage 3 | */ 4 | class KeyUsage extends Array { 5 | 6 | /** 7 | * constructor 8 | */ 9 | constructor (collection) { 10 | super() 11 | collection.forEach(item => this.push(item)) 12 | } 13 | 14 | /** 15 | * normalize 16 | */ 17 | normalize (usages) { 18 | let result = [] 19 | 20 | for (let i = 0; i < this.length; i++) { 21 | let usage = this[i] 22 | 23 | if (usages.includes(usage)) { 24 | result.push(usage) 25 | } 26 | } 27 | 28 | return result 29 | } 30 | } 31 | 32 | /** 33 | * Export 34 | */ 35 | module.exports = new KeyUsage([ 36 | 'encrypt', 37 | 'decrypt', 38 | 'sign', 39 | 'verify', 40 | 'deriveBits', 41 | 'wrapKey', 42 | 'unwrapKey' 43 | ]) 44 | -------------------------------------------------------------------------------- /test/CryptoSpec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test dependencies 3 | */ 4 | const chai = require('chai') 5 | 6 | /** 7 | * Assertions 8 | */ 9 | chai.should() 10 | 11 | /** 12 | * Code under test 13 | */ 14 | const crypto = require('../src') 15 | const SubtleCrypto = require('../src/SubtleCrypto') 16 | 17 | /** 18 | * Tests 19 | */ 20 | describe('Crypto', () => { 21 | describe('getRandomValues', () => { 22 | it('should return the typed array argument', () => { 23 | let array = new Uint8Array(16) 24 | crypto.getRandomValues(array).should.equal(array) 25 | }) 26 | 27 | it('should write random values to the typed array', () => { 28 | let array = new Uint8Array(16) 29 | crypto.getRandomValues(array) 30 | array.forEach(value => value.should.not.equal(0)) 31 | }) 32 | }) 33 | 34 | describe('subtle', () => { 35 | it('should return a SubtleCrypto instance', () => { 36 | crypto.subtle.should.be.instanceof(SubtleCrypto) 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/EcdsaKeyPairsForTesting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies 3 | */ 4 | const ECDSA = require('../src/algorithms/ECDSA') 5 | 6 | /** 7 | * ECDSAObjects 8 | */ 9 | const ECDSA_K256 = new ECDSA({ name: 'ECDSA', namedCurve: 'K-256', hash: { name: 'SHA-256' } }) 10 | const ECDSA_P256 = new ECDSA({ name: 'ECDSA', namedCurve: 'P-256', hash: { name: 'SHA-256' } }) 11 | const ECDSA_P384 = new ECDSA({ name: 'ECDSA', namedCurve: 'P-384', hash: { name: 'SHA-256' } }) 12 | const ECDSA_P521 = new ECDSA({ name: 'ECDSA', namedCurve: 'P-521', hash: { name: 'SHA-256' } }) 13 | 14 | /** 15 | * ECDSA_K256_PublicKey 16 | */ 17 | const ECDSA_K256_PublicKey = ECDSA_K256.importKey( 18 | 'jwk', 19 | { 20 | kty: 'EC', 21 | crv: 'K-256', 22 | x: 'v-7l4HaEJwSkQwx0uzm0qZmHavW2Gpjm5D2tKifeIeo', 23 | y: 'JGGVfZjuI_25bBbEuwI5PA4M2DMyoS5d07BlA5dWr0E' 24 | }, 25 | { 26 | name: 'ECDSA', 27 | namedCurve: 'K-256', 28 | hash: { 29 | name: 'SHA-256' 30 | } 31 | }, 32 | true, 33 | ['verify']) 34 | 35 | /** 36 | * ECDSA_K256_PublicPem 37 | */ 38 | ECDSA_K256_PublicPem = 39 | `-----BEGIN PUBLIC KEY----- 40 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEL/yAQbK4Kg95AknFkfVO8V5rWkN1shsz 41 | 7jrEyDZ3McDY1rv9hRIcMyfrxeycgY5+jdPCIip9tpNe9q9QrNPqqg== 42 | -----END PUBLIC KEY-----` 43 | 44 | /** 45 | * ECDSA_K256_PrivateKey 46 | */ 47 | const ECDSA_K256_PrivateKey = ECDSA_K256.importKey( 48 | 'jwk', 49 | { 50 | kty: 'EC', 51 | crv: 'K-256', 52 | d: '-2luTboUHmtHxZUDNOyq1MY2JzHnVDd8xyhfaol8Mec', 53 | x: 'v-7l4HaEJwSkQwx0uzm0qZmHavW2Gpjm5D2tKifeIeo', 54 | y: 'JGGVfZjuI_25bBbEuwI5PA4M2DMyoS5d07BlA5dWr0E' 55 | }, 56 | { 57 | name: 'ECDSA', 58 | namedCurve: 'K-256', 59 | hash: { 60 | name: 'SHA-256' 61 | } 62 | }, 63 | true, 64 | ['sign']) 65 | 66 | /** 67 | * ECDSA_K256_PrivatePem 68 | */ 69 | ECDSA_K256_PrivatePem = 70 | `-----BEGIN EC PRIVATE KEY----- 71 | MHQCAQEEID0efOySYrkhN2hbYWqJ2H91SbQfVl2mXDe1YtDpgVLcoAcGBSuBBAAK 72 | oUQDQgAEL/yAQbK4Kg95AknFkfVO8V5rWkN1shsz7jrEyDZ3McDY1rv9hRIcMyfr 73 | xeycgY5+jdPCIip9tpNe9q9QrNPqqg== 74 | -----END EC PRIVATE KEY-----` 75 | 76 | /** 77 | * ECDSA_P256_PublicKey 78 | */ 79 | const ECDSA_P256_PublicKey = ECDSA_P256.importKey( 80 | 'jwk', 81 | { 82 | "kty": "EC", 83 | "crv": "P-256", 84 | "x": "bag3R0FTUvlLJGEM7zEhY2IGJgoEN4Q4UA7eR5Uh7BE", 85 | "y": "CM1wRrk_90vXDVymupli0yyHAcRBVS3MdQFUCSq5BV0" 86 | }, 87 | { 88 | name: 'ECDSA', 89 | namedCurve: 'P-256', 90 | hash: { 91 | name: 'SHA-256' 92 | } 93 | }, 94 | true, 95 | ['verify']) 96 | 97 | /** 98 | * ECDSA_P256_PublicPem 99 | */ 100 | ECDSA_P256_PublicPem = `-----BEGIN PUBLIC KEY----- 101 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbag3R0FTUvlLJGEM7zEhY2IGJgoE 102 | N4Q4UA7eR5Uh7BEIzXBGuT/3S9cNXKa6mWLTLIcBxEFVLcx1AVQJKrkFXQ== 103 | -----END PUBLIC KEY-----` 104 | 105 | /** 106 | * ECDSA_P256_PrivateKey 107 | */ 108 | const ECDSA_P256_PrivateKey = ECDSA_P256.importKey( 109 | 'jwk', 110 | { 111 | "kty": "EC", 112 | "crv": "P-256", 113 | "d": "3t2jGdosjga1Un35fmweoWMkgDmNYsHeYpkTY707WYE", 114 | "x": "bag3R0FTUvlLJGEM7zEhY2IGJgoEN4Q4UA7eR5Uh7BE", 115 | "y": "CM1wRrk_90vXDVymupli0yyHAcRBVS3MdQFUCSq5BV0" 116 | }, 117 | { 118 | name: 'ECDSA', 119 | namedCurve: 'P-256', 120 | hash: { 121 | name: 'SHA-256' 122 | } 123 | }, 124 | true, 125 | ['sign']) 126 | 127 | /** 128 | * ECDSA_P256_PrivatePem 129 | */ 130 | ECDSA_P256_PrivatePem = `-----BEGIN EC PRIVATE KEY----- 131 | MHcCAQEEIN7doxnaLI4GtVJ9+X5sHqFjJIA5jWLB3mKZE2O9O1mBoAoGCCqGSM49 132 | AwEHoUQDQgAEbag3R0FTUvlLJGEM7zEhY2IGJgoEN4Q4UA7eR5Uh7BEIzXBGuT/3 133 | S9cNXKa6mWLTLIcBxEFVLcx1AVQJKrkFXQ== 134 | -----END EC PRIVATE KEY-----` 135 | 136 | /** 137 | * ECDSA_P384_PublicKey 138 | */ 139 | const ECDSA_P384_PublicKey = ECDSA_P384.importKey( 140 | 'jwk', 141 | { 142 | "kty": "EC", 143 | "crv": "P-384", 144 | "x": "XN3ga623z2mu5BdxWXIVeGhznGbDHsqEKaIqTK9RlpCxKwrcdoRqaC3qlIPygcN7", 145 | "y": "NVey0D8e2Kjx1iqge8-KTCCd7VMs3o6mSPnVjdC6ls6ntN7-0M7yFNUNqh_LPCKz" 146 | }, 147 | { 148 | name: 'ECDSA', 149 | namedCurve: 'P-384', 150 | hash: { 151 | name: 'SHA-384' 152 | } 153 | }, 154 | true, 155 | ['verify']) 156 | 157 | /** 158 | * ECDSA_P384_PublicPem 159 | */ 160 | ECDSA_P384_PublicPem = `-----BEGIN PUBLIC KEY----- 161 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXN3ga623z2mu5BdxWXIVeGhznGbDHsqE 162 | KaIqTK9RlpCxKwrcdoRqaC3qlIPygcN7NVey0D8e2Kjx1iqge8+KTCCd7VMs3o6m 163 | SPnVjdC6ls6ntN7+0M7yFNUNqh/LPCKz 164 | -----END PUBLIC KEY-----` 165 | 166 | /** 167 | * ECDSA_P384_PrivateKey 168 | */ 169 | const ECDSA_P384_PrivateKey = ECDSA_P384.importKey( 170 | 'jwk', 171 | { 172 | "kty": "EC", 173 | "crv": "P-384", 174 | "d": "WH7_8esDPbqChcrIj5M0espufGuwWMM1lv_EVI9iJw3dh3QLrLl0MiA8JoyPdp51", 175 | "x": "XN3ga623z2mu5BdxWXIVeGhznGbDHsqEKaIqTK9RlpCxKwrcdoRqaC3qlIPygcN7", 176 | "y": "NVey0D8e2Kjx1iqge8-KTCCd7VMs3o6mSPnVjdC6ls6ntN7-0M7yFNUNqh_LPCKz" 177 | }, 178 | { 179 | name: 'ECDSA', 180 | namedCurve: 'P-384', 181 | hash: { 182 | name: 'SHA-384' 183 | } 184 | }, 185 | true, 186 | ['sign']) 187 | 188 | /** 189 | * ECDSA_P384_PrivatePem 190 | */ 191 | ECDSA_P384_PrivatePem = `-----BEGIN EC PRIVATE KEY----- 192 | MIGkAgEBBDBYfv/x6wM9uoKFysiPkzR6ym58a7BYwzWW/8RUj2InDd2HdAusuXQy 193 | IDwmjI92nnWgBwYFK4EEACKhZANiAARc3eBrrbfPaa7kF3FZchV4aHOcZsMeyoQp 194 | oipMr1GWkLErCtx2hGpoLeqUg/KBw3s1V7LQPx7YqPHWKqB7z4pMIJ3tUyzejqZI 195 | +dWN0LqWzqe03v7QzvIU1Q2qH8s8IrM= 196 | -----END EC PRIVATE KEY-----` 197 | 198 | /** 199 | * ECDSA_P521_PublicKey 200 | */ 201 | const ECDSA_P521_PublicKey = ECDSA_P521.importKey( 202 | 'jwk', 203 | { 204 | "kty": "EC", 205 | "crv": "P-521", 206 | "x": "AUfN5-iTkxajWZGoNe-jzNCpSydvol5MrKf-QPS0k_3ZxgW62YdOrespAXYrK--G_ff2IoGrXWOw2kuM0JWiioOn", 207 | "y": "AQc6m4W2GVAfWKxf3qhCtKj6nGCQxCNTHF54AX-Yh4g271LYf8VgW8bshT877SJOJjkYabKJc8NOG_lp1cwffOfq" 208 | }, 209 | { 210 | name: 'ECDSA', 211 | namedCurve: 'P-521', 212 | hash: { 213 | name: 'SHA-512' 214 | } 215 | }, 216 | true, 217 | ['verify']) 218 | 219 | /** 220 | * ECDSA_P521_PublicPem 221 | */ 222 | ECDSA_P521_PublicPem = `-----BEGIN PUBLIC KEY----- 223 | MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBR83n6JOTFqNZkag176PM0KlLJ2+i 224 | Xkysp/5A9LST/dnGBbrZh06t6ykBdisr74b99/YigatdY7DaS4zQlaKKg6cBBzqb 225 | hbYZUB9YrF/eqEK0qPqcYJDEI1McXngBf5iHiDbvUth/xWBbxuyFPzvtIk4mORhp 226 | solzw04b+WnVzB985+o= 227 | -----END PUBLIC KEY-----` 228 | 229 | /** 230 | * ECDSA_P521_PrivateKey 231 | */ 232 | const ECDSA_P521_PrivateKey = ECDSA_P521.importKey( 233 | 'jwk', 234 | { 235 | "kty": "EC", 236 | "crv": "P-521", 237 | "d": "Aae3gDHukB0OAL-LRH3lWBUFJ951hxRRAH8qjnI0lB8zxZjUKR9pDMBaWbulz8WrgU1U5xVvEALQ9aL9S-le2h6W", 238 | "x": "AUfN5-iTkxajWZGoNe-jzNCpSydvol5MrKf-QPS0k_3ZxgW62YdOrespAXYrK--G_ff2IoGrXWOw2kuM0JWiioOn", 239 | "y": "AQc6m4W2GVAfWKxf3qhCtKj6nGCQxCNTHF54AX-Yh4g271LYf8VgW8bshT877SJOJjkYabKJc8NOG_lp1cwffOfq" 240 | }, 241 | { 242 | name: 'ECDSA', 243 | namedCurve: 'P-521', 244 | hash: { 245 | name: 'SHA-512' 246 | } 247 | }, 248 | true, 249 | ['sign']) 250 | 251 | /** 252 | * ECDSA_P521_PrivatePem 253 | */ 254 | ECDSA_P521_PrivatePem = `-----BEGIN EC PRIVATE KEY----- 255 | MIHcAgEBBEIBp7eAMe6QHQ4Av4tEfeVYFQUn3nWHFFEAfyqOcjSUHzPFmNQpH2kM 256 | wFpZu6XPxauBTVTnFW8QAtD1ov1L6V7aHpagBwYFK4EEACOhgYkDgYYABAFHzefo 257 | k5MWo1mRqDXvo8zQqUsnb6JeTKyn/kD0tJP92cYFutmHTq3rKQF2Kyvvhv339iKB 258 | q11jsNpLjNCVooqDpwEHOpuFthlQH1isX96oQrSo+pxgkMQjUxxeeAF/mIeINu9S 259 | 2H/FYFvG7IU/O+0iTiY5GGmyiXPDThv5adXMH3zn6g== 260 | -----END EC PRIVATE KEY-----` 261 | 262 | /** 263 | * Export 264 | */ 265 | module.exports = { 266 | ECDSA_K256_PrivateKey, 267 | ECDSA_K256_PublicKey, 268 | ECDSA_K256_PrivatePem, 269 | ECDSA_K256_PublicPem, 270 | ECDSA_P256_PrivateKey, 271 | ECDSA_P256_PublicKey, 272 | ECDSA_P256_PrivatePem, 273 | ECDSA_P256_PublicPem, 274 | ECDSA_P384_PrivateKey, 275 | ECDSA_P384_PublicKey, 276 | ECDSA_P384_PrivatePem, 277 | ECDSA_P384_PublicPem, 278 | ECDSA_P521_PrivateKey, 279 | ECDSA_P521_PublicKey, 280 | ECDSA_P521_PrivatePem, 281 | ECDSA_P521_PublicPem, 282 | } 283 | -------------------------------------------------------------------------------- /test/RsaKeyPairForPSSTesting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies 3 | */ 4 | const CryptoKey = require('../src/keys/CryptoKey') 5 | const keyto = require('@trust/keyto') 6 | 7 | /** 8 | * RsaPrivateKey 9 | */ 10 | const RsaPrivateKey = 11 | `-----BEGIN RSA PRIVATE KEY----- 12 | MIIEogIBAAKCAQEAtK1GWlnRWt35Nb1J3NUz7SBKP5Ui/r5g8xcqPIdUklKreZdn 13 | xzMLBGf8RV1woo+EudIrLanN5kiReX5e29bm/LKagasBnYn1sb+uLVeFJVahsslS 14 | IY9vKUtIfJlI2svGzrygD2LHLNzrMgQs+Nk7kil+KliFepeaCyEiSuGE+/zb9MUD 15 | K47Jj2YBlhugrqSvCTkfCmIFD98asQsJQpU8oMOKtyDZMHpSaVgNO0MH1/hiH2W4 16 | tC7R0e3/5qpCoD73LOgh0c0EIM03tle7il60cI+9vRleuie7BSqoPiokiQvQLwWi 17 | PLZq3u9hUfLLexHmlPe+JWn9GGi9UNh1eV7OJQIDAQABAoIBAGX9hkBMgWy87wfR 18 | 8ZcSVzydRKx9wIJy74Fp6zK95hSvTBLYUAHXo3l6RaLWa1WolHDc3fjp6Mv83Pnr 19 | RxrsRfoRzDw0TzYiAaq0HFuGEygPrjmhgZZmRIbX83Q6hzDTZUegnO3ygaKmlrHm 20 | P4i9/+2zNIAs9jRMze1IZ/ZDNfGUSZsRyJG69k9hnRpFVoJ/WkPn0zJrkDXmiWyw 21 | vVpK06NBMF0fnnXRPs+XN/NDhhB6arg7PQZlU2hk4xubtPDQiwaBkeSzj1PFP5y5 22 | GkocW/bBha6wHpUxzy2Sx7Yte9jmEY3QoZy6KCGvWdkQQYRQ17JN0RyhDc828cL5 23 | rfonpoECgYEA3g9/IyYuQUf+sCnbsEBsd1ni87CvXAxG3NDQXrioZjNIuu32T5r/ 24 | I2481gtwfUieyYfg4ZFwXA9m2wWC1qWLz82oZBhedGMO5Uw4IY6glzGHmFZDeFt+ 25 | Jrfi0xcmPuBulJTHMkcNHgE/OLMXCpUsr4C96IL7ktGB3OmQagDqE+ECgYEA0EqR 26 | 63vQSabgmVd5KvsGlwgjcjN5ajFA7oDrvrP4DFp/8SFjMzia3XylUXqaHU8tyPwe 27 | uq8OMSF1l4KoYiD00waPXXeguvCGNo4vTXSJvMMLoXPVGGXs8QZ4UzKlacplwpiI 28 | jY2oU1ZOBIKHHqMoZmHSVNUI+VYO3BJ5vlfNwsUCgYAUkgzt/aB1Ta0LNqVyO1WQ 29 | 7NO4TVrBRSXfWLykuahn50JKhra1gx81cgXSsjaWdH65How3eRiWfprBmU4Ygjdk 30 | ZaG+u/8r+u0rUpc0jJjVyLHN69fOM3OJNKmfclqJopK70thtEOXnLKhloTl2MoF0 31 | NJHjExco751/EGffWfxVIQKBgCqpS0/O8S9UpaXim6eo+IWQninyzwhoBCOVdjN+ 32 | Cu0E0DWkH/xKuLVqpTWWBeDA6eDDesvDtQVtE/evRCutElfyfQSoztvbDbI41wln 33 | OBrYXBZ6cgfoQGpxZ82qjuSnFsaPlVBg1jwTbjFQRrqIsmqd2IWViJwA+1Qp2JOa 34 | ykL9AoGAXMgvAWE9jJcoluljAcUhVMp837FTdhY1m1rv6BNzjaG0iQ8qV7OI/MnJ 35 | SHpGrbNh4k/6bUyuozHV9nkWD6vYJL5stEsP3SIv2FqoytyoeGoLpPg4BPD5ye57 36 | QQ6nQ+YQJWuvl63/GPKN3AzB9uMNkcnp4A/ezighFir0KZpwrlw= 37 | -----END RSA PRIVATE KEY-----` 38 | 39 | /** 40 | * RsaPrivateJwk 41 | */ 42 | const RsaPrivateJwk = keyto.from(RsaPrivateKey,'pem').toJwk('private') 43 | 44 | /** 45 | * RsaPrivateCryptoKey 46 | */ 47 | const RsaPrivateCryptoKeySHA1 = new CryptoKey({ 48 | type: 'private', 49 | algorithm: { name: 'RSA-PSS', saltLength: 128, hash: {name: 'SHA-1'} }, 50 | extractable: false, 51 | usages: ['sign'], 52 | handle: RsaPrivateKey 53 | }) 54 | 55 | const RsaPrivateCryptoKeySHA256 = new CryptoKey({ 56 | type: 'private', 57 | algorithm: { name: 'RSA-PSS', saltLength: 128, hash: {name: 'SHA-256'} }, 58 | extractable: false, 59 | usages: ['sign'], 60 | handle: RsaPrivateKey 61 | }) 62 | 63 | const RsaPrivateCryptoKeySHA384 = new CryptoKey({ 64 | type: 'private', 65 | algorithm: { name: 'RSA-PSS', saltLength: 128, hash: {name: 'SHA-384'} }, 66 | extractable: false, 67 | usages: ['sign'], 68 | handle: RsaPrivateKey 69 | }) 70 | 71 | const RsaPrivateCryptoKeySHA512 = new CryptoKey({ 72 | type: 'private', 73 | algorithm: { name: 'RSA-PSS', saltLength: 128, hash: {name: 'SHA-512'} }, 74 | extractable: false, 75 | usages: ['sign'], 76 | handle: RsaPrivateKey 77 | }) 78 | 79 | /** 80 | * RsaPublicKey 81 | */ 82 | const RsaPublicKey = 83 | `-----BEGIN PUBLIC KEY----- 84 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtK1GWlnRWt35Nb1J3NUz 85 | 7SBKP5Ui/r5g8xcqPIdUklKreZdnxzMLBGf8RV1woo+EudIrLanN5kiReX5e29bm 86 | /LKagasBnYn1sb+uLVeFJVahsslSIY9vKUtIfJlI2svGzrygD2LHLNzrMgQs+Nk7 87 | kil+KliFepeaCyEiSuGE+/zb9MUDK47Jj2YBlhugrqSvCTkfCmIFD98asQsJQpU8 88 | oMOKtyDZMHpSaVgNO0MH1/hiH2W4tC7R0e3/5qpCoD73LOgh0c0EIM03tle7il60 89 | cI+9vRleuie7BSqoPiokiQvQLwWiPLZq3u9hUfLLexHmlPe+JWn9GGi9UNh1eV7O 90 | JQIDAQAB 91 | -----END PUBLIC KEY-----` 92 | 93 | /** 94 | * RsaPublicJwk 95 | */ 96 | const RsaPublicJwk = keyto.from(RsaPublicKey,'pem').toJwk('public') 97 | 98 | /** 99 | * RsaPrivateCryptoKey 100 | */ 101 | const RsaPublicCryptoKeySHA1 = new CryptoKey({ 102 | type: 'public', 103 | algorithm: { name: 'RSA-PSS', saltLength: 128, hash: {name: 'SHA-1'} }, 104 | extractable: true, 105 | usages: ['verify'], 106 | handle: RsaPublicKey 107 | }) 108 | 109 | const RsaPublicCryptoKeySHA256 = new CryptoKey({ 110 | type: 'public', 111 | algorithm: { name: 'RSA-PSS', saltLength: 128, hash: {name: 'SHA-256'} }, 112 | extractable: true, 113 | usages: ['verify'], 114 | handle: RsaPublicKey 115 | }) 116 | 117 | const RsaPublicCryptoKeySHA384 = new CryptoKey({ 118 | type: 'public', 119 | algorithm: { name: 'RSA-PSS', saltLength: 128, hash: {name: 'SHA-384'} }, 120 | extractable: true, 121 | usages: ['verify'], 122 | handle: RsaPublicKey 123 | }) 124 | 125 | const RsaPublicCryptoKeySHA512 = new CryptoKey({ 126 | type: 'public', 127 | algorithm: { name: 'RSA-PSS', saltLength: 128, hash: {name: 'SHA-512'} }, 128 | extractable: true, 129 | usages: ['verify'], 130 | handle: RsaPublicKey 131 | }) 132 | 133 | /** 134 | * Export 135 | */ 136 | module.exports = { 137 | RsaPrivateKey, 138 | RsaPrivateJwk, 139 | RsaPrivateCryptoKeySHA1, 140 | RsaPrivateCryptoKeySHA256, 141 | RsaPrivateCryptoKeySHA384, 142 | RsaPrivateCryptoKeySHA512, 143 | RsaPublicKey, 144 | RsaPublicJwk, 145 | RsaPublicCryptoKeySHA1, 146 | RsaPublicCryptoKeySHA256, 147 | RsaPublicCryptoKeySHA384, 148 | RsaPublicCryptoKeySHA512 149 | } 150 | -------------------------------------------------------------------------------- /test/RsaKeyPairForTesting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies 3 | */ 4 | const CryptoKey = require('../src/keys/CryptoKey') 5 | const keyto = require('@trust/keyto') 6 | 7 | /** 8 | * RsaPrivateKey 9 | */ 10 | const RsaPrivateKey = 11 | `-----BEGIN RSA PRIVATE KEY----- 12 | MIIEowIBAAKCAQEAiEJoO1tBT1Yc9jdYWI5JUkMnOlFD+weoi1rkxsWvZoBRJJGi 13 | fjrdmIn/5xOaaW38Cg535lo6NEorsVsq7V6zGan2QCT1TRCb7vJq4UIEq6tL5uB0 14 | BZMyByKBYDKVGAinXYd502nJ1T7sbZQnSjZFC3HgDvrqb/4bDIbO0+sAiaTumt+2 15 | uyIYcGYBuIfTi8vmElz2ngUFh+8K/uQyH7YjrOrg6ThOldh8IVzaOSA7LAb/DjyC 16 | +H44F/J24qMRLGuWK53gz+2RazSBotiNUsGoxdZv30Sud3Yfbz9ZjSXxPWpRnG6m 17 | ZAZW76oSbn8FvTSTWrf0iU6MyNkv/QuAjF+1BQIDAQABAoIBAHb3G8/vDad59NFX 18 | Yu/2Urfa363/882BUztQQXv2bvycPbwi1u9E7+JVYjLbH667ExmopjBdSIIM2/b+ 19 | NQ2H5/EZPmGkovME9E/8ISrInBFR/nP2NfYEHOKz0qctopSYQZ/cP5ZAv7JKPNwz 20 | RNZ7aW7jno8VrYfYIL+gF4ZYoGCLdIdw2rFaobZFGtUQ1ASpuBIS3NAQjxQLTdlz 21 | jUXCqqE02VKVW6Chr/ZPDnsjDmVxZjY5+vLoZRyS4jWBR64fgVrA+FoCFqtbKh5X 22 | ZCGUSRhGYs06XLlnjLn91ftgO6Di3FbQ2d4nrMRkD8ciOPv1iao429wKThiChTge 23 | 0DRF5SECgYEAvblqHOYDjdRTPV2rumoWKPzREhebi0ljKeMBFPvqVBM/IvOhqpVa 24 | cBsDCNGHwkOo3lX+M+c8y381ZR66pJb5QpF7qfIjlOQEYQfLc31HErYcHiPtKSNj 25 | L4HP5kAoZT4ILFZlfnVJP8oZ/S+BKO27juMwDVUk/wlI2CiN0a1oPWkCgYEAt9vB 26 | +yjoWydrBXy5q4m0pMcTm9FZum9kahCXx/0QjYPLjxwX6+d8Tc1Y1/VROtQDAIxu 27 | yMZxkboQ0L8uXtVQCjVz8hG1UDeqzISxLyTVP+JtD6yijhyrtQdgtokgAFzBHpYa 28 | MKgr8tARtojF5EyWPTQJpBSI2+tl0GgwEOa3Gz0CgYB65SQLXCNpN+RDl+2pbxaz 29 | rjBvm8Mx0nPdqiIFSblchKsdJNvP97cBbz3j9HYQLGuyudlUHbGPz/LycZlNDE6i 30 | BEMqrqLFy33arIXpZXkocbZ8/6CcSUPyfhABggWoryn0LnLIG4k7PNrg2mi77mLU 31 | B+4UdNbmLUl2W66h58XiIQKBgDG6kMccE2zERqAfUiDhiCihZ95XS4uvoVtGzabb 32 | /eQo55/3m0jFPcvVZNhUk/nzajR1x2kqs4EU8INlkmc4DwQT3R52R7JAvEPBCCOW 33 | NM+osJLywKzreE3ohvIYOL2gWOOq+b57Xhe4y3GxoMTVKjW3o3vryfChxNIPvCB2 34 | JsSJAoGBAJV3gcwgFgAA6t8m7g4YStDKANJngttdfHZC1IhGFOtKPc/rneobgDCt 35 | 48gw9bQD8gy87laRb/hjm/0Az4bjtDDOkKY5yhCUtipnpx4FR12nGRmMfRGedLJh 36 | rrdlkni8537vUl2rwiG3U3LTi9vHMIbBQek5rxlbc8jS8ejGUFdc 37 | -----END RSA PRIVATE KEY-----` 38 | 39 | /** 40 | * RsaPrivateJwk 41 | */ 42 | const RsaPrivateJwk = keyto.from(RsaPrivateKey,'pem').toJwk('private') 43 | 44 | /** 45 | * RsaPrivateCryptoKey 46 | */ 47 | const RsaPrivateCryptoKeySHA1 = new CryptoKey({ 48 | type: 'private', 49 | algorithm: { name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-1'} }, 50 | extractable: false, 51 | usages: ['sign'], 52 | handle: RsaPrivateKey 53 | }) 54 | 55 | const RsaPrivateCryptoKeySHA256 = new CryptoKey({ 56 | type: 'private', 57 | algorithm: { name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-256'} }, 58 | extractable: false, 59 | usages: ['sign'], 60 | handle: RsaPrivateKey 61 | }) 62 | 63 | const RsaPrivateCryptoKeySHA384 = new CryptoKey({ 64 | type: 'private', 65 | algorithm: { name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-384'} }, 66 | extractable: false, 67 | usages: ['sign'], 68 | handle: RsaPrivateKey 69 | }) 70 | 71 | const RsaPrivateCryptoKeySHA512 = new CryptoKey({ 72 | type: 'private', 73 | algorithm: { name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-512'} }, 74 | extractable: false, 75 | usages: ['sign'], 76 | handle: RsaPrivateKey 77 | }) 78 | 79 | /** 80 | * RsaPublicKey 81 | */ 82 | const RsaPublicKey = 83 | `-----BEGIN PUBLIC KEY----- 84 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiEJoO1tBT1Yc9jdYWI5J 85 | UkMnOlFD+weoi1rkxsWvZoBRJJGifjrdmIn/5xOaaW38Cg535lo6NEorsVsq7V6z 86 | Gan2QCT1TRCb7vJq4UIEq6tL5uB0BZMyByKBYDKVGAinXYd502nJ1T7sbZQnSjZF 87 | C3HgDvrqb/4bDIbO0+sAiaTumt+2uyIYcGYBuIfTi8vmElz2ngUFh+8K/uQyH7Yj 88 | rOrg6ThOldh8IVzaOSA7LAb/DjyC+H44F/J24qMRLGuWK53gz+2RazSBotiNUsGo 89 | xdZv30Sud3Yfbz9ZjSXxPWpRnG6mZAZW76oSbn8FvTSTWrf0iU6MyNkv/QuAjF+1 90 | BQIDAQAB 91 | -----END PUBLIC KEY-----` 92 | 93 | /** 94 | * RsaPublicJwk 95 | */ 96 | const RsaPublicJwk = keyto.from(RsaPublicKey,'pem').toJwk('public') 97 | 98 | /** 99 | * RsaPrivateCryptoKey 100 | */ 101 | const RsaPublicCryptoKeySHA1 = new CryptoKey({ 102 | type: 'public', 103 | algorithm: { name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-1'} }, 104 | extractable: true, 105 | usages: ['verify'], 106 | handle: RsaPublicKey 107 | }) 108 | 109 | const RsaPublicCryptoKeySHA256 = new CryptoKey({ 110 | type: 'public', 111 | algorithm: { name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-256'} }, 112 | extractable: true, 113 | usages: ['verify'], 114 | handle: RsaPublicKey 115 | }) 116 | 117 | const RsaPublicCryptoKeySHA384 = new CryptoKey({ 118 | type: 'public', 119 | algorithm: { name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-384'} }, 120 | extractable: true, 121 | usages: ['verify'], 122 | handle: RsaPublicKey 123 | }) 124 | 125 | const RsaPublicCryptoKeySHA512 = new CryptoKey({ 126 | type: 'public', 127 | algorithm: { name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-512'} }, 128 | extractable: true, 129 | usages: ['verify'], 130 | handle: RsaPublicKey 131 | }) 132 | 133 | /** 134 | * Export 135 | */ 136 | module.exports = { 137 | RsaPrivateKey, 138 | RsaPrivateJwk, 139 | RsaPrivateCryptoKeySHA1, 140 | RsaPrivateCryptoKeySHA256, 141 | RsaPrivateCryptoKeySHA384, 142 | RsaPrivateCryptoKeySHA512, 143 | RsaPublicKey, 144 | RsaPublicJwk, 145 | RsaPublicCryptoKeySHA1, 146 | RsaPublicCryptoKeySHA256, 147 | RsaPublicCryptoKeySHA384, 148 | RsaPublicCryptoKeySHA512 149 | } 150 | -------------------------------------------------------------------------------- /test/algorithms/HMACSpec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test dependencies 3 | */ 4 | const chai = require('chai') 5 | 6 | /** 7 | * Assertions 8 | */ 9 | chai.should() 10 | const expect = chai.expect 11 | 12 | /** 13 | * Code under test 14 | */ 15 | const {TextEncoder} = require('text-encoding') 16 | const crypto = require('../../src') 17 | const CryptoKey = require('../../src/keys/CryptoKey') 18 | const KeyAlgorithm = require('../../src/dictionaries/KeyAlgorithm') 19 | const HmacKeyAlgorithm = require('../../src/dictionaries/HmacKeyAlgorithm') 20 | const HMAC = require('../../src/algorithms/HMAC') 21 | const OperationError = require('../../src/errors/OperationError') 22 | 23 | /** 24 | * Tests 25 | */ 26 | describe('HMAC', () => { 27 | describe('dictionaries getter', () => { 28 | it('should return an array', () => { 29 | HMAC.dictionaries.should.eql([ 30 | KeyAlgorithm, 31 | HmacKeyAlgorithm 32 | ]) 33 | }) 34 | }) 35 | 36 | describe('members getter', () => { 37 | it('should return an object', () => { 38 | HMAC.members.should.eql({}) 39 | }) 40 | }) 41 | 42 | describe('sign', () => { 43 | let alg, rawHmacKey, chromeHmacSignature, data, importedHmacKey 44 | 45 | before(() => { 46 | alg = { name: 'HMAC', hash: { name: 'SHA-256' } } 47 | 48 | rawHmacKey = new Uint8Array([ 49 | 137, 35, 38, 29, 130, 138, 121, 216, 20, 204, 169, 50 | 61, 76, 80, 127, 140, 197, 193, 48, 6, 207, 97, 70, 51 | 77, 57, 30, 72, 245, 249, 9, 204, 207, 215, 1, 53, 52 | 33, 189, 28, 105, 9, 61, 158, 152, 113, 46, 83, 3, 53 | 228, 234, 140, 20, 31, 192, 34, 254, 113, 117, 59, 54 | 17, 78, 164, 52, 116, 38 55 | ]) 56 | 57 | chromeHmacSignature = new Uint8Array([ 58 | 72, 73, 12, 66, 105, 131, 73, 116, 160, 243, 96, 121, 59 | 121, 40, 244, 198, 107, 151, 113, 243, 51, 19, 60, 234, 60 | 93, 23, 199, 14, 42, 118, 25, 161 61 | ]) 62 | 63 | data = new TextEncoder() 64 | .encode('signed with Chrome generated webcrypto key') 65 | 66 | return crypto.subtle 67 | .importKey('raw', rawHmacKey, alg, true, ['sign', 'verify']) 68 | .then(cryptoKey => importedHmacKey = cryptoKey) 69 | }) 70 | 71 | it('should return an ArrayBuffer', () => { 72 | let hmac = new HMAC(alg) 73 | hmac.sign(importedHmacKey, data).should.be.instanceof(ArrayBuffer) 74 | }) 75 | 76 | it('should return a HMAC signature', () => { 77 | let hmac = new HMAC(alg) 78 | let signature = hmac.sign(importedHmacKey, data) 79 | Buffer.from(signature).should.eql(Buffer.from(chromeHmacSignature.buffer)) 80 | }) 81 | }) 82 | 83 | describe('verify', () => { 84 | let alg, rawHmacKey, chromeHmacSignature, data, importedHmacKey 85 | 86 | before(() => { 87 | alg = { name: 'HMAC', hash: { name: 'SHA-256' } } 88 | 89 | rawHmacKey = new Uint8Array([ 90 | 137, 35, 38, 29, 130, 138, 121, 216, 20, 204, 169, 91 | 61, 76, 80, 127, 140, 197, 193, 48, 6, 207, 97, 70, 92 | 77, 57, 30, 72, 245, 249, 9, 204, 207, 215, 1, 53, 93 | 33, 189, 28, 105, 9, 61, 158, 152, 113, 46, 83, 3, 94 | 228, 234, 140, 20, 31, 192, 34, 254, 113, 117, 59, 95 | 17, 78, 164, 52, 116, 38 96 | ]) 97 | 98 | chromeHmacSignature = new Uint8Array([ 99 | 72, 73, 12, 66, 105, 131, 73, 116, 160, 243, 96, 121, 100 | 121, 40, 244, 198, 107, 151, 113, 243, 51, 19, 60, 234, 101 | 93, 23, 199, 14, 42, 118, 25, 161 102 | ]) 103 | 104 | data = new TextEncoder() 105 | .encode('signed with Chrome generated webcrypto key') 106 | 107 | return crypto.subtle 108 | .importKey('raw', rawHmacKey, alg, true, ['sign', 'verify']) 109 | .then(cryptoKey => importedHmacKey = cryptoKey) 110 | }) 111 | 112 | it('should verify a valid HMAC signature', () => { 113 | let hmac = new HMAC(alg) 114 | hmac.verify(importedHmacKey, chromeHmacSignature, data).should.be.true 115 | }) 116 | 117 | it('should not verify an invalid HMAC signature', () => { 118 | let hmac = new HMAC(alg) 119 | let invalidData = new TextEncoder().encode('wrong data') 120 | hmac.verify(importedHmacKey, chromeHmacSignature, invalidData).should.be.false 121 | }) 122 | }) 123 | 124 | describe('generateKey', () => { 125 | let alg, hmac, key 126 | 127 | beforeEach(() => { 128 | alg = { name: 'HMAC', hash: { name: 'SHA-256' } } 129 | hmac = new HMAC(alg) 130 | key = hmac.generateKey(alg, true, ['sign', 'verify']) 131 | }) 132 | 133 | it('should throw with invalid usages', () => { 134 | expect(() => { 135 | hmac.generateKey(alg, true, ['sign', 'verify', 'wrong']) 136 | }).to.throw('Key usages can only include "sign" and "verify"') 137 | }) 138 | 139 | it('should throw with invalid length', () => { 140 | expect(() => { 141 | hmac.generateKey({ 142 | name: 'HMAC', 143 | hash: { 144 | name: 'SHA-256' 145 | }, 146 | length: 0 147 | }, true, ['sign', 'verify']) 148 | }).to.throw('Invalid HMAC length') 149 | }) 150 | 151 | it('should return CryptoKey', () => { 152 | key.should.be.instanceof(CryptoKey) 153 | }) 154 | 155 | it('should set key type', () => { 156 | key.type.should.equal('secret') 157 | }) 158 | 159 | it('should set key algorithm', () => { 160 | key.algorithm.should.be.instanceof(HMAC) 161 | }) 162 | 163 | it('should set key algorithm name', () => { 164 | key.algorithm.name.should.equal('HMAC') 165 | }) 166 | 167 | it('should set key algorithm hash', () => { 168 | key.algorithm.hash.should.be.instanceof(KeyAlgorithm) 169 | }) 170 | 171 | it('should set key algorithm hash name', () => { 172 | key.algorithm.hash.name.should.equal('SHA-256') 173 | }) 174 | 175 | it('should set key extractable', () => { 176 | key.extractable.should.equal(true) 177 | }) 178 | 179 | it('should set key usages', () => { 180 | key.usages.should.eql(['sign', 'verify']) 181 | }) 182 | 183 | it('should set key handle', () => { 184 | key.handle.should.be.instanceof(Buffer) 185 | }) 186 | }) 187 | 188 | describe('importKey', () => { 189 | let alg, hmac, rawHmacKey, key 190 | 191 | beforeEach(() => { 192 | alg = { name: 'HMAC', hash: { name: 'SHA-256' } } 193 | hmac = new HMAC(alg) 194 | 195 | rawHmacKey = new Uint8Array([ 196 | 137, 35, 38, 29, 130, 138, 121, 216, 20, 204, 169, 197 | 61, 76, 80, 127, 140, 197, 193, 48, 6, 207, 97, 70, 198 | 77, 57, 30, 72, 245, 249, 9, 204, 207, 215, 1, 53, 199 | 33, 189, 28, 105, 9, 61, 158, 152, 113, 46, 83, 3, 200 | 228, 234, 140, 20, 31, 192, 34, 254, 113, 117, 59, 201 | 17, 78, 164, 52, 116, 38 202 | ]) 203 | 204 | key = hmac.importKey('raw', rawHmacKey, alg, true, ['sign', 'verify']) 205 | }) 206 | 207 | it('should throw with invalid usages', () => { 208 | expect(() => { 209 | hmac.importKey('raw', rawHmacKey, alg, true, ['sign', 'verify', 'wrong']) 210 | }).to.throw('Key usages can only include "sign" and "verify"') 211 | }) 212 | 213 | it('should throw with missing algorithm hash', () => { 214 | expect(() => { 215 | alg = { name: 'HMAC' } 216 | hmac.importKey('raw', rawHmacKey, alg, true, ['sign', 'verify']) 217 | }).to.throw('HmacKeyGenParams: hash: Missing or not an AlgorithmIdentifier') 218 | }) 219 | 220 | it('should throw with unsupported key format', () => { 221 | expect(() => { 222 | hmac.importKey('WRONG', rawHmacKey, alg, true, ['sign', 'verify']) 223 | }).to.throw('WRONG is not a supported key format') 224 | }) 225 | 226 | it('should throw with empty key data', () => { 227 | expect(() => { 228 | hmac.importKey('raw', new Uint8Array(), alg, true, ['sign', 'verify']) 229 | }).to.throw('HMAC key data must not be empty') 230 | }) 231 | 232 | it('should import raw key data and return a CryptoKey', () => { 233 | hmac.importKey('raw', rawHmacKey, alg, true, ['sign', 'verify']) 234 | .should.be.instanceof(CryptoKey) 235 | }) 236 | 237 | it('should set key type', () => { 238 | hmac.importKey('raw', rawHmacKey, alg, true, ['sign', 'verify']) 239 | .type.should.equal('secret') 240 | }) 241 | 242 | it('should set key algorithm', () => { 243 | hmac.importKey('raw', rawHmacKey, alg, true, ['sign', 'verify']) 244 | .algorithm.should.be.instanceof(HMAC) 245 | }) 246 | 247 | it('should set key algorithm name', () => { 248 | key.algorithm.name.should.equal('HMAC') 249 | }) 250 | 251 | it('should set key algorithm hash name', () => { 252 | key.algorithm.hash.name.should.equal('SHA-256') 253 | }) 254 | 255 | it('should set key extractable', () => { 256 | key.extractable.should.equal(true) 257 | }) 258 | 259 | it('should set key usages', () => { 260 | key.usages.should.eql(['sign', 'verify']) 261 | }) 262 | 263 | it('should set key handle', () => { 264 | key.handle.should.be.instanceof(Buffer) 265 | }) 266 | }) 267 | 268 | describe('exportKey', () => { 269 | let alg, hmac, rawHmacKey, key 270 | 271 | beforeEach(() => { 272 | alg = { name: 'HMAC', hash: { name: 'SHA-256' } } 273 | hmac = new HMAC(alg) 274 | 275 | rawHmacKey = new Uint8Array([ 276 | 137, 35, 38, 29, 130, 138, 121, 216, 20, 204, 169, 277 | 61, 76, 80, 127, 140, 197, 193, 48, 6, 207, 97, 70, 278 | 77, 57, 30, 72, 245, 249, 9, 204, 207, 215, 1, 53, 279 | 33, 189, 28, 105, 9, 61, 158, 152, 113, 46, 83, 3, 280 | 228, 234, 140, 20, 31, 192, 34, 254, 113, 117, 59, 281 | 17, 78, 164, 52, 116, 38 282 | ]) 283 | 284 | key = hmac.importKey('raw', rawHmacKey, alg, true, ['sign', 'verify']) 285 | }) 286 | 287 | it('should throw with invalid usages', () => { 288 | expect(() => { 289 | hmac.exportKey('raw', {}) 290 | }).to.throw('argument must be CryptoKey') 291 | }) 292 | 293 | it('should throw with unsupported key format', () => { 294 | expect(() => { 295 | hmac.exportKey('WRONG', key) 296 | }).to.throw('WRONG is not a supported key format') 297 | }) 298 | 299 | it('should return a raw key', () => { 300 | hmac.exportKey('raw', key).should.be.instanceof(Buffer) 301 | }) 302 | }) 303 | }) 304 | -------------------------------------------------------------------------------- /test/algorithms/RegisteredAlgorithmsSpec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test dependencies 3 | */ 4 | const chai = require('chai') 5 | 6 | /** 7 | * Assertions 8 | */ 9 | chai.should() 10 | const expect = chai.expect 11 | 12 | /** 13 | * Code under test 14 | */ 15 | const RegisteredAlgorithms = require('../../src/algorithms/RegisteredAlgorithms') 16 | 17 | /** 18 | * Tests 19 | */ 20 | describe('RegisteredAlgorithms', () => { 21 | describe('constructor', () => { 22 | it('should assign object argument properties', () => { 23 | let registeredAlgorithms = new RegisteredAlgorithms({ a: 1, b: 2 }) 24 | registeredAlgorithms.should.eql({ a: 1, b: 2 }) 25 | }) 26 | }) 27 | 28 | describe('getCaseInsensitive', () => { 29 | let registeredAlgorithms 30 | 31 | beforeEach(() => { 32 | registeredAlgorithms = new RegisteredAlgorithms({ 33 | 'RSASSA-PKCS1-v1_5': {} 34 | }) 35 | }) 36 | 37 | it('should match a known property with exact case', () => { 38 | registeredAlgorithms 39 | .getCaseInsensitive('RSASSA-PKCS1-v1_5') 40 | .should.equal('RSASSA-PKCS1-v1_5') 41 | }) 42 | 43 | it('should match a known property with different case', () => { 44 | registeredAlgorithms 45 | .getCaseInsensitive('rsassa-pkcs1-v1_5') 46 | .should.equal('RSASSA-PKCS1-v1_5') 47 | }) 48 | 49 | it('should return "undefined" with no match', () => { 50 | expect(registeredAlgorithms.getCaseInsensitive('unknown')).to.be.undefined 51 | }) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /test/algorithms/SHASpec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test dependencies 3 | */ 4 | const chai = require('chai') 5 | 6 | /** 7 | * Assertions 8 | */ 9 | chai.should() 10 | const expect = chai.expect 11 | 12 | /** 13 | * Code under test 14 | */ 15 | const {TextEncoder} = require('text-encoding') 16 | const KeyAlgorithm = require('../../src/dictionaries/KeyAlgorithm') 17 | const ShaKeyAlgorithm = require('../../src/dictionaries/ShaKeyAlgorithm') 18 | const SHA = require('../../src/algorithms/SHA') 19 | const OperationError = require('../../src/errors/OperationError') 20 | 21 | /** 22 | * Tests 23 | */ 24 | describe('SHA', () => { 25 | describe('dictionaries getter', () => { 26 | it('should return an array', () => { 27 | SHA.dictionaries.should.eql([ 28 | KeyAlgorithm, 29 | ShaKeyAlgorithm 30 | ]) 31 | }) 32 | }) 33 | 34 | describe('members getter', () => { 35 | it('should return an object', () => { 36 | SHA.members.should.eql({}) 37 | }) 38 | }) 39 | 40 | describe('digest', () => { 41 | it('should return an ArrayBuffer', () => { 42 | let algorithm = { name: 'SHA-256' } 43 | let data = new TextEncoder().encode('return an ArrayBuffer') 44 | let sha = new SHA(algorithm) 45 | let result = sha.digest(algorithm, data) 46 | result.should.be.instanceof(ArrayBuffer) 47 | }) 48 | 49 | it('should return a SHA-1 digest', () => { 50 | let algorithm = { name: 'SHA-1' } 51 | let data = new TextEncoder().encode('created with webcrypto in Chrome') 52 | let digest = new Uint8Array([ 53 | 240, 245, 162, 97, 158, 225, 111, 59, 198, 40, 54 | 103, 60, 84, 159, 139, 205, 10, 116, 39, 41 55 | ]) 56 | let sha = new SHA(algorithm) 57 | let result = sha.digest(algorithm, data) 58 | Buffer.from(result).should.eql(Buffer.from(digest.buffer)) 59 | }) 60 | 61 | it('should return a SHA-256 digest', () => { 62 | let algorithm = { name: 'SHA-256' } 63 | let data = new TextEncoder().encode('created with webcrypto in Chrome') 64 | let digest = new Uint8Array([ 65 | 34, 103, 130, 78, 94, 197, 88, 55, 100, 33, 101, 66 | 214, 153, 38, 251, 0, 246, 42, 150, 222, 243, 57, 67 | 184, 244, 74, 187, 55, 10, 206, 17, 146, 65 68 | ]) 69 | let sha = new SHA(algorithm) 70 | let result = sha.digest(algorithm, data) 71 | Buffer.from(result).should.eql(Buffer.from(digest.buffer)) 72 | }) 73 | 74 | it('should return a SHA-384 digest', () => { 75 | let algorithm = { name: 'SHA-384' } 76 | let data = new TextEncoder().encode('created with webcrypto in Chrome') 77 | let digest = new Uint8Array([ 78 | 114, 126, 90, 78, 53, 164, 76, 208, 239, 167, 250, 77, 79 | 174, 115, 146, 12, 35, 96, 15, 73, 222, 48, 186, 102, 80 | 200, 91, 124, 153, 232, 76, 252, 143, 50, 251, 81, 152, 81 | 45, 189, 41, 167, 139, 29, 52, 9, 140, 197, 5, 238 82 | ]) 83 | let sha = new SHA(algorithm) 84 | let result = sha.digest(algorithm, data) 85 | Buffer.from(result).should.eql(Buffer.from(digest.buffer)) 86 | }) 87 | 88 | it('should return a SHA-512 digest', () => { 89 | let algorithm = { name: 'SHA-512' } 90 | let data = new TextEncoder().encode('created with webcrypto in Chrome') 91 | let digest = new Uint8Array([ 92 | 101, 55, 12, 162, 223, 251, 198, 26, 154, 74, 173, 61, 93 | 47, 45, 191, 105, 49, 36, 189, 141, 96, 145, 253, 102, 94 | 28, 145, 34, 244, 192, 232, 147, 88, 251, 73, 145, 241, 95 | 204, 213, 77, 129, 119, 107, 197, 94, 204, 57, 20, 153, 96 | 181, 113, 113, 50, 249, 134, 126, 99, 254, 190, 84, 124, 97 | 180, 216, 176, 112 98 | ]) 99 | let sha = new SHA(algorithm) 100 | let result = sha.digest(algorithm, data) 101 | Buffer.from(result).should.eql(Buffer.from(digest.buffer)) 102 | }) 103 | 104 | it('should throw an OperationError with unknown algorithm', () => { 105 | let algorithm = { name: 'SHA-UNKNOWN' } 106 | let data = new TextEncoder().encode('I am undigestable') 107 | let sha = new SHA(algorithm) 108 | expect(() => { 109 | sha.digest(algorithm, data) 110 | }).to.throw('SHA-UNKNOWN is not a supported algorithm') 111 | }) 112 | }) 113 | }) 114 | -------------------------------------------------------------------------------- /test/algorithms/SupportedAlgorithmsSpec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test dependencies 3 | */ 4 | const chai = require('chai') 5 | 6 | /** 7 | * Assertions 8 | */ 9 | chai.should() 10 | 11 | /** 12 | * Code under test 13 | */ 14 | const supportedAlgorithms = require('../../src/algorithms') 15 | const SupportedAlgorithms = require('../../src/algorithms/SupportedAlgorithms') 16 | const RegisteredAlgorithms = require('../../src/algorithms/RegisteredAlgorithms') 17 | const RSASSA_PKCS1_v1_5 = require('../../src/algorithms/RSASSA-PKCS1-v1_5') 18 | const SHA = require('../../src/algorithms/SHA') 19 | const NotSupportedError = require('../../src/errors/NotSupportedError') 20 | 21 | /** 22 | * Tests 23 | */ 24 | describe('SupportedAlgorithms', () => { 25 | 26 | /** 27 | * Constructor 28 | */ 29 | describe('constructor', () => { 30 | it('should initialize a container for each operation', () => { 31 | let operations = SupportedAlgorithms.operations 32 | 33 | operations.forEach(op => { 34 | supportedAlgorithms[op].should.be.instanceof(RegisteredAlgorithms) 35 | }) 36 | }) 37 | }) 38 | 39 | /** 40 | * Operations 41 | */ 42 | describe('operations', () => { 43 | it('should include specified operations', () => { 44 | let operations = SupportedAlgorithms.operations 45 | operations.should.include('encrypt') 46 | operations.should.include('decrypt') 47 | operations.should.include('sign') 48 | operations.should.include('verify') 49 | operations.should.include('deriveBits') 50 | operations.should.include('wrapKey') 51 | operations.should.include('unwrapKey') 52 | operations.should.include('generateKey') 53 | operations.should.include('importKey') 54 | operations.should.include('exportKey') 55 | operations.should.include('getLength') 56 | }) 57 | }) 58 | 59 | /** 60 | * Define 61 | */ 62 | describe('define', () => { 63 | it('should registered a type for an operation of an algorithm', () => { 64 | class Dictionary {} 65 | let alg = 'FAKE' 66 | supportedAlgorithms.define(alg, 'sign', Dictionary) 67 | supportedAlgorithms.sign[alg].should.equal(Dictionary) 68 | }) 69 | }) 70 | 71 | /** 72 | * Normalize 73 | */ 74 | describe('normalize', () => { 75 | describe('with string "alg" argument', () => { 76 | describe('unknown algorithm', () => { 77 | let normalizedAlgorithm 78 | 79 | before(() => { 80 | normalizedAlgorithm = supportedAlgorithms.normalize('sign', 'UNKNOWN') 81 | }) 82 | 83 | it('should return an error', () => { 84 | normalizedAlgorithm.should.be.instanceof(NotSupportedError) 85 | }) 86 | }) 87 | 88 | describe('valid algorithm', () => { 89 | let normalizedAlgorithm 90 | 91 | before(() => { 92 | normalizedAlgorithm = supportedAlgorithms.normalize('digest', 'SHA-256') 93 | }) 94 | 95 | it('should return the normalized algorithm', () => { 96 | normalizedAlgorithm.should.be.instanceof(SHA) 97 | }) 98 | }) 99 | }) 100 | 101 | describe('with object "alg" argument', () => { 102 | describe('invalid "name"', () => { 103 | let normalizedAlgorithm 104 | 105 | before(() => { 106 | normalizedAlgorithm = supportedAlgorithms.normalize('sign', {}) 107 | }) 108 | 109 | it('should return an error', () => { 110 | normalizedAlgorithm.should.be.instanceof(Error) 111 | }) 112 | }) 113 | 114 | describe('unknown algorithm', () => { 115 | let normalizedAlgorithm 116 | 117 | before(() => { 118 | normalizedAlgorithm = supportedAlgorithms.normalize('sign', { 119 | name: 'UNKNOWN' 120 | }) 121 | }) 122 | 123 | it('should return a NotSupportedError', () => { 124 | normalizedAlgorithm.should.be.instanceof(NotSupportedError) 125 | }) 126 | }) 127 | 128 | describe('invalid param', () => { 129 | it('should return an error') 130 | }) 131 | 132 | describe('valid params', () => { 133 | let normalizedAlgorithm 134 | 135 | before(() => { 136 | normalizedAlgorithm = supportedAlgorithms.normalize('generateKey', { 137 | name: 'RSASSA-PKCS1-v1_5', 138 | modulusLength: 2048, 139 | publicExponent: new Uint8Array([0x01, 0x00, 0x01]), 140 | hash: 'SHA-1' 141 | }) 142 | }) 143 | 144 | it('should return the normalized algorithm', () => { 145 | normalizedAlgorithm.should.be.instanceof(RSASSA_PKCS1_v1_5) 146 | }) 147 | }) 148 | }) 149 | }) 150 | 151 | /** 152 | * Default registration 153 | */ 154 | describe('default registration', () => {}) 155 | }) 156 | -------------------------------------------------------------------------------- /test/dictionaries/AlgorithmSpec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test dependencies 3 | */ 4 | const chai = require('chai') 5 | 6 | /** 7 | * Assertions 8 | */ 9 | chai.should() 10 | const expect = chai.expect 11 | 12 | /** 13 | * Code under test 14 | */ 15 | const Algorithm = require('../../src/dictionaries/Algorithm') 16 | 17 | /** 18 | * Tests 19 | */ 20 | describe('Algorithm', () => { 21 | describe('constructor', () => { 22 | describe('with string "algorithm" argument', () => { 23 | it('should set instance name to argument value', () => { 24 | let algorithm = new Algorithm('RSASSA-PKCS1-v1_5') 25 | algorithm.name.should.equal('RSASSA-PKCS1-v1_5') 26 | }) 27 | }) 28 | describe('with object "algorithm" argument', () => { 29 | it('should assign argument property values to instance', () => { 30 | let algorithm = new Algorithm({ name: 'RSASSA-PKCS1-v1_5' }) 31 | algorithm.name.should.equal('RSASSA-PKCS1-v1_5') 32 | }) 33 | 34 | it('should require algorithm name to be a string', () => { 35 | expect(() => { 36 | new Algorithm({ other: false }) 37 | }).to.throw('Algorithm name must be a string') 38 | }) 39 | }) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /test/dictionaries/HmacKeyAlgorithmSpec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test dependencies 3 | */ 4 | const chai = require('chai') 5 | 6 | /** 7 | * Assertions 8 | */ 9 | chai.should() 10 | const expect = chai.expect 11 | 12 | /** 13 | * Code under test 14 | */ 15 | const {TextEncoder} = require('text-encoding') 16 | const crypto = require('../../src') 17 | const CryptoKey = require('../../src/keys/CryptoKey') 18 | const KeyAlgorithm = require('../../src/dictionaries/KeyAlgorithm') 19 | const HmacKeyAlgorithm = require('../../src/dictionaries/HmacKeyAlgorithm') 20 | const OperationError = require('../../src/errors/OperationError') 21 | 22 | /** 23 | * Tests 24 | */ 25 | describe('HmacKeyAlgorithm', () => { 26 | }) 27 | -------------------------------------------------------------------------------- /test/dictionaries/KeyAlgorithmSpec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test dependencies 3 | */ 4 | const chai = require('chai') 5 | 6 | /** 7 | * Assertions 8 | */ 9 | chai.should() 10 | const expect = chai.expect 11 | 12 | /** 13 | * Code under test 14 | */ 15 | const KeyAlgorithm = require('../../src/dictionaries/KeyAlgorithm') 16 | const NotSupportedError = require('../../src/errors/NotSupportedError') 17 | 18 | /** 19 | * Tests 20 | */ 21 | describe('KeyAlgorithm', () => { 22 | describe('constructor', () => { 23 | it('should assign argument property values to instance', () => { 24 | let keyAlgorithm = new KeyAlgorithm({ name: 'RSASSA-PKCS1-v1_5' }) 25 | keyAlgorithm.name.should.equal('RSASSA-PKCS1-v1_5') 26 | }) 27 | 28 | it('should require algorithm name', () => { 29 | expect(() => { 30 | new KeyAlgorithm({ other: false }) 31 | }).to.throw('KeyAlgorithm must have a name') 32 | }) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /test/dictionaries/RsaHashedKeyAlgorithmSpec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test dependencies 3 | */ 4 | const chai = require('chai') 5 | const expect = chai.expect 6 | 7 | /** 8 | * Assertions 9 | */ 10 | chai.should() 11 | 12 | /** 13 | * Code under test 14 | */ 15 | const { 16 | RsaPrivateKey, 17 | RsaPrivateJwk, 18 | RsaPrivateCryptoKey, 19 | RsaPublicKey, 20 | RsaPublicJwk, 21 | RsaPublicCryptoKey 22 | } = require('../RsaKeyPairForTesting') 23 | 24 | const {TextEncoder} = require('text-encoding') 25 | const crypto = require('../../src') 26 | const CryptoKey = require('../../src/keys/CryptoKey') 27 | const CryptoKeyPair = require('../../src/keys/CryptoKeyPair') 28 | const KeyAlgorithm = require('../../src/dictionaries/KeyAlgorithm') 29 | const RsaKeyAlgorithm = require('../../src/dictionaries/RsaKeyAlgorithm') 30 | const RsaHashedKeyAlgorithm = require('../../src/dictionaries/RsaHashedKeyAlgorithm') 31 | const DataError = require('../../src/errors/DataError') 32 | const OperationError = require('../../src/errors/OperationError') 33 | const NotSupportedError = require('../../src/errors/NotSupportedError') 34 | 35 | /** 36 | * Tests 37 | */ 38 | describe('RsaHashedKeyAlgorithm', () => { 39 | }) 40 | -------------------------------------------------------------------------------- /test/dictionaries/ShaKeyAlgorithmSpec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test dependencies 3 | */ 4 | const chai = require('chai') 5 | 6 | /** 7 | * Assertions 8 | */ 9 | chai.should() 10 | const expect = chai.expect 11 | 12 | /** 13 | * Code under test 14 | */ 15 | const {TextEncoder} = require('text-encoding') 16 | const KeyAlgorithm = require('../../src/dictionaries/KeyAlgorithm') 17 | const ShaKeyAlgorithm = require('../../src/dictionaries/ShaKeyAlgorithm') 18 | const OperationError = require('../../src/errors/OperationError') 19 | 20 | /** 21 | * Tests 22 | */ 23 | describe('ShaKeyAlgorithm', () => { 24 | }) 25 | -------------------------------------------------------------------------------- /test/keys/CryptoKeyPairSpec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvilresearch/webcrypto/210653f1bee449fec86214dc2fa4258fff775b4c/test/keys/CryptoKeyPairSpec.js -------------------------------------------------------------------------------- /test/keys/CryptoKeySpec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvilresearch/webcrypto/210653f1bee449fec86214dc2fa4258fff775b4c/test/keys/CryptoKeySpec.js -------------------------------------------------------------------------------- /test/keys/JsonWebKeySpec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvilresearch/webcrypto/210653f1bee449fec86214dc2fa4258fff775b4c/test/keys/JsonWebKeySpec.js -------------------------------------------------------------------------------- /test/keys/recognizedKeyUsagesSpec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test dependencies 3 | */ 4 | const chai = require('chai') 5 | 6 | /** 7 | * Assertions 8 | */ 9 | chai.should() 10 | 11 | /** 12 | * Code under test 13 | */ 14 | const recognizedKeyUsages = require('../../src/keys/recognizedKeyUsages') 15 | const KeyUsage = recognizedKeyUsages.constructor 16 | 17 | /** 18 | * Tests 19 | */ 20 | describe('recognizedKeyUsages', () => { 21 | 22 | /** 23 | * constructor 24 | */ 25 | describe('constructor', () => { 26 | it('should initialize the list', () => { 27 | let usages = new KeyUsage(['sign', 'verify']) 28 | usages.should.include('sign') 29 | usages.should.include('verify') 30 | usages.length.should.equal(2) 31 | }) 32 | }) 33 | 34 | /** 35 | * normalize 36 | */ 37 | describe('normalize', () => { 38 | let normalized 39 | 40 | before(() => { 41 | normalized = recognizedKeyUsages.normalize(['foo', 'sign', 'bar', 'verify']) 42 | }) 43 | 44 | it('should include recognized usages', () => { 45 | normalized.should.include('sign') 46 | normalized.should.include('verify') 47 | }) 48 | 49 | it('should ignore unknown usages', () => { 50 | normalized.should.not.include('foo') 51 | normalized.should.not.include('bar') 52 | }) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | -R spec --recursive 2 | --------------------------------------------------------------------------------