├── .eslintrc.json
├── .gitattributes
├── .gitignore
├── .travis.yml
├── .yo-rc.json
├── BUG-BOUNTY.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Gruntfile.js
├── LICENSE
├── README.md
├── Todo.md
├── babel.config.js
├── dist
├── jose.js
├── jose.js.map
└── jose.min.js
├── examples
├── jose-jwe-example1.html
├── jose-jwe-example2.html
├── jose-jws-ecdsa-example.html
└── jose-jws-rsa-example.html
├── jose-jwe-jws.d.ts
├── lib
├── jose-core.js
├── jose-core.test.js
├── jose-jwe-decrypt.js
├── jose-jwe-encrypt.js
├── jose-jwe-webcryptographer.js
├── jose-jws-sign.js
├── jose-jws-verify.js
└── jose-utils.js
├── package.json
├── test
├── jose-jwe-test.js
├── jose-jws-ecdsa-test.js
├── jose-jws-rsa-test.js
└── qunit-promises.js
├── webpack.dev.js
└── webpack.prod.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true,
6 | "jest/globals": true
7 | },
8 | "extends": [
9 | "standard"
10 | ],
11 | "globals": {
12 | "Atomics": "readonly",
13 | "SharedArrayBuffer": "readonly"
14 | },
15 | "parserOptions": {
16 | "ecmaVersion": 2018,
17 | "sourceType": "module"
18 | },
19 | "rules": {
20 | "semi": ["error", "always"]
21 | },
22 | "plugins": ["jest"]
23 | }
24 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | dist/* -diff
2 |
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | node_modules
3 | .idea
4 | output.log
5 |
6 | package-lock.json
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "lts/*"
4 | - "node"
5 |
6 | addons:
7 | chrome: stable
8 |
9 | before_install:
10 | - npm install -g grunt-cli
11 |
12 | install: npm install
13 |
14 | script: grunt with_coverage --verbose
15 |
16 | after_success: grunt coveralls
17 |
--------------------------------------------------------------------------------
/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "jose-jwe-jws": {
3 | "configuration": {
4 | "config": {
5 | "topScope": [
6 | "const webpack = require('webpack')",
7 | "const path = require('path')",
8 | "\n",
9 | "/*\n * SplitChunksPlugin is enabled by default and replaced\n * deprecated CommonsChunkPlugin. It automatically identifies modules which\n * should be splitted of chunk by heuristics using module duplication count and\n * module category (i. e. node_modules). And splits the chunks…\n *\n * It is safe to remove \"splitChunks\" from the generated configuration\n * and was added as an educational example.\n *\n * https://webpack.js.org/plugins/split-chunks-plugin/\n *\n */",
10 | "/*\n * We've enabled UglifyJSPlugin for you! This minifies your app\n * in order to load faster and run less javascript.\n *\n * https://github.com/webpack-contrib/uglifyjs-webpack-plugin\n *\n */",
11 | "const UglifyJSPlugin = require('uglifyjs-webpack-plugin');",
12 | "\n"
13 | ],
14 | "webpackOptions": {
15 | "module": {
16 | "rules": [
17 | {
18 | "include": [
19 | "path.resolve(__dirname, 'src')"
20 | ],
21 | "loader": "'babel-loader'",
22 | "options": {
23 | "plugins": [
24 | "'syntax-dynamic-import'"
25 | ],
26 | "presets": [
27 | [
28 | "'env'",
29 | {
30 | "'modules'": false
31 | }
32 | ]
33 | ]
34 | },
35 | "test": "/\\.js$/"
36 | }
37 | ]
38 | },
39 | "entry": "./lib/jose-core",
40 | "output": {
41 | "filename": "'[name].[chunkhash].js'",
42 | "path": "path.resolve(__dirname, 'dist')"
43 | },
44 | "mode": "'development'",
45 | "plugins": [
46 | "new UglifyJSPlugin()"
47 | ],
48 | "optimization": {
49 | "splitChunks": {
50 | "cacheGroups": {
51 | "vendors": {
52 | "priority": -10,
53 | "test": "/[\\\\/]node_modules[\\\\/]/"
54 | }
55 | },
56 | "chunks": "'async'",
57 | "minChunks": 1,
58 | "minSize": 30000,
59 | "name": true
60 | }
61 | }
62 | },
63 | "configName": "dev"
64 | }
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/BUG-BOUNTY.md:
--------------------------------------------------------------------------------
1 | Serious about security
2 | ======================
3 |
4 | Square recognizes the important contributions the security research community
5 | can make. We therefore encourage reporting security issues with the code
6 | contained in this repository.
7 |
8 | If you believe you have discovered a security vulnerability, please follow the
9 | guidelines at https://hackerone.com/square-open-source
10 |
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project should eventually adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
6 | however it has not stabilized, yet.
7 |
8 | ## [Unreleased]
9 |
10 | ## [0.2.2] - 2020-01-13
11 | ### Fixed
12 | - Fix library export for TypeScript (#125)
13 |
14 | ### Changed
15 | - Update of many dev dependencies (#120, #123, #134, #137, #139, #140)
16 |
17 | ## [0.2.1] - 2019-08-08
18 | ### Fixed
19 | - Fix WebCryptographer export (#116).
20 |
21 | ### Changed
22 | - Update of many dev dependencies (#103, #104, #105, #107, #108, #111, #112)
23 |
24 | ### Deprecated
25 | - Node.js 9 is unsupported. CI is configured to run latest Node.js version and latest
26 | LTS release (#113).
27 |
28 | ## [0.2.0] - 2019-07-23
29 | ### Added
30 | - Support for signing and verifying using EC keys (#95).
31 |
32 | ### Fixed
33 | - Fix babel-loader config (#91).
34 | - `window.crypto` check now works for Service Workers (#92).
35 | - Add missing method signature to type definitions (#98).
36 | - Fix default ES6-style export (#97).
37 |
38 | ### Changed
39 | - Use ESLint instead of JSHint and adopt [JavaScript Standard Style](https://standardjs.com/)
40 | for the project -- with the exception of [semi rule](https://eslint.org/docs/rules/semi)
41 | (we still require semicolons everywhere) (#99).
42 |
43 | ## [0.1.7] - 2019-02-21
44 | ### Changed
45 | - Library is now written in ES6 and Babel is used for backwards compatibility (in browsers
46 | and Node.js environments) and it works with Webpack (#82).
47 |
48 | ### Fixed
49 | - Use passed algorithm when importing private RSA key (#67).
50 |
51 | ## [0.1.6] - 2017-10-16
52 | ### Added
53 | - Better type definitions that now also include allowed algorithm types (#61).
54 |
55 | ## [0.1.5] - 2017-05-20
56 | ### Fixed
57 | - Perform UTF-8 conversion on JSON stringified objects, not just strings (#55).
58 |
59 |
60 | ## [0.1.4] - 2016-10-25
61 | ### Added
62 | - Add payload to successful verification results (#48).
63 |
64 | ### Fixed
65 | - Ability to use `setCrypto` API in browsers (#49).
66 | - Allow CryptoKey to be recognized even when provided by minified applications (#50).
67 |
68 | ## [0.1.2] - 2016-09-16
69 | ### Added
70 | - Handle UTF-8 plaintexts (as allowed by the JOSE spec) (#42 & #46).
71 |
72 |
73 | ## [0.1.1] - 2016-09-06
74 | ### Fixed
75 | - Restore serialization methods for `SignedJws` interface (#39).
76 | - Replace Promise.accept() with Promise.resolve() as Promise API changed (#45).
77 |
78 | ## [0.1.0] - 2016-04-11
79 | ### Added
80 | - JWS support (#15).
81 | - Direct encryption using pre-shared symmetric keys (#22).
82 | - Add optional argument in Verifier that returns `Promise` given
83 | a key id (#24).
84 | - CommonJS packaging that allows for importing js-jose via Webpack (#27).
85 | - Support for Node.js, however it still requires a use of [polyfill](https://github.com/PeculiarVentures/node-webcrypto-ossl) (#30).
86 | - The API is enriched with TypeScript type definitions (#28 & #35).
87 |
88 | ### Changed
89 | - NPM package renamed to `jose-jwe-jws`
90 |
91 | ## [0.0.3] - 2015-03-03
92 | ### Added
93 | - Travis CI integration (#8).
94 | - Browser support check that ensures necessary APIs are available before
95 | JOSE operations (#11).
96 |
97 | ## [0.0.2] - 2015-02-11
98 | ### Added
99 | - Partial support for user defined headers (#2).
100 | - First NPM release.
101 |
102 | ### Fixed
103 | - Bug in public key import (#1).
104 |
105 | ## [0.0.1] - 2014-11-25
106 | ### Added
107 | - Initial release with support for JWE encryption.
108 | - Support for key encryption algorithms: RSA-OAEP (default), RSA-OAEP-256,
109 | A128KW (not recommended for use) and A256KW (not recommended for use).
110 | - Support for content encryption: A128CBC-HS256, A256CBC-HS512, A128GCM,
111 | A256GCM (default).
112 |
113 | [Unreleased]: https://github.com/square/js-jose/compare/v0.2.2...HEAD
114 | [0.2.2]: https://github.com/square/js-jose/compare/v0.2.1...v0.2.2
115 | [0.2.1]: https://github.com/square/js-jose/compare/v0.2.0...v0.2.1
116 | [0.2.0]: https://github.com/square/js-jose/compare/v0.1.7...v0.2.0
117 | [0.1.7]: https://github.com/square/js-jose/compare/v0.1.6...v0.1.7
118 | [0.1.6]: https://github.com/square/js-jose/compare/v0.1.5...v0.1.6
119 | [0.1.5]: https://github.com/square/js-jose/compare/v0.1.4...v0.1.5
120 | [0.1.4]: https://github.com/square/js-jose/compare/v0.1.2...v0.1.4
121 | [0.1.2]: https://github.com/square/js-jose/compare/v0.1.1...v0.1.2
122 | [0.1.1]: https://github.com/square/js-jose/compare/v0.1.0...v0.1.1
123 | [0.1.0]: https://github.com/square/js-jose/compare/v0.0.3...v0.1.0
124 | [0.0.3]: https://github.com/square/js-jose/compare/v0.0.2...v0.0.3
125 | [0.0.2]: https://github.com/square/js-jose/compare/v0.0.1...v0.0.2
126 | [0.0.1]: https://github.com/square/js-jose/releases/tag/v0.0.1
127 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | If you would like to contribute code to js-jose you can do so through GitHub by
4 | forking the repository and sending a pull request.
5 |
6 | When submitting code, please make every effort to follow existing conventions
7 | and style in order to keep the code as readable as possible. Please also make
8 | sure all tests pass by running `npm run build`.
9 |
10 | Before your code can be accepted into the project you must also sign the
11 | Individual Contributor License Agreement. We use [cla-assistant.io][1] and you
12 | will be prompted to sign once a pull request is opened.
13 |
14 | [1]: https://cla-assistant.io/
15 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | const webpackDevConfig = require('./webpack.dev');
2 | const webpackProdConfig = require('./webpack.prod');
3 |
4 | module.exports = function (grunt) {
5 | var config = {
6 | pkg: grunt.file.readJSON('package.json'),
7 |
8 | run: {
9 | jest: {
10 | cmd: 'jest'
11 | }
12 | },
13 |
14 | eslint: {
15 | options: {
16 | fix: true
17 | },
18 | target: 'lib'
19 | },
20 |
21 | webpack: {
22 | dev: webpackDevConfig,
23 | prod: webpackProdConfig
24 | },
25 |
26 | karma: {
27 | with_coverage: {
28 | options: {
29 | preprocessors: {
30 | 'dist/jose.js': ['coverage']
31 | },
32 | reporters: ['coverage', 'progress'],
33 | coverageReporter: {
34 | type: 'lcovonly',
35 | dir: 'coverage/'
36 | },
37 | frameworks: ['qunit'],
38 | files: [
39 | { pattern: 'dist/jose.js', watching: false, included: true },
40 | { pattern: 'test/qunit-promises.js', watching: false, included: true },
41 | 'test/jose-*.js'
42 | ],
43 | client: {
44 | clearContext: false,
45 | qunit: {
46 | showUI: true,
47 | testTimeout: 5000
48 | }
49 | },
50 | autoWatch: true,
51 | browsers: ['Chrome', 'ChromeHeadless', 'ChromeHeadlessNoSandbox'],
52 | customLaunchers: {
53 | ChromeHeadlessNoSandbox: {
54 | base: 'ChromeHeadless',
55 | flags: ['--no-sandbox']
56 | }
57 | },
58 | singleRun: true,
59 | plugins: ['karma-coverage', 'karma-qunit', 'karma-chrome-launcher']
60 | }
61 | },
62 | without_coverage: {
63 | options: {
64 | frameworks: ['qunit'],
65 | files: [
66 | { pattern: 'dist/jose.js', watching: false, included: true },
67 | { pattern: 'test/qunit-promises.js', watching: false, included: true },
68 | 'test/jose-*.js'
69 | ],
70 | client: {
71 | clearContext: false,
72 | qunit: {
73 | showUI: true,
74 | testTimeout: 5000
75 | }
76 | },
77 | autoWatch: true,
78 | browsers: ['Chrome'],
79 | browserConsoleLogOptions: {
80 | level: 'info',
81 | format: '%b %T: %m',
82 | path: 'output.log',
83 | terminal: true
84 | },
85 | singleRun: true,
86 | logLevel: 'info'
87 | }
88 | }
89 | },
90 |
91 | coveralls: {
92 | options: {
93 | debug: true,
94 | coverageDir: 'coverage',
95 | force: true,
96 | recursive: true
97 | }
98 | }
99 | };
100 |
101 | if (process.env.TRAVIS) {
102 | config.karma.with_coverage.browsers = ['ChromeHeadlessNoSandbox'];
103 | config.karma.without_coverage.browsers = ['ChromeHeadlessNoSandbox'];
104 | }
105 |
106 | grunt.initConfig(config);
107 |
108 | grunt.loadNpmTasks('grunt-run');
109 | grunt.loadNpmTasks('grunt-contrib-concat');
110 | grunt.loadNpmTasks('grunt-eslint');
111 | grunt.loadNpmTasks('grunt-contrib-uglify');
112 | grunt.loadNpmTasks('grunt-karma');
113 | grunt.loadNpmTasks('grunt-karma-coveralls');
114 | grunt.loadNpmTasks('grunt-webpack');
115 |
116 | grunt.registerTask('default', ['eslint', 'run:jest', 'webpack', 'karma:without_coverage']);
117 | grunt.registerTask('with_coverage', ['eslint', 'run:jest', 'webpack', 'karma:with_coverage']);
118 | };
119 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JS JOSE (DEPRECATED)
2 |
3 | This repository is **DEPRECATED**.
4 |
5 | It will be placed in an archived state on **February 27th, 2023**.
6 |
7 | ## What should I use instead?
8 |
9 | We recommend users of `square/js-jose` migrate to `panva/jose`:
10 |
11 | https://github.com/panva/jose
12 |
13 | This library is available in [NPM] and actively maintained. No support, security
14 | fixes or updates will be delivered to the Square repository.
15 |
16 | [NPM]: https://www.npmjs.com/package/jose
17 |
--------------------------------------------------------------------------------
/Todo.md:
--------------------------------------------------------------------------------
1 | TODO List
2 | =========
3 |
4 | * Improve README. There is some stigma attached to doing crypto in the browser.
5 | This library uses the browser's Crypto API, so it's not as bad as it sounds :)
6 | * The code currently only works on browsers which implement the Web Crypto API.
7 | We should expose a way to check if the current browser is supported or not.
8 | * Think about pros vs cons of implementing polyfills for older browsers. We will
9 | need to think about entropy, timing leaks, etc.
10 | * Build a unittest coverage map
11 | * Make it easier to build a subset of the library. Some people might only want
12 | to do encryption or encryption with a specific algorithm.
13 | * Rethink error handling. Do we really need JoseJWE.assert?
14 | * Add support for importing public keys as PEM files and validating signatures.
15 | * Improve handling of malformed JSON input (see https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-10.12)
16 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | // babel.config.js
2 | module.exports = {
3 | presets: [
4 | ['@babel/preset-env', { targets: { node: 'current' } }],
5 | ],
6 | };
7 |
--------------------------------------------------------------------------------
/dist/jose.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Jose=e():t.Jose=e()}(window,function(){return function(t){var e={};function r(n){if(e[n])return e[n].exports;var i=e[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)r.d(n,i,function(e){return t[e]}.bind(null,i));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=1)}([function(t,e,r){"use strict";r.r(e),r.d(e,"importPublicKey",function(){return u}),r.d(e,"importPrivateKey",function(){return s}),r.d(e,"importEcPublicKey",function(){return f}),r.d(e,"importEcPrivateKey",function(){return c}),r.d(e,"importRsaPublicKey",function(){return h}),r.d(e,"importRsaPrivateKey",function(){return y}),r.d(e,"isString",function(){return p}),r.d(e,"arrayish",function(){return l}),r.d(e,"convertRsaKey",function(){return g}),r.d(e,"arrayFromString",function(){return d}),r.d(e,"arrayFromUtf8String",function(){return v}),r.d(e,"stringFromArray",function(){return m}),r.d(e,"utf8StringFromArray",function(){return w}),r.d(e,"stripLeadingZeros",function(){return S}),r.d(e,"arrayFromInt32",function(){return b}),r.d(e,"arrayBufferConcat",function(){return A}),r.d(e,"sha256",function(){return E}),r.d(e,"isCryptoKey",function(){return _}),r.d(e,"Base64Url",function(){return P});var n=r(2),i=r(1);function o(t,e){for(var r=0;r0&&a.assert(!1,"convertRsaKey: Was expecting "+i.join()),void 0!==t.kty&&a.assert("RSA"===t.kty,"convertRsaKey: expecting rsaKey['kty'] to be 'RSA'"),n.kty="RSA";try{a.getSignConfig(t.alg),r=t.alg}catch(e){try{a.getCryptoConfig(t.alg),r=t.alg}catch(t){a.assert(r,"convertRsaKey: expecting rsaKey['alg'] to have a valid value")}}n.alg=r;for(var o=function(t){return parseInt(t,16)},u=0;u0,o=this.keyEncryption.id;return i.Jose.crypto.subtle.unwrapKey("raw",t,e,o,r.id,n,r.dec_op)}},{key:"getCekWorkaround",value:function(t){var e=t.specific_cekBytes;if(e){if(16===e)return{id:{name:"AES-CBC",length:128},enc_op:["encrypt"],dec_op:["decrypt"]};if(32===e)return{id:{name:"AES-CBC",length:256},enc_op:["encrypt"],dec_op:["decrypt"]};if(64===e)return{id:{name:"HMAC",hash:{name:"SHA-256"}},enc_op:["sign"],dec_op:["verify"]};if(128===e)return{id:{name:"HMAC",hash:{name:"SHA-384"}},enc_op:["sign"],dec_op:["verify"]};this.assert(!1,"getCekWorkaround: invalid len")}return{id:t.id,enc_op:["encrypt"],dec_op:["decrypt"]}}},{key:"encrypt",value:function(t,e,r,n){var o=this,a=this.content_encryption;if(t.length!==a.iv_bytes)return Promise.reject(Error("invalid IV length"));if(a.auth.aead){var u=a.auth.tagBytes,s={name:a.id.name,iv:t,additionalData:e,tagLength:8*u};return r.then(function(t){return i.Jose.crypto.subtle.encrypt(s,t,n).then(function(t){var e=t.byteLength-u;return{cipher:t.slice(0,e),tag:t.slice(e)}})})}var f=this.splitKey(a,r,["encrypt"]),c=f[0],h=f[1].then(function(e){var r={name:a.id.name,iv:t};return i.Jose.crypto.subtle.encrypt(r,e,n)}),y=h.then(function(r){return o.truncatedMac(a,c,e,t,r)});return Promise.all([h,y]).then(function(t){return{cipher:t[0],tag:t[1]}})}},{key:"compare",value:function(t,e,r,n){return this.assert(r instanceof Uint8Array,"compare: invalid input"),this.assert(n instanceof Uint8Array,"compare: invalid input"),e.then(function(e){var o=i.Jose.crypto.subtle.sign(t.auth.id,e,r),a=i.Jose.crypto.subtle.sign(t.auth.id,e,n);return Promise.all([o,a]).then(function(t){var e=new Uint8Array(t[0]),r=new Uint8Array(t[1]);if(e.length!==r.length)throw new Error("compare failed");for(var n=0;n0))throw new Error("No recipients defined. At least one is required to verify the JWS.");if(t.waiting_kid)throw new Error("still generating key IDs");return e.forEach(function(e){var a=e.protected.kid;i&&(r[a]=i(a)),o.push(t.cryptographer.verify(e.aad,t.payload,e.signature,r[a],a).then(function(e){return e.verified&&(e.payload=(new n.Base64Url).decode(t.payload)),e}))}),Promise.all(o)}}])&&o(e.prototype,r),a&&o(e,a),t}()},function(t,e,r){"use strict";(function(t){
2 | /*!
3 | * The buffer module from node.js, for the browser.
4 | *
5 | * @author Feross Aboukhadijeh
6 | * @license MIT
7 | */
8 | var n=r(9),i=r(10),o=r(11);function a(){return s.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function u(t,e){if(a()=a())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+a().toString(16)+" bytes");return 0|t}function l(t,e){if(s.isBuffer(t))return t.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(t)||t instanceof ArrayBuffer))return t.byteLength;"string"!=typeof t&&(t=""+t);var r=t.length;if(0===r)return 0;for(var n=!1;;)switch(e){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":case void 0:return N(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return L(t).length;default:if(n)return N(t).length;e=(""+e).toLowerCase(),n=!0}}function g(t,e,r){var n=!1;if((void 0===e||e<0)&&(e=0),e>this.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(e>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return B(this,e,r);case"utf8":case"utf-8":return C(this,e,r);case"ascii":return R(this,e,r);case"latin1":case"binary":return U(this,e,r);case"base64":return P(this,e,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return O(this,e,r);default:if(n)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),n=!0}}function d(t,e,r){var n=t[e];t[e]=t[r],t[r]=n}function v(t,e,r,n,i){if(0===t.length)return-1;if("string"==typeof r?(n=r,r=0):r>2147483647?r=2147483647:r<-2147483648&&(r=-2147483648),r=+r,isNaN(r)&&(r=i?0:t.length-1),r<0&&(r=t.length+r),r>=t.length){if(i)return-1;r=t.length-1}else if(r<0){if(!i)return-1;r=0}if("string"==typeof e&&(e=s.from(e,n)),s.isBuffer(e))return 0===e.length?-1:m(t,e,r,n,i);if("number"==typeof e)return e&=255,s.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(t,e,r):Uint8Array.prototype.lastIndexOf.call(t,e,r):m(t,[e],r,n,i);throw new TypeError("val must be string, number or Buffer")}function m(t,e,r,n,i){var o,a=1,u=t.length,s=e.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(t.length<2||e.length<2)return-1;a=2,u/=2,s/=2,r/=2}function f(t,e){return 1===a?t[e]:t.readUInt16BE(e*a)}if(i){var c=-1;for(o=r;ou&&(r=u-s),o=r;o>=0;o--){for(var h=!0,y=0;yi&&(n=i):n=i;var o=e.length;if(o%2!=0)throw new TypeError("Invalid hex string");n>o/2&&(n=o/2);for(var a=0;a>8,i=r%256,o.push(i),o.push(n);return o}(e,t.length-r),t,r,n)}function P(t,e,r){return 0===e&&r===t.length?n.fromByteArray(t):n.fromByteArray(t.slice(e,r))}function C(t,e,r){r=Math.min(t.length,r);for(var n=[],i=e;i239?4:f>223?3:f>191?2:1;if(i+h<=r)switch(h){case 1:f<128&&(c=f);break;case 2:128==(192&(o=t[i+1]))&&(s=(31&f)<<6|63&o)>127&&(c=s);break;case 3:o=t[i+1],a=t[i+2],128==(192&o)&&128==(192&a)&&(s=(15&f)<<12|(63&o)<<6|63&a)>2047&&(s<55296||s>57343)&&(c=s);break;case 4:o=t[i+1],a=t[i+2],u=t[i+3],128==(192&o)&&128==(192&a)&&128==(192&u)&&(s=(15&f)<<18|(63&o)<<12|(63&a)<<6|63&u)>65535&&s<1114112&&(c=s)}null===c?(c=65533,h=1):c>65535&&(c-=65536,n.push(c>>>10&1023|55296),c=56320|1023&c),n.push(c),i+=h}return function(t){var e=t.length;if(e<=k)return String.fromCharCode.apply(String,t);var r="",n=0;for(;n0&&(t=this.toString("hex",0,r).match(/.{2}/g).join(" "),this.length>r&&(t+=" ... ")),""},s.prototype.compare=function(t,e,r,n,i){if(!s.isBuffer(t))throw new TypeError("Argument must be a Buffer");if(void 0===e&&(e=0),void 0===r&&(r=t?t.length:0),void 0===n&&(n=0),void 0===i&&(i=this.length),e<0||r>t.length||n<0||i>this.length)throw new RangeError("out of range index");if(n>=i&&e>=r)return 0;if(n>=i)return-1;if(e>=r)return 1;if(this===t)return 0;for(var o=(i>>>=0)-(n>>>=0),a=(r>>>=0)-(e>>>=0),u=Math.min(o,a),f=this.slice(n,i),c=t.slice(e,r),h=0;hi)&&(r=i),t.length>0&&(r<0||e<0)||e>this.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var o=!1;;)switch(n){case"hex":return w(this,t,e,r);case"utf8":case"utf-8":return S(this,t,e,r);case"ascii":return b(this,t,e,r);case"latin1":case"binary":return A(this,t,e,r);case"base64":return E(this,t,e,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return _(this,t,e,r);default:if(o)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),o=!0}},s.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var k=4096;function R(t,e,r){var n="";r=Math.min(t.length,r);for(var i=e;in)&&(r=n);for(var i="",o=e;or)throw new RangeError("Trying to access beyond buffer length")}function K(t,e,r,n,i,o){if(!s.isBuffer(t))throw new TypeError('"buffer" argument must be a Buffer instance');if(e>i||et.length)throw new RangeError("Index out of range")}function T(t,e,r,n){e<0&&(e=65535+e+1);for(var i=0,o=Math.min(t.length-r,2);i>>8*(n?i:1-i)}function I(t,e,r,n){e<0&&(e=4294967295+e+1);for(var i=0,o=Math.min(t.length-r,4);i>>8*(n?i:3-i)&255}function J(t,e,r,n,i,o){if(r+n>t.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function M(t,e,r,n,o){return o||J(t,0,r,4),i.write(t,e,r,n,23,4),r+4}function x(t,e,r,n,o){return o||J(t,0,r,8),i.write(t,e,r,n,52,8),r+8}s.prototype.slice=function(t,e){var r,n=this.length;if((t=~~t)<0?(t+=n)<0&&(t=0):t>n&&(t=n),(e=void 0===e?n:~~e)<0?(e+=n)<0&&(e=0):e>n&&(e=n),e0&&(i*=256);)n+=this[t+--e]*i;return n},s.prototype.readUInt8=function(t,e){return e||j(t,1,this.length),this[t]},s.prototype.readUInt16LE=function(t,e){return e||j(t,2,this.length),this[t]|this[t+1]<<8},s.prototype.readUInt16BE=function(t,e){return e||j(t,2,this.length),this[t]<<8|this[t+1]},s.prototype.readUInt32LE=function(t,e){return e||j(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},s.prototype.readUInt32BE=function(t,e){return e||j(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},s.prototype.readIntLE=function(t,e,r){t|=0,e|=0,r||j(t,e,this.length);for(var n=this[t],i=1,o=0;++o=(i*=128)&&(n-=Math.pow(2,8*e)),n},s.prototype.readIntBE=function(t,e,r){t|=0,e|=0,r||j(t,e,this.length);for(var n=e,i=1,o=this[t+--n];n>0&&(i*=256);)o+=this[t+--n]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*e)),o},s.prototype.readInt8=function(t,e){return e||j(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},s.prototype.readInt16LE=function(t,e){e||j(t,2,this.length);var r=this[t]|this[t+1]<<8;return 32768&r?4294901760|r:r},s.prototype.readInt16BE=function(t,e){e||j(t,2,this.length);var r=this[t+1]|this[t]<<8;return 32768&r?4294901760|r:r},s.prototype.readInt32LE=function(t,e){return e||j(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},s.prototype.readInt32BE=function(t,e){return e||j(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},s.prototype.readFloatLE=function(t,e){return e||j(t,4,this.length),i.read(this,t,!0,23,4)},s.prototype.readFloatBE=function(t,e){return e||j(t,4,this.length),i.read(this,t,!1,23,4)},s.prototype.readDoubleLE=function(t,e){return e||j(t,8,this.length),i.read(this,t,!0,52,8)},s.prototype.readDoubleBE=function(t,e){return e||j(t,8,this.length),i.read(this,t,!1,52,8)},s.prototype.writeUIntLE=function(t,e,r,n){(t=+t,e|=0,r|=0,n)||K(this,t,e,r,Math.pow(2,8*r)-1,0);var i=1,o=0;for(this[e]=255&t;++o=0&&(o*=256);)this[e+i]=t/o&255;return e+r},s.prototype.writeUInt8=function(t,e,r){return t=+t,e|=0,r||K(this,t,e,1,255,0),s.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),this[e]=255&t,e+1},s.prototype.writeUInt16LE=function(t,e,r){return t=+t,e|=0,r||K(this,t,e,2,65535,0),s.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):T(this,t,e,!0),e+2},s.prototype.writeUInt16BE=function(t,e,r){return t=+t,e|=0,r||K(this,t,e,2,65535,0),s.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):T(this,t,e,!1),e+2},s.prototype.writeUInt32LE=function(t,e,r){return t=+t,e|=0,r||K(this,t,e,4,4294967295,0),s.TYPED_ARRAY_SUPPORT?(this[e+3]=t>>>24,this[e+2]=t>>>16,this[e+1]=t>>>8,this[e]=255&t):I(this,t,e,!0),e+4},s.prototype.writeUInt32BE=function(t,e,r){return t=+t,e|=0,r||K(this,t,e,4,4294967295,0),s.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):I(this,t,e,!1),e+4},s.prototype.writeIntLE=function(t,e,r,n){if(t=+t,e|=0,!n){var i=Math.pow(2,8*r-1);K(this,t,e,r,i-1,-i)}var o=0,a=1,u=0;for(this[e]=255&t;++o>0)-u&255;return e+r},s.prototype.writeIntBE=function(t,e,r,n){if(t=+t,e|=0,!n){var i=Math.pow(2,8*r-1);K(this,t,e,r,i-1,-i)}var o=r-1,a=1,u=0;for(this[e+o]=255&t;--o>=0&&(a*=256);)t<0&&0===u&&0!==this[e+o+1]&&(u=1),this[e+o]=(t/a>>0)-u&255;return e+r},s.prototype.writeInt8=function(t,e,r){return t=+t,e|=0,r||K(this,t,e,1,127,-128),s.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),t<0&&(t=255+t+1),this[e]=255&t,e+1},s.prototype.writeInt16LE=function(t,e,r){return t=+t,e|=0,r||K(this,t,e,2,32767,-32768),s.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):T(this,t,e,!0),e+2},s.prototype.writeInt16BE=function(t,e,r){return t=+t,e|=0,r||K(this,t,e,2,32767,-32768),s.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):T(this,t,e,!1),e+2},s.prototype.writeInt32LE=function(t,e,r){return t=+t,e|=0,r||K(this,t,e,4,2147483647,-2147483648),s.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8,this[e+2]=t>>>16,this[e+3]=t>>>24):I(this,t,e,!0),e+4},s.prototype.writeInt32BE=function(t,e,r){return t=+t,e|=0,r||K(this,t,e,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),s.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):I(this,t,e,!1),e+4},s.prototype.writeFloatLE=function(t,e,r){return M(this,t,e,!0,r)},s.prototype.writeFloatBE=function(t,e,r){return M(this,t,e,!1,r)},s.prototype.writeDoubleLE=function(t,e,r){return x(this,t,e,!0,r)},s.prototype.writeDoubleBE=function(t,e,r){return x(this,t,e,!1,r)},s.prototype.copy=function(t,e,r,n){if(r||(r=0),n||0===n||(n=this.length),e>=t.length&&(e=t.length),e||(e=0),n>0&&n=this.length)throw new RangeError("sourceStart out of bounds");if(n<0)throw new RangeError("sourceEnd out of bounds");n>this.length&&(n=this.length),t.length-e=0;--i)t[i+e]=this[i+r];else if(o<1e3||!s.TYPED_ARRAY_SUPPORT)for(i=0;i>>=0,r=void 0===r?this.length:r>>>0,t||(t=0),"number"==typeof t)for(o=e;o55295&&r<57344){if(!i){if(r>56319){(e-=3)>-1&&o.push(239,191,189);continue}if(a+1===n){(e-=3)>-1&&o.push(239,191,189);continue}i=r;continue}if(r<56320){(e-=3)>-1&&o.push(239,191,189),i=r;continue}r=65536+(i-55296<<10|r-56320)}else i&&(e-=3)>-1&&o.push(239,191,189);if(i=null,r<128){if((e-=1)<0)break;o.push(r)}else if(r<2048){if((e-=2)<0)break;o.push(r>>6|192,63&r|128)}else if(r<65536){if((e-=3)<0)break;o.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((e-=4)<0)break;o.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return o}function L(t){return n.toByteArray(function(t){if((t=function(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}(t).replace(Y,"")).length<2)return"";for(;t.length%4!=0;)t+="=";return t}(t))}function D(t,e,r,n){for(var i=0;i=e.length||i>=t.length);++i)e[i+r]=t[i];return i}}).call(this,r(3))},function(t,e,r){"use strict";e.byteLength=function(t){var e=f(t),r=e[0],n=e[1];return 3*(r+n)/4-n},e.toByteArray=function(t){for(var e,r=f(t),n=r[0],a=r[1],u=new o(function(t,e,r){return 3*(e+r)/4-r}(0,n,a)),s=0,c=a>0?n-4:n,h=0;h>16&255,u[s++]=e>>8&255,u[s++]=255&e;2===a&&(e=i[t.charCodeAt(h)]<<2|i[t.charCodeAt(h+1)]>>4,u[s++]=255&e);1===a&&(e=i[t.charCodeAt(h)]<<10|i[t.charCodeAt(h+1)]<<4|i[t.charCodeAt(h+2)]>>2,u[s++]=e>>8&255,u[s++]=255&e);return u},e.fromByteArray=function(t){for(var e,r=t.length,i=r%3,o=[],a=0,u=r-i;au?u:a+16383));1===i?(e=t[r-1],o.push(n[e>>2]+n[e<<4&63]+"==")):2===i&&(e=(t[r-2]<<8)+t[r-1],o.push(n[e>>10]+n[e>>4&63]+n[e<<2&63]+"="));return o.join("")};for(var n=[],i=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",u=0,s=a.length;u0)throw new Error("Invalid string. Length must be a multiple of 4");var r=t.indexOf("=");return-1===r&&(r=e),[r,r===e?0:4-r%4]}function c(t,e,r){for(var i,o,a=[],u=e;u>18&63]+n[o>>12&63]+n[o>>6&63]+n[63&o]);return a.join("")}i["-".charCodeAt(0)]=62,i["_".charCodeAt(0)]=63},function(t,e){e.read=function(t,e,r,n,i){var o,a,u=8*i-n-1,s=(1<>1,c=-7,h=r?i-1:0,y=r?-1:1,p=t[e+h];for(h+=y,o=p&(1<<-c)-1,p>>=-c,c+=u;c>0;o=256*o+t[e+h],h+=y,c-=8);for(a=o&(1<<-c)-1,o>>=-c,c+=n;c>0;a=256*a+t[e+h],h+=y,c-=8);if(0===o)o=1-f;else{if(o===s)return a?NaN:1/0*(p?-1:1);a+=Math.pow(2,n),o-=f}return(p?-1:1)*a*Math.pow(2,o-n)},e.write=function(t,e,r,n,i,o){var a,u,s,f=8*o-i-1,c=(1<>1,y=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,p=n?0:o-1,l=n?1:-1,g=e<0||0===e&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(u=isNaN(e)?1:0,a=c):(a=Math.floor(Math.log(e)/Math.LN2),e*(s=Math.pow(2,-a))<1&&(a--,s*=2),(e+=a+h>=1?y/s:y*Math.pow(2,1-h))*s>=2&&(a++,s/=2),a+h>=c?(u=0,a=c):a+h>=1?(u=(e*s-1)*Math.pow(2,i),a+=h):(u=e*Math.pow(2,h-1)*Math.pow(2,i),a=0));i>=8;t[r+p]=255&u,p+=l,u/=256,i-=8);for(a=a<0;t[r+p]=255&a,p+=l,a/=256,f-=8);t[r+p-l]|=128*g}},function(t,e){var r={}.toString;t.exports=Array.isArray||function(t){return"[object Array]"==r.call(t)}}])});
--------------------------------------------------------------------------------
/examples/jose-jwe-example1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | plain text: hello world
4 | cipher text:
5 | error:
6 |
7 |
8 |
58 |
59 |
--------------------------------------------------------------------------------
/examples/jose-jwe-example2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | plain text: hello world, ⛄
4 | cipher text:
5 | error:
6 |
7 |
8 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/examples/jose-jws-ecdsa-example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | payload: hello world, ⛄
4 |
5 | signature:
6 |
7 |
8 | error:
9 |
10 |
11 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/examples/jose-jws-rsa-example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | payload: hello world, ⛄
4 |
5 | signature:
6 |
7 |
8 | error:
9 |
10 |
11 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/jose-jwe-jws.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for jose-jwe-jws
2 | // Project: jose-jwe-jws
3 | // Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
4 |
5 | interface IEncryptionResult {
6 | cipher: ArrayBuffer;
7 | tag: ArrayBuffer;
8 | }
9 |
10 | interface IVerificationResult {
11 | kid: string;
12 | verified: boolean;
13 | payload?: string;
14 | }
15 |
16 | type RsaSsaSignAlgorithm = "RS256" | "RS384" | "RS512";
17 | type RsaPssSignAlgorithm = "PS256" | "PS384" | "PS512";
18 | type HmacSignAlgorithm = "HS256" | "HS384" | "HS512";
19 | type EcdsaSignAlgorithm = "ES256" | "ES384" | "ES512";
20 | type SignAlgorithm = RsaSsaSignAlgorithm | RsaPssSignAlgorithm | HmacSignAlgorithm | EcdsaSignAlgorithm;
21 |
22 | type RsaOaepEncryptAlgorithm = "RSA-OAEP" | "RSA-OAEP-256";
23 | type AesKwEncryptAlgorithm = "A128KW" | "A256KW";
24 | type AesCbcEncryptAlgorithm = "A128CBC-HS256" | "A256CBC-HS512";
25 | type AesGcmEncryptAlgorithm = "A128GCM" | "A256GCM";
26 | type EncryptAlgorithm = RsaOaepEncryptAlgorithm | AesKwEncryptAlgorithm | AesCbcEncryptAlgorithm | AesGcmEncryptAlgorithm;
27 |
28 | type JoseAlgorithm = EncryptAlgorithm | SignAlgorithm;
29 |
30 | interface IWebCryptographer {
31 | new(): IWebCryptographer;
32 | getContentEncryptionAlgorithm(): EncryptAlgorithm;
33 | setContentEncryptionAlgorithm(algorithm: EncryptAlgorithm): void;
34 | getKeyEncryptionAlgorithm(): EncryptAlgorithm;
35 | setKeyEncryptionAlgorithm(algorithm: EncryptAlgorithm): void;
36 | getContentSignAlgorithm(): SignAlgorithm;
37 | setContentSignAlgorithm(algorithm: SignAlgorithm): void;
38 | createIV(): ArrayBufferView;
39 | createCek(): PromiseLike;
40 | wrapCek(): PromiseLike;
41 | unwrapCek(): PromiseLike;
42 | encrypt(iv: Uint8Array, aad: Uint8Array, cek: CryptoKey | PromiseLike, plaintext: Uint8Array): PromiseLike;
43 | decrypt(cek: CryptoKey | PromiseLike, aad: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array, tag: Uint8Array): PromiseLike;
44 | sign(aad: Object, payload: string | Object, key: CryptoKey | PromiseLike): PromiseLike;
45 | verify(aad: string, payload: string, signature: Uint8Array, key: CryptoKey | PromiseLike, kid: string): PromiseLike;
46 | }
47 |
48 | interface IJsonWebKey {
49 | kty: string;
50 | alg?: string;
51 | kid?: string;
52 | use?: string;
53 | key_ops?: string[];
54 | x5u?: string;
55 | x5c?: string[];
56 | x5t?: string;
57 | "x5t#S256"?: string;
58 | }
59 |
60 | interface JWKRSA extends IJsonWebKey {
61 | n?: string;
62 | e?: string;
63 | d?: string;
64 | p?: string;
65 | q?: string;
66 | dp?: string;
67 | dq?: string;
68 | qi?: string;
69 | oth?: {
70 | r: string;
71 | d: string;
72 | t: string;
73 | }[];
74 | }
75 |
76 | interface IJwkSet {
77 | keys: IJsonWebKey[];
78 | }
79 |
80 | interface IHeaders {
81 | alg: string;
82 | jku?: string;
83 | jwk?: IJsonWebKey;
84 | kid?: string;
85 | x5u?: string;
86 | x5c?: string[];
87 | x5t?: string;
88 | 'x5t#S256'?: string;
89 | typ?: string;
90 | cty?: string;
91 | crit?: string[];
92 | }
93 |
94 | interface IUtils {
95 | importRsaPublicKey(rsa_key: JWKRSA, alg: JoseAlgorithm): PromiseLike;
96 | importRsaPrivateKey(rsa_key: JWKRSA, alg: JoseAlgorithm): PromiseLike;
97 | importPublicKey(key: IJsonWebKey, alg: JoseAlgorithm): PromiseLike;
98 | }
99 |
100 | interface IJose {
101 | Utils: IUtils;
102 | JoseJWE: IJoseJWE;
103 | JoseJWS: IJoseJWS
104 | caniuse(): boolean;
105 | WebCryptographer: IWebCryptographer;
106 | }
107 |
108 | interface ISignedJws {
109 | header: string;
110 | protected: string;
111 | payload: string;
112 | signature: string;
113 | JsonSerialize(): Object;
114 | CompactSerialize(): string;
115 | }
116 |
117 | interface ISigner {
118 | new(cryptographer: IWebCryptographer): ISigner;
119 | addSigner(pk: CryptoKey, kid: string, aad?: Object, header?: Object): PromiseLike;
120 | sign(payload: any, aad?: Object, header?: Object): PromiseLike;
121 | }
122 |
123 | interface IVerifier {
124 | new(cryptographer: IWebCryptographer, msg: string,
125 | keyfinder?: (kid: string) => PromiseLike): IVerifier;
126 | addRecipient(pk: CryptoKey | string | JWKRSA, kid?: string, alg?: SignAlgorithm): PromiseLike;
127 | verify(): PromiseLike;
128 | }
129 |
130 | interface IJoseJWS {
131 | Signer: ISigner;
132 | Verifier: IVerifier;
133 | }
134 |
135 | interface IEncrypter {
136 | new(cryptographer: IWebCryptographer, pubkey: CryptoKey | PromiseLike): IEncrypter;
137 | addHeader(k: string, v: string): void;
138 | encrypt(plaintext: string): PromiseLike;
139 | }
140 |
141 | interface IDecrypter {
142 | new(cryptographer: IWebCryptographer, key: CryptoKey | PromiseLike): IDecrypter;
143 | decrypt(ciphertext: string): PromiseLike;
144 | }
145 |
146 | interface IJoseJWE {
147 | Encrypter: IEncrypter;
148 | Decrypter: IDecrypter;
149 | }
150 |
151 | interface Window {
152 | Jose: IJose;
153 | }
154 |
155 | declare function setCrypto(cryptoProvider: Crypto): void;
156 | declare const Jose: IJose;
157 | declare var crypto: Crypto;
158 |
159 | declare module "jose-jwe-jws" {
160 | export function setCrypto(cryptoProvider: Crypto): void;
161 | export const Jose: IJose;
162 | export var crypto: Crypto;
163 | }
164 |
--------------------------------------------------------------------------------
/lib/jose-core.js:
--------------------------------------------------------------------------------
1 | /* -
2 | * Copyright 2014 Square Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import * as JoseUtils from './jose-utils';
17 | import { Encrypter } from './jose-jwe-encrypt';
18 | import { Decrypter } from './jose-jwe-decrypt';
19 | import { Signer } from './jose-jws-sign';
20 | import { Verifier } from './jose-jws-verify';
21 | import { WebCryptographer } from './jose-jwe-webcryptographer';
22 |
23 | export var crypto;
24 | /**
25 | * Javascript Object Signing and Encryption library.
26 | *
27 | * @author Alok Menghrajani
28 | */
29 |
30 | export var Utils = JoseUtils;
31 |
32 | /**
33 | * Initializes a JoseJWE object.
34 | */
35 | const JoseJWE = {
36 | Encrypter,
37 | Decrypter
38 | };
39 |
40 | /**
41 | * Initializes a JoseJWS object.
42 | */
43 | const JoseJWS = {
44 | Signer,
45 | Verifier
46 | };
47 |
48 | /**
49 | * Set crypto provider to use (window.crypto, node-webcrypto-ossl, node-webcrypto-pkcs11 etc.).
50 | */
51 | export var setCrypto = function (cp) {
52 | crypto = cp;
53 | };
54 |
55 | /**
56 | * Default to the global "crypto" variable
57 | */
58 | if (typeof window !== 'undefined') {
59 | if (typeof window.crypto !== 'undefined') {
60 | setCrypto(window.crypto);
61 | if (!crypto.subtle) {
62 | crypto.subtle = crypto.webkitSubtle;
63 | }
64 | }
65 | }
66 |
67 | const Jose = { JoseJWS, JoseJWE, WebCryptographer, crypto, Utils };
68 |
69 | export default { Jose, WebCryptographer };
70 | export { Jose, JoseJWE, JoseJWS, WebCryptographer };
71 |
72 | /**
73 | * Use Node versions of atob, btoa functions outside the browser
74 | */
75 | if (typeof atob !== 'function') {
76 | // eslint-disable-next-line no-global-assign
77 | atob = (str) => {
78 | return Buffer.from(str, 'base64').toString('binary');
79 | };
80 | }
81 |
82 | if (typeof btoa !== 'function') {
83 | // eslint-disable-next-line no-global-assign
84 | btoa = (str) => {
85 | var buffer;
86 | if (str instanceof Buffer) {
87 | buffer = str;
88 | } else {
89 | buffer = Buffer.from(str.toString(), 'binary');
90 | }
91 | return buffer.toString('base64');
92 | };
93 | }
94 |
95 | /**
96 | * Checks if we have all the required APIs.
97 | *
98 | * It might make sense to take a Cryptographer and delegate some of the checks
99 | * to the cryptographer. I however wanted to keep things simple, so I put all
100 | * the checks here for now.
101 | *
102 | * This list is generated manually and needs to be kept up-to-date.
103 | *
104 | * Casual testing shows that:
105 | * - things work in Chrome 40.0.2214.115
106 | * - things work in Firefox 35.0.1
107 | * - Safari 7.1.3 doesn't support JWK keys.
108 | * - Internet Explorer doesn't support Promises.
109 | *
110 | * Note: We don't check if the browser supports specific crypto operations.
111 | * I.e. it's possible for this function to return true, but encryption or
112 | * decryption to subsequently fail because the browser does not support a
113 | * given encryption, decryption, key wrapping, key unwrapping or hmac
114 | * operation.
115 | *
116 | * @return bool
117 | */
118 | export const caniuse = () => {
119 | var r = true;
120 |
121 | // Promises/A+ (https://promisesaplus.com/)
122 | r = r && (typeof Promise === 'function');
123 | r = r && (typeof Promise.reject === 'function');
124 | r = r && (typeof Promise.prototype.then === 'function');
125 | r = r && (typeof Promise.all === 'function');
126 |
127 | const globalObject = window || global;
128 |
129 | // Crypto (http://www.w3.org/TR/WebCryptoAPI/)
130 | r = r && (typeof globalObject.crypto === 'object');
131 | r = r && (typeof globalObject.crypto.subtle === 'object');
132 | r = r && (typeof globalObject.crypto.getRandomValues === 'function');
133 | r = r && (typeof globalObject.crypto.subtle.importKey === 'function');
134 | r = r && (typeof globalObject.crypto.subtle.generateKey === 'function');
135 | r = r && (typeof globalObject.crypto.subtle.exportKey === 'function');
136 | r = r && (typeof globalObject.crypto.subtle.wrapKey === 'function');
137 | r = r && (typeof globalObject.crypto.subtle.unwrapKey === 'function');
138 | r = r && (typeof globalObject.crypto.subtle.encrypt === 'function');
139 | r = r && (typeof globalObject.crypto.subtle.decrypt === 'function');
140 | r = r && (typeof globalObject.crypto.subtle.sign === 'function');
141 |
142 | // ArrayBuffer (http://people.mozilla.org/~jorendorff/es6-draft.html#sec-arraybuffer-constructor)
143 | r = r && (typeof ArrayBuffer === 'function');
144 | r = r && (typeof Uint8Array === 'function' || typeof Uint8Array === 'object'); // Safari uses "object"
145 | r = r && (typeof Uint32Array === 'function' || typeof Uint32Array === 'object'); // Safari uses "object"
146 | // skipping Uint32Array.prototype.buffer because https://people.mozilla.org/~jorendorff/es6-draft.html#sec-properties-of-the-%typedarrayprototype%-object
147 |
148 | // JSON (http://www.ecma-international.org/ecma-262/5.1/#sec-15.12.3)
149 | r = r && (typeof JSON === 'object');
150 | r = r && (typeof JSON.parse === 'function');
151 | r = r && (typeof JSON.stringify === 'function');
152 |
153 | // Base64 (http://www.w3.org/TR/html5/webappapis.html#dom-windowbase64-atob)
154 | r = r && (typeof atob === 'function');
155 | r = r && (typeof btoa === 'function');
156 |
157 | // skipping Array functions (map, join, push, length, etc.)
158 | // skipping String functions (split, charCodeAt, fromCharCode, replace, etc.)
159 | // skipping regexp.test and parseInt
160 |
161 | return r;
162 | };
163 |
--------------------------------------------------------------------------------
/lib/jose-core.test.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/no-duplicates
2 | import DefaultJose, { Jose } from './jose-core.js';
3 | // eslint-disable-next-line import/no-duplicates
4 | import * as AsJose from './jose-core.js';
5 |
6 | it('ensure imports work', () => {
7 | expect(Jose).not.toBeUndefined();
8 | expect(Jose.JoseJWS).not.toBeUndefined();
9 | expect(Jose.JoseJWE).not.toBeUndefined();
10 | expect(Jose.WebCryptographer).not.toBeUndefined();
11 |
12 | console.log(Jose);
13 | expect(DefaultJose).not.toBeUndefined();
14 | expect(DefaultJose.WebCryptographer).not.toBeUndefined();
15 | expect(DefaultJose.Jose).not.toBeUndefined();
16 | expect(DefaultJose.Jose.JoseJWS).not.toBeUndefined();
17 | expect(DefaultJose.Jose.JoseJWE).not.toBeUndefined();
18 | expect(DefaultJose.Jose.WebCryptographer).not.toBeUndefined();
19 |
20 | expect(AsJose).not.toBeUndefined();
21 | expect(AsJose.WebCryptographer).not.toBeUndefined();
22 | expect(AsJose.Jose).not.toBeUndefined();
23 | expect(AsJose.Jose.JoseJWS).not.toBeUndefined();
24 | expect(AsJose.Jose.JoseJWE).not.toBeUndefined();
25 | expect(AsJose.Jose.WebCryptographer).not.toBeUndefined();
26 | });
27 |
--------------------------------------------------------------------------------
/lib/jose-jwe-decrypt.js:
--------------------------------------------------------------------------------
1 | /* -
2 | * Copyright 2014 Square Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as Utils from './jose-utils';
18 |
19 | /**
20 | * Handles decryption.
21 | *
22 | * @param cryptographer an instance of WebCryptographer (or equivalent). Keep
23 | * in mind that decryption mutates the cryptographer.
24 | * @param keyPromise Promise, either RSA or shared key
25 | */
26 | export class Decrypter {
27 | constructor (cryptographer, keyPromise) {
28 | this.cryptographer = cryptographer;
29 | this.keyPromise = keyPromise;
30 | this.headers = {};
31 | this.base64UrlEncoder = new Utils.Base64Url();
32 | }
33 |
34 | getHeaders () {
35 | return this.headers;
36 | }
37 |
38 | /**
39 | * Performs decryption.
40 | *
41 | * @param cipherText String
42 | * @return Promise
43 | */
44 | decrypt (cipherText) {
45 | // Split cipherText in 5 parts
46 | var parts = cipherText.split('.');
47 | if (parts.length !== 5) {
48 | return Promise.reject(Error('decrypt: invalid input'));
49 | }
50 |
51 | // part 1: header
52 | this.headers = JSON.parse(this.base64UrlEncoder.decode(parts[0]));
53 | if (!this.headers.alg) {
54 | return Promise.reject(Error('decrypt: missing alg'));
55 | }
56 | if (!this.headers.enc) {
57 | return Promise.reject(Error('decrypt: missing enc'));
58 | }
59 | this.cryptographer.setKeyEncryptionAlgorithm(this.headers.alg);
60 | this.cryptographer.setContentEncryptionAlgorithm(this.headers.enc);
61 |
62 | if (this.headers.crit) {
63 | // We don't support the crit header
64 | return Promise.reject(Error('decrypt: crit is not supported'));
65 | }
66 |
67 | var cekPromise;
68 |
69 | if (this.headers.alg === 'dir') {
70 | // with direct mode, we already have the cek
71 | cekPromise = Promise.resolve(this.keyPromise);
72 | } else {
73 | // part 2: decrypt the CEK
74 | // In some modes (e.g. RSA-PKCS1v1.5), you must take precautions to prevent
75 | // chosen-ciphertext attacks as described in RFC 3218, "Preventing
76 | // the Million Message Attack on Cryptographic Message Syntax". We currently
77 | // only support RSA-OAEP, so we don't generate a key if unwrapping fails.
78 | var encryptedCek = this.base64UrlEncoder.decodeArray(parts[1]);
79 | cekPromise = this.keyPromise.then(function (key) {
80 | return this.cryptographer.unwrapCek(encryptedCek, key);
81 | }.bind(this));
82 | }
83 |
84 | // part 3: decrypt the cipher text
85 | var plainTextPromise = this.cryptographer.decrypt(
86 | cekPromise,
87 | Utils.arrayFromString(parts[0]),
88 | this.base64UrlEncoder.decodeArray(parts[2]),
89 | this.base64UrlEncoder.decodeArray(parts[3]),
90 | this.base64UrlEncoder.decodeArray(parts[4]));
91 |
92 | return plainTextPromise.then(Utils.utf8StringFromArray);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/lib/jose-jwe-encrypt.js:
--------------------------------------------------------------------------------
1 | /* -
2 | * Copyright 2014 Square Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import * as Utils from './jose-utils';
17 |
18 | /**
19 | * Handles encryption.
20 | *
21 | * @param cryptographer an instance of WebCryptographer (or equivalent).
22 | * @param keyPromise Promise, either RSA or shared key
23 | */
24 | export class Encrypter {
25 | constructor (cryptographer, keyPromise) {
26 | this.cryptographer = cryptographer;
27 | this.keyPromise = keyPromise;
28 | this.userHeaders = {};
29 | }
30 |
31 | /**
32 | * Adds a key/value pair which will be included in the header.
33 | *
34 | * The data lives in plaintext (an attacker can read the header) but is tamper
35 | * proof (an attacker cannot modify the header).
36 | *
37 | * Note: some headers have semantic implications. E.g. if you set the "zip"
38 | * header, you are responsible for properly compressing plainText before
39 | * calling encrypt().
40 | *
41 | * @param k String
42 | * @param v String
43 | */
44 | addHeader (k, v) {
45 | this.userHeaders[k] = v;
46 | }
47 |
48 | /**
49 | * Performs encryption.
50 | *
51 | * @param plainText utf-8 string
52 | * @return Promise
53 | */
54 | encrypt (plainText) {
55 | /**
56 | * Encrypts plainText with CEK.
57 | *
58 | * @param cekPromise Promise
59 | * @param plainText string
60 | * @return Promise
61 | */
62 | var encryptPlainText = function (cekPromise, plainText) {
63 | // Create header
64 | var headers = {};
65 | for (var i in this.userHeaders) {
66 | headers[i] = this.userHeaders[i];
67 | }
68 | headers.alg = this.cryptographer.getKeyEncryptionAlgorithm();
69 | headers.enc = this.cryptographer.getContentEncryptionAlgorithm();
70 | var jweProtectedHeader = new Utils.Base64Url().encode(JSON.stringify(headers));
71 |
72 | // Create the IV
73 | var iv = this.cryptographer.createIV();
74 |
75 | // Create the AAD
76 | var aad = Utils.arrayFromString(jweProtectedHeader);
77 | plainText = Utils.arrayFromUtf8String(plainText);
78 |
79 | return this.cryptographer.encrypt(iv, aad, cekPromise, plainText).then(function (r) {
80 | r.header = jweProtectedHeader;
81 | r.iv = iv;
82 | return r;
83 | });
84 | };
85 |
86 | var cekPromise, encryptedCek;
87 |
88 | if (this.cryptographer.getKeyEncryptionAlgorithm() === 'dir') {
89 | // with direct encryption, this.keyPromise provides the cek
90 | // and encryptedCek is empty
91 | cekPromise = Promise.resolve(this.keyPromise);
92 | encryptedCek = [];
93 | } else {
94 | // Create a CEK key
95 | cekPromise = this.cryptographer.createCek();
96 |
97 | // Key & Cek allows us to create the encryptedCek
98 | encryptedCek = Promise.all([this.keyPromise, cekPromise]).then(function (all) {
99 | var key = all[0];
100 | var cek = all[1];
101 | return this.cryptographer.wrapCek(cek, key);
102 | }.bind(this));
103 | }
104 |
105 | // Cek allows us to encrypy the plain text
106 | var encPromise = encryptPlainText.bind(this, cekPromise, plainText)();
107 |
108 | // Once we have all the promises, we can base64 encode all the pieces.
109 | return Promise.all([encryptedCek, encPromise]).then(function (all) {
110 | var encryptedCek = all[0];
111 | var data = all[1];
112 | var base64UrlEncoder = new Utils.Base64Url();
113 | return data.header + '.' +
114 | base64UrlEncoder.encodeArray(encryptedCek) + '.' +
115 | base64UrlEncoder.encodeArray(data.iv) + '.' +
116 | base64UrlEncoder.encodeArray(data.cipher) + '.' +
117 | base64UrlEncoder.encodeArray(data.tag);
118 | });
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/lib/jose-jwe-webcryptographer.js:
--------------------------------------------------------------------------------
1 | /* -
2 | * Copyright 2014 Square Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // TODO(eslint): figure out how to properly include Jose or expose crypto object
18 |
19 | import * as Utils from './jose-utils';
20 | import { Jose } from './jose-core';
21 |
22 | /**
23 | * The WebCryptographer uses http://www.w3.org/TR/WebCryptoAPI/ to perform
24 | * various crypto operations. In theory, this should help build the library with
25 | * different underlying crypto APIs. I'm however unclear if we'll run into code
26 | * duplication or callback vs Promise based API issues.
27 | */
28 | export class WebCryptographer {
29 | constructor () {
30 | this.setKeyEncryptionAlgorithm('RSA-OAEP');
31 | this.setContentEncryptionAlgorithm('A256GCM');
32 | this.setContentSignAlgorithm('RS256');
33 | }
34 |
35 | /**
36 | * Overrides the default key encryption algorithm
37 | * @param alg string
38 | */
39 | setKeyEncryptionAlgorithm (alg) {
40 | this.keyEncryption = this.getCryptoConfig(alg);
41 | }
42 |
43 | getKeyEncryptionAlgorithm () {
44 | return this.keyEncryption.jweName;
45 | }
46 |
47 | /**
48 | * Overrides the default content encryption algorithm
49 | * @param alg string
50 | */
51 | setContentEncryptionAlgorithm (alg) {
52 | this.content_encryption = this.getCryptoConfig(alg);
53 | }
54 |
55 | getContentEncryptionAlgorithm () {
56 | return this.content_encryption.jweName;
57 | }
58 |
59 | /**
60 | * Overrides the default content sign algorithm
61 | * @param alg string
62 | */
63 | setContentSignAlgorithm (alg) {
64 | this.content_sign = this.getSignConfig(alg);
65 | }
66 |
67 | getContentSignAlgorithm () {
68 | return this.content_sign.jwa_name;
69 | }
70 |
71 | /**
72 | * Generates an IV.
73 | * This function mainly exists so that it can be mocked for testing purpose.
74 | *
75 | * @return Uint8Array with random bytes
76 | */
77 | createIV () {
78 | var iv = new Uint8Array(new Array(this.content_encryption.iv_bytes));
79 | return Jose.crypto.getRandomValues(iv);
80 | }
81 |
82 | /**
83 | * Creates a random content encryption key.
84 | * This function mainly exists so that it can be mocked for testing purpose.
85 | *
86 | * @return Promise
87 | */
88 | createCek () {
89 | var hack = this.getCekWorkaround(this.content_encryption);
90 | return Jose.crypto.subtle.generateKey(hack.id, true, hack.enc_op);
91 | }
92 |
93 | wrapCek (cek, key) {
94 | return Jose.crypto.subtle.wrapKey('raw', cek, key, this.keyEncryption.id);
95 | }
96 |
97 | unwrapCek (cek, key) {
98 | var hack = this.getCekWorkaround(this.content_encryption);
99 | var extractable = (this.content_encryption.specific_cekBytes > 0);
100 | var keyEncryption = this.keyEncryption.id;
101 |
102 | return Jose.crypto.subtle.unwrapKey('raw', cek, key, keyEncryption, hack.id, extractable, hack.dec_op);
103 | }
104 |
105 | /**
106 | * Returns algorithm and operation needed to create a CEK.
107 | *
108 | * In some cases, e.g. A128CBC-HS256, the CEK gets split into two keys. The Web
109 | * Crypto API does not allow us to generate an arbitrary number of bytes and
110 | * then create a CryptoKey without any associated algorithm. We therefore piggy
111 | * back on AES-CBS and HMAC which allows the creation of CEKs of size 16, 32, 64
112 | * and 128 bytes.
113 | */
114 | getCekWorkaround (alg) {
115 | var len = alg.specific_cekBytes;
116 | if (len) {
117 | if (len === 16) {
118 | return { id: { name: 'AES-CBC', length: 128 }, enc_op: ['encrypt'], dec_op: ['decrypt'] };
119 | } else if (len === 32) {
120 | return { id: { name: 'AES-CBC', length: 256 }, enc_op: ['encrypt'], dec_op: ['decrypt'] };
121 | } else if (len === 64) {
122 | return { id: { name: 'HMAC', hash: { name: 'SHA-256' } }, enc_op: ['sign'], dec_op: ['verify'] };
123 | } else if (len === 128) {
124 | return { id: { name: 'HMAC', hash: { name: 'SHA-384' } }, enc_op: ['sign'], dec_op: ['verify'] };
125 | } else {
126 | this.assert(false, 'getCekWorkaround: invalid len');
127 | }
128 | }
129 | return { id: alg.id, enc_op: ['encrypt'], dec_op: ['decrypt'] };
130 | }
131 |
132 | /**
133 | * Encrypts plainText with cek.
134 | *
135 | * @param iv Uint8Array
136 | * @param aad Uint8Array
137 | * @param cekPromise Promise
138 | * @param plainText Uint8Array
139 | * @return Promise
140 | */
141 | encrypt (iv, aad, cekPromise, plainText) {
142 | var config = this.content_encryption;
143 | if (iv.length !== config.iv_bytes) {
144 | return Promise.reject(Error('invalid IV length'));
145 | }
146 | if (config.auth.aead) {
147 | var tagBytes = config.auth.tagBytes;
148 |
149 | var enc = {
150 | name: config.id.name,
151 | iv: iv,
152 | additionalData: aad,
153 | tagLength: tagBytes * 8
154 | };
155 |
156 | return cekPromise.then(function (cek) {
157 | return Jose.crypto.subtle.encrypt(enc, cek, plainText).then(function (cipherText) {
158 | var offset = cipherText.byteLength - tagBytes;
159 | return {
160 | cipher: cipherText.slice(0, offset),
161 | tag: cipherText.slice(offset)
162 | };
163 | });
164 | });
165 | } else {
166 | var keys = this.splitKey(config, cekPromise, ['encrypt']);
167 | var macKeyPromise = keys[0];
168 | var encKeyPromise = keys[1];
169 |
170 | // Encrypt the plain text
171 | var cipherTextPromise = encKeyPromise.then(function (encKey) {
172 | var enc = {
173 | name: config.id.name,
174 | iv: iv
175 | };
176 | return Jose.crypto.subtle.encrypt(enc, encKey, plainText);
177 | });
178 |
179 | // compute MAC
180 | var macPromise = cipherTextPromise.then((cipherText) => {
181 | return this.truncatedMac(
182 | config,
183 | macKeyPromise,
184 | aad,
185 | iv,
186 | cipherText);
187 | });
188 |
189 | return Promise.all([cipherTextPromise, macPromise]).then(function (all) {
190 | var cipherText = all[0];
191 | var mac = all[1];
192 | return {
193 | cipher: cipherText,
194 | tag: mac
195 | };
196 | });
197 | }
198 | }
199 |
200 | /**
201 | * Compares two Uint8Arrays in constant time.
202 | *
203 | * @return Promise
204 | */
205 | compare (config, macKeyPromise, arr1, arr2) {
206 | this.assert(arr1 instanceof Uint8Array, 'compare: invalid input');
207 | this.assert(arr2 instanceof Uint8Array, 'compare: invalid input');
208 |
209 | return macKeyPromise.then(function (macKey) {
210 | var hash1 = Jose.crypto.subtle.sign(config.auth.id, macKey, arr1);
211 | var hash2 = Jose.crypto.subtle.sign(config.auth.id, macKey, arr2);
212 | return Promise.all([hash1, hash2]).then(function (all) {
213 | var hash1 = new Uint8Array(all[0]);
214 | var hash2 = new Uint8Array(all[1]);
215 | if (hash1.length !== hash2.length) {
216 | throw new Error('compare failed');
217 | }
218 | for (var i = 0; i < hash1.length; i++) {
219 | if (hash1[i] !== hash2[i]) {
220 | throw new Error('compare failed');
221 | }
222 | }
223 | return Promise.resolve(null);
224 | });
225 | });
226 | }
227 |
228 | /**
229 | * Decrypts cipherText with cek. Validates the tag.
230 | *
231 | * @param cekPromise Promise
232 | * @param aad protected header
233 | * @param iv IV
234 | * @param cipherText text to be decrypted
235 | * @param tag to be verified
236 | * @return Promise
237 | */
238 | decrypt (cekPromise, aad, iv, cipherText, tag) {
239 | if (iv.length !== this.content_encryption.iv_bytes) {
240 | return Promise.reject(Error('decryptCiphertext: invalid IV'));
241 | }
242 |
243 | var config = this.content_encryption;
244 | if (config.auth.aead) {
245 | var dec = {
246 | name: config.id.name,
247 | iv: iv,
248 | additionalData: aad,
249 | tagLength: config.auth.tagBytes * 8
250 | };
251 |
252 | return cekPromise.then(function (cek) {
253 | var buf = Utils.arrayBufferConcat(cipherText, tag);
254 | return Jose.crypto.subtle.decrypt(dec, cek, buf);
255 | });
256 | } else {
257 | var keys = this.splitKey(config, cekPromise, ['decrypt']);
258 | var macKeyPromise = keys[0];
259 | var encKeyPromise = keys[1];
260 |
261 | // Validate the MAC
262 | var macPromise = this.truncatedMac(
263 | config,
264 | macKeyPromise,
265 | aad,
266 | iv,
267 | cipherText);
268 |
269 | return Promise.all([encKeyPromise, macPromise]).then((all) => {
270 | var encKey = all[0];
271 | var mac = all[1];
272 |
273 | return this.compare(config, macKeyPromise, new Uint8Array(mac), tag).then(() => {
274 | var dec = {
275 | name: config.id.name,
276 | iv: iv
277 | };
278 | return Jose.crypto.subtle.decrypt(dec, encKey, cipherText);
279 | }).catch(() => {
280 | return Promise.reject(Error('decryptCiphertext: MAC failed.'));
281 | });
282 | });
283 | }
284 | }
285 |
286 | /**
287 | * Signs plainText.
288 | *
289 | * @param aad json
290 | * @param payload String or json
291 | * @param keyPromise Promise
292 | * @return Promise
293 | */
294 | sign (aad, payload, keyPromise) {
295 | var config = this.content_sign;
296 |
297 | if (aad.alg) {
298 | config = this.getSignConfig(aad.alg);
299 | }
300 |
301 | // Encrypt the plain text
302 | return keyPromise.then(function (key) {
303 | var base64UrlEncoder = new Utils.Base64Url();
304 | return Jose.crypto.subtle.sign(config.id, key, Utils.arrayFromString(base64UrlEncoder.encode(JSON.stringify(aad)) + '.' + base64UrlEncoder.encodeArray(payload)));
305 | });
306 | }
307 |
308 | /**
309 | * Verify JWS.
310 | *
311 | * @param payload Base64Url encoded payload
312 | * @param aad String Base64Url encoded JSON representation of the protected JWS header
313 | * @param signature Uint8Array containing the signature
314 | * @param keyPromise Promise
315 | * @param keyId value of the kid JoseHeader, it'll be passed as part of the result to the returned promise
316 | * @return Promise
317 | */
318 | verify (aad, payload, signature, keyPromise, keyId) {
319 | var config = this.content_sign;
320 |
321 | return keyPromise.then(function (key) {
322 | return Jose.crypto.subtle.verify(config.id, key, signature, Utils.arrayFromString(aad + '.' + payload)).then(function (res) {
323 | return { kid: keyId, verified: res };
324 | });
325 | });
326 | }
327 |
328 | keyId (rsaKey) {
329 | return Utils.sha256(rsaKey.n + '+' + rsaKey.d);
330 | }
331 |
332 | /**
333 | * Splits a CEK into two pieces: a MAC key and an ENC key.
334 | *
335 | * This code is structured around the fact that the crypto API does not provide
336 | * a way to validate truncated MACs. The MAC key is therefore always imported to
337 | * sign data.
338 | *
339 | * @param config (used for key lengths & algorithms)
340 | * @param cekPromise Promise CEK key to split
341 | * @param purpose Array usages of the imported key
342 | * @return [Promise, Promise]
343 | */
344 | splitKey (config, cekPromise, purpose) {
345 | // We need to split the CEK key into a MAC and ENC keys
346 | var cekBytesPromise = cekPromise.then(function (cek) {
347 | return Jose.crypto.subtle.exportKey('raw', cek);
348 | });
349 | var macKeyPromise = cekBytesPromise.then(function (cekBytes) {
350 | if (cekBytes.byteLength * 8 !== config.id.length + config.auth.key_bytes * 8) {
351 | return Promise.reject(Error('encryptPlainText: incorrect cek length'));
352 | }
353 | var bytes = cekBytes.slice(0, config.auth.key_bytes);
354 | return Jose.crypto.subtle.importKey('raw', bytes, config.auth.id, false, ['sign']);
355 | });
356 | var encKeyPromise = cekBytesPromise.then(function (cekBytes) {
357 | if (cekBytes.byteLength * 8 !== config.id.length + config.auth.key_bytes * 8) {
358 | return Promise.reject(Error('encryptPlainText: incorrect cek length'));
359 | }
360 | var bytes = cekBytes.slice(config.auth.key_bytes);
361 | return Jose.crypto.subtle.importKey('raw', bytes, config.id, false, purpose);
362 | });
363 | return [macKeyPromise, encKeyPromise];
364 | }
365 |
366 | /**
367 | * Converts the Jose web algorithms into data which is
368 | * useful for the Web Crypto API.
369 | *
370 | * length = in bits
371 | * bytes = in bytes
372 | */
373 | getCryptoConfig (alg) {
374 | switch (alg) {
375 | // Key encryption
376 | case 'RSA-OAEP':
377 | return {
378 | jweName: 'RSA-OAEP',
379 | id: { name: 'RSA-OAEP', hash: { name: 'SHA-1' } }
380 | };
381 | case 'RSA-OAEP-256':
382 | return {
383 | jweName: 'RSA-OAEP-256',
384 | id: { name: 'RSA-OAEP', hash: { name: 'SHA-256' } }
385 | };
386 | case 'A128KW':
387 | return {
388 | jweName: 'A128KW',
389 | id: { name: 'AES-KW', length: 128 }
390 | };
391 | case 'A256KW':
392 | return {
393 | jweName: 'A256KW',
394 | id: { name: 'AES-KW', length: 256 }
395 | };
396 | case 'dir':
397 | return {
398 | jweName: 'dir'
399 | };
400 |
401 | // Content encryption
402 | case 'A128CBC-HS256':
403 | return {
404 | jweName: 'A128CBC-HS256',
405 | id: { name: 'AES-CBC', length: 128 },
406 | iv_bytes: 16,
407 | specific_cekBytes: 32,
408 | auth: {
409 | key_bytes: 16,
410 | id: { name: 'HMAC', hash: { name: 'SHA-256' } },
411 | truncated_bytes: 16
412 | }
413 | };
414 | case 'A256CBC-HS512':
415 | return {
416 | jweName: 'A256CBC-HS512',
417 | id: { name: 'AES-CBC', length: 256 },
418 | iv_bytes: 16,
419 | specific_cekBytes: 64,
420 | auth: {
421 | key_bytes: 32,
422 | id: { name: 'HMAC', hash: { name: 'SHA-512' } },
423 | truncated_bytes: 32
424 | }
425 | };
426 | case 'A128GCM':
427 | return {
428 | jweName: 'A128GCM',
429 | id: { name: 'AES-GCM', length: 128 },
430 | iv_bytes: 12,
431 | auth: {
432 | aead: true,
433 | tagBytes: 16
434 | }
435 | };
436 | case 'A256GCM':
437 | return {
438 | jweName: 'A256GCM',
439 | id: { name: 'AES-GCM', length: 256 },
440 | iv_bytes: 12,
441 | auth: {
442 | aead: true,
443 | tagBytes: 16
444 | }
445 | };
446 | default:
447 | throw Error('unsupported algorithm: ' + alg);
448 | }
449 | }
450 |
451 | /**
452 | * Computes a truncated MAC.
453 | *
454 | * @param config configuration
455 | * @param macKeyPromise Promise mac key
456 | * @param aad Uint8Array
457 | * @param iv Uint8Array
458 | * @param cipherText Uint8Array
459 | * @return Promise truncated MAC
460 | */
461 | truncatedMac (config, macKeyPromise, aad, iv, cipherText) {
462 | return macKeyPromise.then(function (macKey) {
463 | var al = new Uint8Array(Utils.arrayFromInt32(aad.length * 8));
464 | var alFull = new Uint8Array(8);
465 | alFull.set(al, 4);
466 | var buf = Utils.arrayBufferConcat(aad, iv, cipherText, alFull);
467 | return Jose.crypto.subtle.sign(config.auth.id, macKey, buf).then(function (bytes) {
468 | return bytes.slice(0, config.auth.truncated_bytes);
469 | });
470 | });
471 | }
472 |
473 | /**
474 | * Converts the Jose web algorithms into data which is
475 | * useful for the Web Crypto API.
476 | */
477 | getSignConfig (alg) {
478 | switch (alg) {
479 | case 'RS256':
480 | return {
481 | jwa_name: 'RS256',
482 | id: { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-256' } }
483 | };
484 | case 'RS384':
485 | return {
486 | jwa_name: 'RS384',
487 | id: { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-384' } }
488 | };
489 | case 'RS512':
490 | return {
491 | jwa_name: 'RS512',
492 | id: { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-512' } }
493 | };
494 | case 'PS256':
495 | return {
496 | jwa_name: 'PS256',
497 | id: { name: 'RSA-PSS', hash: { name: 'SHA-256' }, saltLength: 20 }
498 | };
499 | case 'PS384':
500 | return {
501 | jwa_name: 'PS384',
502 | id: { name: 'RSA-PSS', hash: { name: 'SHA-384' }, saltLength: 20 }
503 | };
504 | case 'PS512':
505 | return {
506 | jwa_name: 'PS512',
507 | id: { name: 'RSA-PSS', hash: { name: 'SHA-512' }, saltLength: 20 }
508 | };
509 | case 'HS256':
510 | return {
511 | jwa_name: 'HS256',
512 | id: { name: 'HMAC', hash: { name: 'SHA-256' } }
513 | };
514 | case 'HS384':
515 | return {
516 | jwa_name: 'HS384',
517 | id: { name: 'HMAC', hash: { name: 'SHA-384' } }
518 | };
519 | case 'HS512':
520 | return {
521 | jwa_name: 'HS512',
522 | id: { name: 'HMAC', hash: { name: 'SHA-512' } }
523 | };
524 | case 'ES256':
525 | return {
526 | jwa_name: 'ES256',
527 | id: { name: 'ECDSA', namedCurve: 'P-256', hash: { name: 'SHA-256' } }
528 | };
529 | case 'ES384':
530 | return {
531 | jwa_name: 'ES384',
532 | id: { name: 'ECDSA', namedCurve: 'P-384', hash: { name: 'SHA-384' } }
533 | };
534 | case 'ES512':
535 | return {
536 | jwa_name: 'ES512',
537 | id: { name: 'ECDSA', namedCurve: 'P-521', hash: { name: 'SHA-512' } }
538 | };
539 | default:
540 | throw Error('unsupported algorithm: ' + alg);
541 | }
542 | }
543 |
544 | /**
545 | * Derives key usage from algorithm's name
546 | *
547 | * @param alg String algorithm name
548 | * @returns {*}
549 | */
550 | getKeyUsageByAlg (alg) {
551 | switch (alg) {
552 | // signature
553 | case 'RS256':
554 | case 'RS384':
555 | case 'RS512':
556 | case 'PS256':
557 | case 'PS384':
558 | case 'PS512':
559 | case 'HS256':
560 | case 'HS384':
561 | case 'HS512':
562 | case 'ES256':
563 | case 'ES384':
564 | case 'ES512':
565 | case 'ES256K':
566 | return {
567 | publicKey: 'verify',
568 | privateKey: 'sign'
569 | };
570 | // key encryption
571 | case 'RSA-OAEP':
572 | case 'RSA-OAEP-256':
573 | case 'A128KW':
574 | case 'A256KW':
575 | return {
576 | publicKey: 'wrapKey',
577 | privateKey: 'unwrapKey'
578 | };
579 | default:
580 | throw Error('unsupported algorithm: ' + alg);
581 | }
582 | }
583 |
584 | /**
585 | * Feel free to override this function.
586 | */
587 | assert (expr, msg) {
588 | if (!expr) {
589 | throw new Error(msg);
590 | }
591 | }
592 | }
593 |
--------------------------------------------------------------------------------
/lib/jose-jws-sign.js:
--------------------------------------------------------------------------------
1 | /* -
2 | * Copyright 2015 Peculiar Ventures
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as Utils from './jose-utils';
18 |
19 | /**
20 | * Handles decryption.
21 | *
22 | * @param cryptographer an instance of WebCryptographer (or equivalent). Keep
23 | * in mind that decryption mutates the cryptographer.
24 | *
25 | * @author Patrizio Bruno
26 | */
27 | export class Signer {
28 | constructor (cryptographer) {
29 | this.cryptographer = cryptographer;
30 |
31 | this.keyPromises = {};
32 | this.waiting_kid = 0;
33 | this.headers = {};
34 | this.signer_aads = {};
35 | this.signer_headers = {};
36 | }
37 |
38 | /**
39 | * Adds a signer to JoseJWS instance.
40 | *
41 | * @param key private key in json format. Parameters can be base64
42 | * encoded, strings or number (for e.g. 'e'), or CryptoKey.
43 | * @param keyId a string identifying the key. OPTIONAL
44 | * @param aad Object protected header
45 | * @param header Object unprotected header
46 | */
47 | addSigner (key, keyId, aad, header) {
48 | var that = this;
49 | var keyPromise;
50 | if (Utils.isCryptoKey(key)) {
51 | keyPromise = new Promise(function (resolve) {
52 | resolve(key);
53 | });
54 | } else {
55 | var alg;
56 | if (aad && aad.alg) {
57 | alg = aad.alg;
58 | } else {
59 | alg = that.cryptographer.getContentSignAlgorithm();
60 | }
61 | keyPromise = Utils.importPrivateKey(key, alg, 'sign');
62 | }
63 |
64 | var kidPromise;
65 | if (keyId) {
66 | kidPromise = new Promise(function (resolve) {
67 | resolve(keyId);
68 | });
69 | } else if (Utils.isCryptoKey(key)) {
70 | throw new Error('keyId is a mandatory argument when the key is a CryptoKey');
71 | } else {
72 | kidPromise = this.cryptographer.keyId(key);
73 | }
74 |
75 | that.waiting_kid++;
76 |
77 | return kidPromise.then(function (kid) {
78 | that.keyPromises[kid] = keyPromise;
79 | that.waiting_kid--;
80 | if (aad) {
81 | that.signer_aads[kid] = aad;
82 | }
83 | if (header) {
84 | that.signer_headers[kid] = header;
85 | }
86 | return kid;
87 | });
88 | }
89 |
90 | /**
91 | * Adds a signature to a JWS object
92 | * @param jws JWS Object to be signed or its representation
93 | * @param aad Object protected header
94 | * @param header Object unprotected header
95 | * @return Promise
96 | */
97 | addSignature (jws, aad, header) {
98 | if (Utils.isString(jws)) {
99 | jws = JSON.parse(jws);
100 | }
101 |
102 | if (jws.payload && Utils.isString(jws.payload) &&
103 | jws.protected && Utils.isString(jws.protected) &&
104 | jws.header && jws.header instanceof Object &&
105 | jws.signature && Utils.isString(jws.signature)) {
106 | return this.sign(JWS.fromObject(jws), aad, header);
107 | } else {
108 | throw new Error('JWS is not a valid JWS object');
109 | }
110 | }
111 |
112 | /**
113 | * Computes signature.
114 | *
115 | * @param payload JWS Object or utf-8 string to be signed
116 | * @param aad Object protected header
117 | * @param header Object unprotected header
118 | * @return Promise
119 | */
120 | sign (payload, aad, header) {
121 | var that = this;
122 | var kids = [];
123 |
124 | if (Object.keys(that.keyPromises).length === 0) {
125 | throw new Error('No signers defined. At least one is required to sign the JWS.');
126 | }
127 |
128 | if (that.waiting_kid) {
129 | throw new Error('still generating key IDs');
130 | }
131 |
132 | function sign (message, protectedHeader, unprotectedHeader, keyPromise, kid) {
133 | var toBeSigned;
134 |
135 | if (!protectedHeader) {
136 | protectedHeader = {};
137 | }
138 |
139 | if (!protectedHeader.alg) {
140 | protectedHeader.alg = that.cryptographer.getContentSignAlgorithm();
141 | protectedHeader.typ = 'JWT';
142 | }
143 |
144 | if (!protectedHeader.kid) {
145 | protectedHeader.kid = kid;
146 | }
147 |
148 | if (Utils.isString(message)) {
149 | toBeSigned = Utils.arrayFromUtf8String(message);
150 | } else {
151 | try {
152 | toBeSigned = Utils.arrayish(message);
153 | } catch (e) {
154 | if (message instanceof JWS) {
155 | toBeSigned = Utils.arrayFromString(new Utils.Base64Url().decode(message.payload));
156 | } else if (message instanceof Object) {
157 | toBeSigned = Utils.arrayFromUtf8String(JSON.stringify(message));
158 | } else {
159 | throw new Error('cannot sign this message');
160 | }
161 | }
162 | }
163 |
164 | return that.cryptographer.sign(protectedHeader, toBeSigned, keyPromise).then(function (signature) {
165 | var jws = new JWS(protectedHeader, unprotectedHeader, toBeSigned, signature);
166 | if (message instanceof JWS) {
167 | delete jws.payload;
168 | if (!message.signatures) {
169 | message.signatures = [jws];
170 | } else {
171 | message.signatures.push(jws);
172 | }
173 | return message;
174 | }
175 | return jws;
176 | });
177 | }
178 |
179 | function doSign (pl, ph, uh, kps, kids) {
180 | if (kids.length) {
181 | var kid = kids.shift();
182 | var rv = sign(pl, that.signer_aads[kid] || ph, that.signer_headers[kid] || uh, kps[kid], kid);
183 | if (kids.length) {
184 | rv = rv.then(function (jws) {
185 | return doSign(jws, null, null, kps, kids);
186 | });
187 | }
188 | return rv;
189 | }
190 | }
191 |
192 | for (var kid in that.keyPromises) {
193 | if (Object.prototype.hasOwnProperty.call(that.keyPromises, kid)) {
194 | kids.push(kid);
195 | }
196 | }
197 | return doSign(payload, aad, header, that.keyPromises, kids);
198 | }
199 | }
200 |
201 | /**
202 | * Initialize a JWS object.
203 | *
204 | * @param protectedHeader protected header (JS object)
205 | * @param payload Uint8Array payload to be signed
206 | * @param signature ArrayBuffer signature of the payload
207 | * @param header unprotected header (JS object)
208 | *
209 | * @constructor
210 | */
211 | export class JWS {
212 | constructor (protectedHeader, header, payload, signature) {
213 | this.header = header;
214 | var base64UrlEncoder = new Utils.Base64Url();
215 | this.payload = base64UrlEncoder.encodeArray(payload);
216 | if (signature) {
217 | this.signature = base64UrlEncoder.encodeArray(signature);
218 | }
219 | this.protected = base64UrlEncoder.encode(JSON.stringify(protectedHeader));
220 | }
221 |
222 | fromObject (obj) {
223 | var rv = new JWS(obj.protected, obj.header, obj.payload, null);
224 | rv.signature = obj.signature;
225 | rv.signatures = obj.signatures;
226 | return rv;
227 | }
228 |
229 | /**
230 | * Serialize a JWS object using the JSON serialization format
231 | *
232 | * @returns {Object} a copy of this
233 | */
234 | JsonSerialize () {
235 | return JSON.stringify(this);
236 | }
237 |
238 | /**
239 | * Serialize a JWS object using the Compact Serialization Format
240 | *
241 | * @returns {string} BASE64URL(UTF8(PROTECTED HEADER)).BASE64URL(PAYLOAD).BASE64URL(SIGNATURE)
242 | */
243 | CompactSerialize () {
244 | return this.protected + '.' + this.payload + '.' + this.signature;
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/lib/jose-jws-verify.js:
--------------------------------------------------------------------------------
1 | /* -
2 | * Copyright 2015 Peculiar Ventures
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as Utils from './jose-utils';
18 |
19 | /**
20 | * Handles signature verification.
21 | *
22 | * @param cryptographer an instance of WebCryptographer (or equivalent). Keep
23 | * in mind that decryption mutates the cryptographer.
24 | * @param message a JWS message
25 | * @param keyfinder (optional) a function returning a Promise given
26 | * a key id
27 | *
28 | * @author Patrizio Bruno
29 | */
30 | export class Verifier {
31 | constructor (cryptographer, message, keyfinder) {
32 | var that = this;
33 | var alg;
34 | var jwt;
35 | var aad;
36 | var header;
37 | var payload;
38 | var signatures;
39 | var protectedHeader;
40 | var jwtRx = /^([0-9a-z_-]+)\.([0-9a-z_-]+)\.([0-9a-z_-]+)$/i;
41 |
42 | that.cryptographer = cryptographer;
43 |
44 | alg = cryptographer.getContentSignAlgorithm();
45 |
46 | if (Utils.isString(message)) {
47 | if ((jwt = jwtRx.exec(message))) {
48 | if (jwt.length !== 4) {
49 | throw new Error('wrong JWS compact serialization format');
50 | }
51 |
52 | message = {
53 | protected: jwt[1],
54 | payload: jwt[2],
55 | signature: jwt[3]
56 | };
57 | } else {
58 | message = JSON.parse(message);
59 | }
60 | } else if (typeof message !== 'object') {
61 | throw new Error('data format not supported');
62 | }
63 |
64 | aad = message.protected;
65 | header = message.header;
66 | payload = message.payload;
67 | signatures = message.signatures instanceof Array ? message.signatures.slice(0) : [];
68 |
69 | signatures.forEach(function (sign) {
70 | sign.aad = sign.protected;
71 | sign.protected = JSON.parse(new Utils.Base64Url().decode(sign.protected));
72 | });
73 |
74 | that.aad = aad;
75 | protectedHeader = new Utils.Base64Url().decode(aad);
76 | try {
77 | protectedHeader = JSON.parse(protectedHeader);
78 | } catch (e) {
79 | }
80 |
81 | if (!protectedHeader && !header) {
82 | throw new Error('at least one header is required');
83 | }
84 |
85 | if (!protectedHeader.alg) {
86 | throw new Error("'alg' is a mandatory header");
87 | }
88 |
89 | if (protectedHeader.alg !== alg) {
90 | throw new Error("the alg header '" + protectedHeader.alg + "' doesn't match the requested algorithm '" + alg + "'");
91 | }
92 |
93 | if (protectedHeader && protectedHeader.typ && protectedHeader.typ !== 'JWT') {
94 | throw new Error("typ '" + protectedHeader.typ + "' not supported");
95 | }
96 |
97 | if (message.signature) {
98 | signatures.unshift({
99 | aad: aad,
100 | protected: protectedHeader,
101 | header: header,
102 | signature: message.signature
103 | });
104 | }
105 |
106 | that.signatures = [];
107 | for (var i = 0; i < signatures.length; i++) {
108 | that.signatures[i] = JSON.parse(JSON.stringify(signatures[i]));
109 | that.signatures[i].signature = Utils.arrayFromString(new Utils.Base64Url().decode(signatures[i].signature));
110 | }
111 |
112 | that.payload = payload;
113 |
114 | that.keyPromises = {};
115 | that.waiting_kid = 0;
116 |
117 | if (keyfinder) {
118 | that.keyfinder = keyfinder;
119 | }
120 | }
121 |
122 | /**
123 | * Add supported recipients to verify multiple signatures
124 | *
125 | * @param key public key in json format. Parameters can be base64
126 | * encoded, strings or number (for 'e'), or CryptoKey.
127 | * @param keyId a string identifying the key. OPTIONAL
128 | * @param alg String signature algorithm. OPTIONAL
129 | * @returns Promise a Promise of a key id
130 | */
131 | addRecipient (key, keyId, alg) {
132 | var that = this;
133 | var kidPromise;
134 | var keyPromise;
135 | if (Utils.isCryptoKey(key)) {
136 | keyPromise = new Promise(function (resolve) { resolve(key); });
137 | } else {
138 | keyPromise = Utils.importPublicKey(key, alg || that.cryptographer.getContentSignAlgorithm(), 'verify');
139 | }
140 |
141 | if (keyId) {
142 | kidPromise = new Promise(function (resolve) {
143 | resolve(keyId);
144 | });
145 | } else if (Utils.isCryptoKey(key)) {
146 | throw new Error('keyId is a mandatory argument when the key is a CryptoKey');
147 | } else {
148 | console.log("it's unsafe to omit a keyId");
149 | kidPromise = this.cryptographer.keyId(key);
150 | }
151 |
152 | that.waiting_kid++;
153 |
154 | return kidPromise.then(function (kid) {
155 | that.keyPromises[kid] = keyPromise;
156 | that.waiting_kid--;
157 | return kid;
158 | });
159 | }
160 |
161 | /**
162 | * Verifies a JWS signature
163 | *
164 | * @returns Promise a Promise of an array of objects { kid: string, verified: bool, payload?: string }
165 | *
166 | * payload is only populated and usable if verified is true
167 | */
168 | verify () {
169 | var that = this;
170 | var signatures = that.signatures;
171 | var keyPromises = that.keyPromises;
172 | var keyfinder = that.keyfinder;
173 | var promises = [];
174 | var check = !!keyfinder || Object.keys(that.keyPromises).length > 0;
175 |
176 | if (!check) {
177 | throw new Error('No recipients defined. At least one is required to verify the JWS.');
178 | }
179 |
180 | if (that.waiting_kid) {
181 | throw new Error('still generating key IDs');
182 | }
183 |
184 | signatures.forEach(function (sig) {
185 | var kid = sig.protected.kid;
186 | if (keyfinder) {
187 | keyPromises[kid] = keyfinder(kid);
188 | }
189 | promises.push(that.cryptographer.verify(sig.aad, that.payload, sig.signature, keyPromises[kid], kid)
190 | .then(function (vr) {
191 | if (vr.verified) {
192 | vr.payload = new Utils.Base64Url().decode(that.payload);
193 | }
194 | return vr;
195 | }));
196 | });
197 | return Promise.all(promises);
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/lib/jose-utils.js:
--------------------------------------------------------------------------------
1 | /* -
2 | * Copyright 2014 Square Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // TODO(eslint): figure out how to properly include Jose or expose crypto object
18 |
19 | import { WebCryptographer } from './jose-jwe-webcryptographer';
20 | import { Jose } from './jose-core';
21 | const webCryptographer = new WebCryptographer();
22 |
23 | /**
24 | * Import a public key in JWK format, either RSA or ECDSA.
25 | *
26 | * @param key JWK public key
27 | * @param alg Name of the JWA signing algorithm (e.g. RS256)
28 | * @return Promise
29 | */
30 | export const importPublicKey = (key, alg) => {
31 | switch (alg) {
32 | case 'RS256':
33 | case 'RS384':
34 | case 'RS512':
35 | case 'PS256':
36 | case 'PS384':
37 | case 'PS512':
38 | return importRsaPublicKey(key, alg);
39 | case 'ES256':
40 | case 'ES384':
41 | case 'ES512':
42 | return importEcPublicKey(key, alg);
43 | default:
44 | throw Error('unsupported algorithm: ' + alg);
45 | }
46 | };
47 |
48 | /**
49 | * Import a private key in JWK format, either RSA or EC.
50 | *
51 | * @param key JWK private key
52 | * @param alg Name of the JWA signing algorithm (e.g. RS256)
53 | * @return Promise
54 | */
55 | export const importPrivateKey = (key, alg) => {
56 | switch (alg) {
57 | case 'RS256':
58 | case 'RS384':
59 | case 'RS512':
60 | case 'PS256':
61 | case 'PS384':
62 | case 'PS512':
63 | return importRsaPrivateKey(key, alg);
64 | case 'ES256':
65 | case 'ES384':
66 | case 'ES512':
67 | return importEcPrivateKey(key, alg);
68 | default:
69 | throw Error('unsupported algorithm: ' + alg);
70 | }
71 | };
72 |
73 | /**
74 | * Import a public EC key in JWK format.
75 | *
76 | * @param ecKey JWK public key
77 | * @param alg Name of the JWA signing algorithm (e.g. ES256)
78 | * @return Promise
79 | */
80 | export const importEcPublicKey = (ecKey, alg) => {
81 | var config = webCryptographer.getSignConfig(alg);
82 | var usage = webCryptographer.getKeyUsageByAlg(alg);
83 |
84 | return Jose.crypto.subtle.importKey('jwk', ecKey, config.id, false, [usage.publicKey]);
85 | };
86 |
87 | /**
88 | * Import a private EC key in JWK format.
89 | *
90 | * @param ecKey JWK private key
91 | * @param alg Name of the JWA signing algorithm (e.g. ES256)
92 | * @return Promise
93 | */
94 | export const importEcPrivateKey = (ecKey, alg) => {
95 | var config = webCryptographer.getSignConfig(alg);
96 | var usage = webCryptographer.getKeyUsageByAlg(alg);
97 |
98 | return Jose.crypto.subtle.importKey('jwk', ecKey, config.id, false, [usage.privateKey]);
99 | };
100 |
101 | /**
102 | * Converts the output from `openssl x509 -text` or `openssl rsa -text` into a
103 | * CryptoKey which can then be used with RSA-OAEP. Also accepts (and validates)
104 | * JWK keys.
105 | *
106 | * TODO: this code probably belongs in the webcryptographer.
107 | *
108 | * @param rsaKey public RSA key in json format. Parameters can be base64
109 | * encoded, strings or number (for 'e').
110 | * @param alg String, name of the algorithm
111 | * @return Promise
112 | */
113 | export const importRsaPublicKey = (rsaKey, alg) => {
114 | var jwk;
115 | var config;
116 | var usage = webCryptographer.getKeyUsageByAlg(alg);
117 |
118 | if (usage.publicKey === 'wrapKey') {
119 | if (!rsaKey.alg) {
120 | rsaKey.alg = alg;
121 | }
122 | jwk = convertRsaKey(rsaKey, ['n', 'e']);
123 | config = webCryptographer.getCryptoConfig(alg);
124 | } else {
125 | var rk = {};
126 | for (var name in rsaKey) {
127 | if (Object.prototype.hasOwnProperty.call(rsaKey, name)) {
128 | rk[name] = rsaKey[name];
129 | }
130 | }
131 |
132 | if (!rk.alg && alg) {
133 | rk.alg = alg;
134 | }
135 | config = webCryptographer.getSignConfig(rk.alg);
136 | jwk = convertRsaKey(rk, ['n', 'e']);
137 | jwk.ext = true;
138 | }
139 | return Jose.crypto.subtle.importKey('jwk', jwk, config.id, false, [usage.publicKey]);
140 | };
141 |
142 | /**
143 | * Converts the output from `openssl x509 -text` or `openssl rsa -text` into a
144 | * CryptoKey which can then be used with RSA-OAEP and RSA. Also accepts (and validates)
145 | * JWK keys.
146 | *
147 | * TODO: this code probably belongs in the webcryptographer.
148 | *
149 | * @param rsaKey private RSA key in json format. Parameters can be base64
150 | * encoded, strings or number (for 'e').
151 | * @param alg String, name of the algorithm
152 | * @return Promise
153 | */
154 | export const importRsaPrivateKey = (rsaKey, alg) => {
155 | var jwk;
156 | var config;
157 | var usage = webCryptographer.getKeyUsageByAlg(alg);
158 |
159 | if (usage.privateKey === 'unwrapKey') {
160 | if (!rsaKey.alg) {
161 | rsaKey.alg = alg;
162 | }
163 | jwk = convertRsaKey(rsaKey, ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']);
164 | config = webCryptographer.getCryptoConfig(alg);
165 | } else {
166 | var rk = {};
167 | for (var name in rsaKey) {
168 | if (Object.prototype.hasOwnProperty.call(rsaKey, name)) {
169 | rk[name] = rsaKey[name];
170 | }
171 | }
172 | config = webCryptographer.getSignConfig(alg);
173 | if (!rk.alg && alg) {
174 | rk.alg = alg;
175 | }
176 | jwk = convertRsaKey(rk, ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']);
177 | jwk.ext = true;
178 | }
179 | return Jose.crypto.subtle.importKey('jwk', jwk, config.id, false, [usage.privateKey]);
180 | };
181 |
182 | // Private functions
183 |
184 | export const isString = (str) => {
185 | return ((typeof (str) === 'string') || (str instanceof String));
186 | };
187 |
188 | /**
189 | * Takes an arrayish (an array, ArrayBuffer or Uint8Array)
190 | * and returns an array or a Uint8Array.
191 | *
192 | * @param arr arrayish
193 | * @return array or Uint8Array
194 | */
195 | export const arrayish = (arr) => {
196 | if (arr instanceof Array) {
197 | return arr;
198 | }
199 | if (arr instanceof Uint8Array) {
200 | return arr;
201 | }
202 | if (arr instanceof ArrayBuffer) {
203 | return new Uint8Array(arr);
204 | }
205 | webCryptographer.assert(false, 'arrayish: invalid input');
206 | };
207 |
208 | /**
209 | * Checks if an RSA key contains all the expected parameters. Also checks their
210 | * types. Converts hex encoded strings (or numbers) to base64.
211 | *
212 | * @param rsaKey RSA key in json format. Parameters can be base64 encoded,
213 | * strings or number (for 'e').
214 | * @param parameters array
215 | * @return json
216 | */
217 | export const convertRsaKey = (rsaKey, parameters) => {
218 | var r = {};
219 | var alg;
220 |
221 | // Check that we have all the parameters
222 | var missing = [];
223 | parameters.map(function (p) { if (typeof (rsaKey[p]) === 'undefined') { missing.push(p); } });
224 |
225 | if (missing.length > 0) {
226 | webCryptographer.assert(false, 'convertRsaKey: Was expecting ' + missing.join());
227 | }
228 |
229 | // kty is either missing or is set to "RSA"
230 | if (typeof (rsaKey.kty) !== 'undefined') {
231 | webCryptographer.assert(rsaKey.kty === 'RSA', "convertRsaKey: expecting rsaKey['kty'] to be 'RSA'");
232 | }
233 | r.kty = 'RSA';
234 |
235 | try {
236 | webCryptographer.getSignConfig(rsaKey.alg);
237 | alg = rsaKey.alg;
238 | } catch (err) {
239 | try {
240 | webCryptographer.getCryptoConfig(rsaKey.alg);
241 | alg = rsaKey.alg;
242 | } catch (er) {
243 | webCryptographer.assert(alg, "convertRsaKey: expecting rsaKey['alg'] to have a valid value");
244 | }
245 | }
246 | r.alg = alg;
247 |
248 | // note: we punt on checking key_ops
249 |
250 | var intFromHex = (e) => {
251 | return parseInt(e, 16);
252 | };
253 | for (var i = 0; i < parameters.length; i++) {
254 | var p = parameters[i];
255 | var v = rsaKey[p];
256 | var base64UrlEncoder = new Base64Url();
257 | if (p === 'e') {
258 | if (typeof (v) === 'number') {
259 | v = base64UrlEncoder.encodeArray(stripLeadingZeros(arrayFromInt32(v)));
260 | }
261 | } else if (/^([0-9a-fA-F]{2}:)+[0-9a-fA-F]{2}$/.test(v)) {
262 | var arr = v.split(':').map(intFromHex);
263 | v = base64UrlEncoder.encodeArray(stripLeadingZeros(arr));
264 | } else if (typeof (v) !== 'string') {
265 | webCryptographer.assert(false, "convertRsaKey: expecting rsaKey['" + p + "'] to be a string");
266 | }
267 | r[p] = v;
268 | }
269 |
270 | return r;
271 | };
272 |
273 | /**
274 | * Converts a string into an array of ascii codes.
275 | *
276 | * @param str ascii string
277 | * @return Uint8Array
278 | */
279 | export const arrayFromString = (str) => {
280 | webCryptographer.assert(isString(str), 'arrayFromString: invalid input');
281 | var arr = str.split('').map(function (c) {
282 | return c.charCodeAt(0);
283 | });
284 | return new Uint8Array(arr);
285 | };
286 |
287 | /**
288 | * Converts a string into an array of utf-8 codes.
289 | *
290 | * @param str utf-8 string
291 | * @return Uint8Array
292 | */
293 | export const arrayFromUtf8String = (str) => {
294 | webCryptographer.assert(isString(str), 'arrayFromUtf8String: invalid input');
295 | // javascript represents strings as utf-16. Jose imposes the use of
296 | // utf-8, so we need to convert from one representation to the other.
297 | str = unescape(encodeURIComponent(str));
298 | return arrayFromString(str);
299 | };
300 |
301 | /**
302 | * Converts an array of ascii bytes into a string.
303 | *
304 | * @param arr arrayish
305 | * @return ascii string
306 | */
307 | export const stringFromArray = (arr) => {
308 | arr = arrayish(arr);
309 | var r = '';
310 | for (var i = 0; i < arr.length; i++) {
311 | r += String.fromCharCode(arr[i]);
312 | }
313 |
314 | return r;
315 | };
316 |
317 | /**
318 | * Converts an array of ascii bytes into a string.
319 | *
320 | * @param arr ArrayBuffer
321 | * @return ascii string
322 | */
323 | export const utf8StringFromArray = (arr) => {
324 | webCryptographer.assert(arr instanceof ArrayBuffer, 'utf8StringFromArray: invalid input');
325 |
326 | // javascript represents strings as utf-16. Jose imposes the use of
327 | // utf-8, so we need to convert from one representation to the other.
328 | var r = stringFromArray(arr);
329 | return decodeURIComponent(escape(r));
330 | };
331 |
332 | /**
333 | * Strips leading zero in an array.
334 | *
335 | * @param arr arrayish
336 | * @return array
337 | */
338 | export const stripLeadingZeros = (arr) => {
339 | if (arr instanceof ArrayBuffer) {
340 | arr = new Uint8Array(arr);
341 | }
342 | var isLeadingZero = true;
343 | var r = [];
344 | for (var i = 0; i < arr.length; i++) {
345 | if (isLeadingZero && arr[i] === 0) {
346 | continue;
347 | }
348 | isLeadingZero = false;
349 | r.push(arr[i]);
350 | }
351 | return r;
352 | };
353 |
354 | /**
355 | * Converts a number into an array of 4 bytes (big endian).
356 | *
357 | * @param i number
358 | * @return ArrayBuffer
359 | */
360 | export const arrayFromInt32 = (i) => {
361 | webCryptographer.assert(typeof (i) === 'number', 'arrayFromInt32: invalid input');
362 | // TODO(eslint): figure out if there's a better way to validate i
363 | // eslint-disable-next-line eqeqeq, no-self-compare
364 | webCryptographer.assert(i == i | 0, 'arrayFromInt32: out of range');
365 |
366 | var buf = new Uint8Array(new Uint32Array([i]).buffer);
367 | var r = new Uint8Array(4);
368 | for (var j = 0; j < 4; j++) {
369 | r[j] = buf[3 - j];
370 | }
371 | return r.buffer;
372 | };
373 |
374 | /**
375 | * Concatenates arrayishes.
376 | *
377 | * note: cannot be a Arrow function, because Arrow functions do not expose 'arguments' object
378 | * and Rest parameters are not supported in Babel yet.
379 | *
380 | * @param arguments two or more arrayishes
381 | * @return Uint8Array
382 | */
383 | export function arrayBufferConcat (/* ... */) {
384 | // Compute total size
385 | var args = [];
386 | var total = 0;
387 | for (var i = 0; i < arguments.length; i++) {
388 | args.push(arrayish(arguments[i]));
389 | total += args[i].length;
390 | }
391 | var r = new Uint8Array(total);
392 | var offset = 0;
393 | for (i = 0; i < arguments.length; i++) {
394 | for (var j = 0; j < args[i].length; j++) {
395 | r[offset++] = args[i][j];
396 | }
397 | }
398 | webCryptographer.assert(offset === total, 'arrayBufferConcat: unexpected offset');
399 | return r;
400 | }
401 |
402 | export const sha256 = (str) => {
403 | // Browser docs indicate the first parameter to crypto.subtle.digest to be a
404 | // DOMString. This was initially implemented as an object and continues to be
405 | // supported, so we favor the older form for backwards compatibility.
406 | return Jose.crypto.subtle.digest({ name: 'SHA-256' }, arrayFromString(str)).then(function (hash) {
407 | return new Base64Url().encodeArray(hash);
408 | });
409 | };
410 |
411 | export const isCryptoKey = (rsaKey) => {
412 | // Some browsers don't expose the CryptoKey as an object, so we need to check
413 | // the constructor's name.
414 | if (rsaKey.constructor.name === 'CryptoKey') {
415 | return true;
416 | }
417 |
418 | // In the presence of minifiers, relying on class names can be problematic,
419 | // so let's also allow objects that have an 'algorithm' property.
420 | if (Object.prototype.hasOwnProperty.call(rsaKey, 'algorithm')) {
421 | return true;
422 | }
423 |
424 | return false;
425 | };
426 | export class Base64Url {
427 | /**
428 | * Base64Url encodes a string (no trailing '=')
429 | *
430 | * @param str string
431 | * @return string
432 | */
433 | encode (str) {
434 | webCryptographer.assert(isString(str), 'Base64Url.encode: invalid input');
435 | return btoa(str)
436 | .replace(/\+/g, '-')
437 | .replace(/\//g, '_')
438 | .replace(/=+$/, '');
439 | }
440 |
441 | /**
442 | * Base64Url encodes an array
443 | *
444 | * @param arr array or ArrayBuffer
445 | * @return string
446 | */
447 | encodeArray (arr) {
448 | return this.encode(stringFromArray(arr));
449 | }
450 |
451 | /**
452 | * Base64Url decodes a string
453 | *
454 | * @param str string
455 | * @return string
456 | */
457 | decode (str) {
458 | webCryptographer.assert(isString(str), 'Base64Url.decode: invalid input');
459 | // atob is nice and ignores missing '='
460 | return atob(str.replace(/-/g, '+').replace(/_/g, '/'));
461 | }
462 |
463 | decodeArray (str) {
464 | webCryptographer.assert(isString(str), 'Base64Url.decodeArray: invalid input');
465 | return arrayFromString(this.decode(str));
466 | }
467 | }
468 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jose-jwe-jws",
3 | "version": "0.2.2",
4 | "description": "Library to encrypt and decrypt data in JSON Web Encryption (JWE) format and to sign data in JSON Web Signature (JWS) format. Leverages Browser's native web crypto API.",
5 | "keywords": [
6 | "crypto",
7 | "jwe",
8 | "jws",
9 | "encrypt",
10 | "decrypt",
11 | "sign",
12 | "verify"
13 | ],
14 | "license": "Apache-2.0",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/square/js-jose.git"
18 | },
19 | "scripts": {
20 | "build": "grunt",
21 | "lint": "eslint lib --fix",
22 | "test": "jest"
23 | },
24 | "devDependencies": {
25 | "@babel/core": "^7.4.5",
26 | "@babel/preset-env": "^7.4.5",
27 | "@webpack-cli/init": "^0.3.0",
28 | "babel": "^6.23.0",
29 | "babel-core": "^6.26.3",
30 | "babel-jest": "^25.1.0",
31 | "babel-loader": "^8.0.4",
32 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
33 | "coveralls": "^3.0.5",
34 | "eslint": "^6.0.1",
35 | "eslint-config-standard": "^14.1.0",
36 | "eslint-plugin-import": "^2.18.0",
37 | "eslint-plugin-jest": "^23.1.1",
38 | "eslint-plugin-node": "^11.0.0",
39 | "eslint-plugin-promise": "^4.2.1",
40 | "eslint-plugin-standard": "^4.0.0",
41 | "grunt": "^1.0.4",
42 | "grunt-contrib-concat": "^1.0.1",
43 | "grunt-contrib-uglify": "^4.0.1",
44 | "grunt-eslint": "^22.0.0",
45 | "grunt-karma": "^3.0.2",
46 | "grunt-karma-coveralls": "^2.5.4",
47 | "grunt-run": "^0.8.1",
48 | "grunt-webpack": "^3.1.3",
49 | "jest": "^26.1.0",
50 | "karma": "^4.4.1",
51 | "karma-chrome-launcher": "^3.1.0",
52 | "karma-coverage": "^2.0.1",
53 | "karma-qunit": "^4.0.0",
54 | "qunit": "^2.9.2",
55 | "terser": "^4.1.2",
56 | "uglifyjs-webpack-plugin": "^2.0.1",
57 | "webpack": "^4.20.2",
58 | "webpack-cli": "^3.1.1"
59 | },
60 | "typings": "jose-jwe-jws.d.ts",
61 | "main": "dist/jose.min.js"
62 | }
63 |
--------------------------------------------------------------------------------
/test/jose-jwe-test.js:
--------------------------------------------------------------------------------
1 | QUnit.test('encryption using RSAES OAEP and AES GCM (keys & IV from appendix-A.1)', function (assert) {
2 | const cryptographer = new Jose.WebCryptographer();
3 | cryptographer.createIV = () => {
4 | return new Uint8Array([227, 197, 117, 252, 2, 219, 233, 68, 180, 225, 77, 219]);
5 | };
6 | cryptographer.createCek = () => {
7 | const cek = new Uint8Array([177, 161, 244, 128, 84, 143, 225, 115, 63, 180, 3, 255, 107, 154, 212, 246, 138, 7, 110, 91, 112, 46, 34, 105, 47, 130, 203, 46, 122, 234, 64, 252]);
8 | return crypto.subtle.importKey('raw', cek, { name: 'AES-GCM' }, true, ['encrypt']);
9 | };
10 | const rsaKey =
11 | {
12 | 'kty': 'RSA',
13 | 'n': 'oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW' +
14 | 'cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S' +
15 | 'psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a' +
16 | 'sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS' +
17 | 'tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj' +
18 | 'YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw',
19 | 'e': 'AQAB',
20 | 'd': 'kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N' +
21 | 'WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9' +
22 | '3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk' +
23 | 'qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl' +
24 | 't3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd' +
25 | 'VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ',
26 | 'p': '1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-' +
27 | 'SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lf' +
28 | 'fNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0',
29 | 'q': 'wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBm' +
30 | 'UDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aX' +
31 | 'IWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc',
32 | 'dp': 'ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KL' +
33 | 'hMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827' +
34 | 'rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE',
35 | 'dq': 'Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCj' +
36 | 'ywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDB' +
37 | 'UfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis',
38 | 'qi': 'VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7' +
39 | 'AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3' +
40 | 'eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY'
41 | };
42 | const publicRsaKey = Jose.Utils.importRsaPublicKey(rsaKey, 'RSA-OAEP');
43 | const plaintext = 'The true sign of intelligence is not knowledge but imagination.';
44 | const encrypter = new Jose.JoseJWE.Encrypter(cryptographer, publicRsaKey);
45 | const cipherTextPromise = encrypter.encrypt(plaintext);
46 |
47 | assert.willEqual(cipherTextPromise.then(function (result) { return result.split('.').length; }),
48 | 5, 'got right number of components');
49 |
50 | assert.willEqual(cipherTextPromise.then(function (result) { return result.split('.')[0]; }),
51 | 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ',
52 | 'got expected header');
53 |
54 | assert.willEqual(cipherTextPromise.then(function (result) { return result.split('.')[2]; }),
55 | '48V1_ALb6US04U3b',
56 | 'got expected IV');
57 |
58 | assert.willEqual(cipherTextPromise.then(function (result) { return result.split('.')[3]; }),
59 | '5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A',
60 | 'got expected cipher text');
61 |
62 | assert.willEqual(cipherTextPromise.then(function (result) { return result.split('.')[4]; }),
63 | 'XFBoMYUZodetZdvTiFvSkQ',
64 | 'got expected tag');
65 |
66 | const privateRsaKey = Jose.Utils.importRsaPrivateKey(rsaKey, 'RSA-OAEP');
67 | const decrypter = new Jose.JoseJWE.Decrypter(cryptographer, privateRsaKey);
68 | const decryptedPlaintextPromise = cipherTextPromise.then(function (ciphertext) {
69 | return decrypter.decrypt(ciphertext);
70 | });
71 | assert.willEqual(decryptedPlaintextPromise, plaintext, 'Error: got expected decrypted plain text');
72 |
73 | // Modify string and check for invalid tag
74 | const macFailure = cipherTextPromise.then(function (ciphertext) {
75 | ciphertext = ciphertext.split('.');
76 | ciphertext.pop();
77 | ciphertext.push('WFBoMYUZodetZdvTiFvSkQ');
78 | return decrypter.decrypt(ciphertext.join('.'));
79 | });
80 | assert.wont(macFailure, 'invalid tag did not cause failure');
81 | });
82 |
83 | // We can't test appendix-A.2 because Chrome dropped support for RSAES-PKCS1-V1_5.
84 | QUnit.test('encryption using AES Key Wrap and AES_128_CBC_HMAC_SHA_256 (keys & IV from appendix-A.3)', function (assert) {
85 | const cryptographer = new Jose.WebCryptographer();
86 | cryptographer.setKeyEncryptionAlgorithm('A128KW');
87 | cryptographer.setContentEncryptionAlgorithm('A128CBC-HS256');
88 |
89 | cryptographer.createIV = function () {
90 | return new Uint8Array([3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101]);
91 | };
92 | cryptographer.createCek = function () {
93 | const cek = new Uint8Array([4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207]);
94 | return crypto.subtle.importKey('raw', cek, { name: 'AES-GCM' }, true, ['encrypt']);
95 | };
96 | let sharedKey = { 'kty': 'oct', 'k': 'GawgguFyGrWKav7AX4VKUg' };
97 | sharedKey = crypto.subtle.importKey('jwk', sharedKey, { name: 'AES-KW' }, true, ['wrapKey', 'unwrapKey']);
98 |
99 | const plaintext = 'Live long and prosper.';
100 | const encrypter = new Jose.JoseJWE.Encrypter(cryptographer, sharedKey);
101 | const cipherTextPromise = encrypter.encrypt(plaintext);
102 |
103 | assert.willEqual(cipherTextPromise.then(function (result) { return result.split('.').length; }),
104 | 5, 'got right number of components');
105 |
106 | assert.willEqual(cipherTextPromise.then(function (result) { return result.split('.')[0]; }),
107 | 'eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0',
108 | 'got expected header');
109 |
110 | assert.willEqual(cipherTextPromise.then(function (result) { return result.split('.')[1]; }),
111 | '6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ',
112 | 'got expected encrypted key');
113 |
114 | assert.willEqual(cipherTextPromise.then(function (result) { return result.split('.')[2]; }),
115 | 'AxY8DCtDaGlsbGljb3RoZQ',
116 | 'got expected IV');
117 |
118 | assert.willEqual(cipherTextPromise.then(function (result) { return result.split('.')[3]; }),
119 | 'KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY',
120 | 'got expected cipher text');
121 |
122 | assert.willEqual(cipherTextPromise.then(function (result) { return result.split('.')[4]; }),
123 | 'U0m_YmjN04DJvceFICbCVQ',
124 | 'got expected tag');
125 |
126 | const decrypter = new Jose.JoseJWE.Decrypter(cryptographer, sharedKey);
127 | const decryptedPlaintextPromise = cipherTextPromise.then(function (ciphertext) {
128 | return decrypter.decrypt(ciphertext);
129 | });
130 | assert.willEqual(decryptedPlaintextPromise, plaintext, 'got expected decrypted plain text');
131 |
132 | // Modify string and check for invalid tag
133 | const macFailure = cipherTextPromise.then(function (ciphertext) {
134 | ciphertext = ciphertext.split('.');
135 | ciphertext.pop();
136 | ciphertext.push('WEm_YmjN04DJvceFICbCVQ');
137 | return decrypter.decrypt(ciphertext.join('.'));
138 | });
139 | assert.wontEqual(macFailure, 'Error: decryptCiphertext: MAC failed.', 'invalid tag did not cause failure');
140 | });
141 |
142 | QUnit.test('key import', function (assert) {
143 | const cryptographer = new Jose.WebCryptographer();
144 | cryptographer.setContentEncryptionAlgorithm('A128CBC-HS256');
145 |
146 | // Key was generated with: `openssl genrsa 1024 | openssl rsa -text`
147 | const rsaKey = {
148 | 'e': 65537,
149 | 'n': '00:bf:53:bf:5b:19:bc:80:5c:88:15:d8:a5:f7:70:cf:c7:0c:aa:f5:b4:07:b8:d1:7b:3a:34:fc:b3:d8:25:46:86:58:5a:d5:69:49:8d:83:23:cd:e2:87:cd:4a:6d:39:a6:8a:dd:dd:c2:e4:d0:ce:7b:a3:d8:28:f8:f0:af:eb:87:2f:f5:c5:bc:5b:c2:08:ec:67:8a:96:57:bb:b6:6b:b5:a2:7d:ab:ff:67:13:09:1b:32:ba:89:c2:27:fd:1b:00:8b:1b:a0:44:3f:a5:4c:39:75:cd:dd:be:75:e6:c7:a5:4f:27:e3:e8:91:0b:1f:38:b7:f5:f7:03:88:f5:1e:b5',
150 | 'd': '0d:f4:61:c4:97:3f:f4:6c:cb:50:2c:99:0e:4f:20:18:78:88:0f:9b:ad:e4:81:02:e7:df:ed:7e:80:89:57:77:7d:02:43:06:86:e2:d7:69:c9:1e:78:a1:34:88:7a:e7:f6:c0:ef:e7:c3:20:a7:ae:c4:e8:83:34:84:f9:8f:c8:10:22:b5:19:ad:07:de:18:5d:d2:ff:27:c2:a7:42:1b:9a:6b:64:43:75:6e:e7:6d:5e:3a:77:fd:2a:65:18:a5:e9:46:79:ea:50:60:a9:27:21:7b:da:71:9b:00:0d:07:63:0f:e4:a7:f7:d7:3c:32:19:b2:73:a5:2b:24:8d:01',
151 | 'p': '00:de:16:f5:44:0a:bf:b5:4c:00:ce:1b:fe:e9:33:6b:47:66:0e:f9:a8:b1:44:ee:54:3f:1c:51:0d:36:fb:40:3a:53:61:46:3b:63:ee:6b:95:54:1d:b2:49:30:47:92:fd:b7:69:87:a5:f0:91:ab:16:ed:1d:0a:c3:ee:27:3c:71',
152 | 'q': '00:dc:8a:57:d7:1a:ba:2d:e9:07:39:bf:64:ef:b2:f2:91:20:6c:32:4b:0d:15:2e:78:ab:a5:99:c5:4f:25:40:cc:8a:9c:d4:f5:3d:ab:a7:e1:e6:d4:97:90:66:bc:fb:45:af:c6:84:1d:1c:56:f6:18:7a:b2:81:27:e3:fa:38:85',
153 | 'dp': '00:9b:4b:2e:61:4f:aa:d1:98:bd:8f:61:a0:13:6c:b2:fd:0f:ee:34:c0:b2:83:e2:aa:e2:1e:68:c6:76:c5:a5:19:a3:a8:07:36:0c:20:70:f5:d0:05:9b:de:f5:75:76:e1:16:59:22:52:f4:2e:c7:95:96:63:92:5d:82:af:c8:e1',
154 | 'dq': '74:1d:fb:05:ec:b2:9e:3d:95:6a:58:55:82:c7:4b:64:12:18:25:9a:d2:76:96:93:3e:7c:e0:ab:bc:72:36:dd:fb:15:7c:22:eb:a7:97:ab:1f:68:4b:ac:e2:0b:1a:99:a4:64:f7:66:84:67:5d:07:a2:82:9d:f2:2c:dc:b0:29',
155 | 'qi': '0c:c8:32:20:2e:df:d7:85:0f:e6:50:ec:ba:1b:6f:60:dd:18:79:3f:d4:ac:d8:6c:bf:05:d7:68:11:3f:2e:1b:26:d5:63:9d:c7:02:0f:e0:c2:70:49:c9:d1:7b:68:66:da:17:36:f5:f2:6b:4e:06:bd:db:29:04:c6:34:7a:e0'
156 | };
157 |
158 | const publicRsaKey = Jose.Utils.importRsaPublicKey(rsaKey, 'RSA-OAEP');
159 | const plaintext = 'Look deep into nature, and then you will understand everything better. --Albert Einstein';
160 | const encrypter = new Jose.JoseJWE.Encrypter(cryptographer, publicRsaKey);
161 | const cipherTextPromise = encrypter.encrypt(plaintext);
162 |
163 | const privateRsaKey = Jose.Utils.importRsaPrivateKey(rsaKey, 'RSA-OAEP');
164 | const decryptedPlaintextPromise = cipherTextPromise.then(function (ciphertext) {
165 | const decrypter = new Jose.JoseJWE.Decrypter(cryptographer, privateRsaKey);
166 | return decrypter.decrypt(ciphertext);
167 | });
168 | assert.willEqual(decryptedPlaintextPromise, plaintext, 'got expected decrypted plain text');
169 | });
170 |
171 | QUnit.test('extra headers', function (assert) {
172 | const cryptographer = new Jose.WebCryptographer();
173 | cryptographer.setKeyEncryptionAlgorithm('A128KW');
174 | let sharedKey = { 'kty': 'oct', 'k': 'GawgguFyGrWKav7AX4VKUg' };
175 | sharedKey = crypto.subtle.importKey('jwk', sharedKey, cryptographer.keyEncryption.id, true, ['wrapKey', 'unwrapKey']);
176 | const plaintext = 'I only went out for a walk and finally concluded to stay out till sundown, for going out, I found, was really going in. --John Muir';
177 |
178 | const encrypter = new Jose.JoseJWE.Encrypter(cryptographer, sharedKey);
179 | encrypter.addHeader('cty', 'text/plain');
180 | const cipherTextPromise = encrypter.encrypt(plaintext);
181 |
182 | const decrypter = new Jose.JoseJWE.Decrypter(cryptographer, sharedKey);
183 | const decryptedPlaintextPromise = cipherTextPromise.then(function (ciphertext) {
184 | return decrypter.decrypt(ciphertext);
185 | });
186 | assert.willEqual(decryptedPlaintextPromise.then(function (_) { return decrypter.getHeaders()['cty']; }), 'text/plain', 'got expected header');
187 | });
188 |
189 | QUnit.test('RSA-OAEP-256 with A256CBC-HS512', function (assert) {
190 | const cryptographer = new Jose.WebCryptographer();
191 | cryptographer.setKeyEncryptionAlgorithm('RSA-OAEP-256');
192 | cryptographer.setContentEncryptionAlgorithm('A256CBC-HS512');
193 | const keyPromise = crypto.subtle.generateKey({
194 | name: 'RSA-OAEP',
195 | hash: { name: 'SHA-256' },
196 | modulusLength: 2048,
197 | publicExponent: new Uint8Array([0x01, 0x00, 0x01])
198 | }, false, ['wrapKey', 'unwrapKey']);
199 | const plaintext = 'Always remember that you are absolutely unique. Just like everyone else. --Margaret Mead';
200 |
201 | const decryptedPlaintextPromise = keyPromise.then(function (key) {
202 | const encrypter = new Jose.JoseJWE.Encrypter(cryptographer, Promise.resolve(key.publicKey));
203 | return encrypter.encrypt(plaintext).then(function (ciphertext) {
204 | const decrypter = new Jose.JoseJWE.Decrypter(cryptographer, Promise.resolve(key.privateKey));
205 | return decrypter.decrypt(ciphertext);
206 | });
207 | });
208 | assert.willEqual(decryptedPlaintextPromise, plaintext, 'got expected decrypted plain text');
209 | });
210 |
211 | QUnit.test('A256KW with A128GCM', function (assert) {
212 | const cryptographer = new Jose.WebCryptographer();
213 | cryptographer.setKeyEncryptionAlgorithm('A256KW');
214 | cryptographer.setContentEncryptionAlgorithm('A128GCM');
215 |
216 | let sharedKey = { 'kty': 'oct', 'k': 'GawgguFyGrWKav7AX4VKUg' };
217 | sharedKey = crypto.subtle.importKey('jwk', sharedKey, cryptographer.keyEncryption.id, true, ['wrapKey', 'unwrapKey']);
218 |
219 | const plaintext = 'All generalizations are false, including this one. --Mark Twain';
220 | const encrypter = new Jose.JoseJWE.Encrypter(cryptographer, sharedKey);
221 | const cipherTextPromise = encrypter.encrypt(plaintext);
222 |
223 | const decryptedPlaintextPromise = cipherTextPromise.then(function (ciphertext) {
224 | const decrypter = new Jose.JoseJWE.Decrypter(cryptographer, sharedKey);
225 | return decrypter.decrypt(ciphertext);
226 | });
227 | assert.willEqual(decryptedPlaintextPromise, plaintext, 'got expected decrypted plain text');
228 | });
229 |
230 | QUnit.test('direct A256GCM', function (assert) {
231 | const cryptographer = new Jose.WebCryptographer();
232 | cryptographer.setKeyEncryptionAlgorithm('dir');
233 |
234 | const sharedJwk = { 'alg': 'A256GCM', 'ext': true, 'k': 'Wx5b1Z2nZFgZ8wrEXHo497ZWuvpej1m3PVCgTReiMic', 'key_ops': ['encrypt', 'decrypt'], 'kty': 'oct' };
235 | const plaintext = "Idealism increases in direct proportion to one's distance from the problem. --John Galsworthy";
236 | let sharedKey;
237 |
238 | var roundtrip = crypto.subtle.importKey('jwk', sharedJwk, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt'])
239 | .then(function (key) {
240 | sharedKey = key;
241 | var encrypter = new Jose.JoseJWE.Encrypter(cryptographer, sharedKey);
242 | return encrypter.encrypt(plaintext);
243 | })
244 | .then(function (ciphertext) {
245 | var decrypter = new Jose.JoseJWE.Decrypter(cryptographer, sharedKey);
246 | return decrypter.decrypt(ciphertext);
247 | });
248 |
249 | assert.willEqual(roundtrip, plaintext, 'got expected decrypted plain text');
250 | });
251 |
252 | QUnit.test('unicode encryption using RSAES OAEP and AES GCM (keys & IV from appendix-A.1)', function (assert) {
253 | const cryptographer = new Jose.WebCryptographer();
254 | cryptographer.createIV = function () {
255 | return new Uint8Array([227, 197, 117, 252, 2, 219, 233, 68, 180, 225, 77, 219]);
256 | };
257 | cryptographer.createCek = function () {
258 | const cek = new Uint8Array([177, 161, 244, 128, 84, 143, 225, 115, 63, 180, 3, 255, 107, 154, 212, 246, 138, 7, 110, 91, 112, 46, 34, 105, 47, 130, 203, 46, 122, 234, 64, 252]);
259 | return crypto.subtle.importKey('raw', cek, { name: 'AES-GCM' }, true, ['encrypt']);
260 | };
261 | const rsaKey =
262 | {
263 | 'kty': 'RSA',
264 | 'n': 'oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW' +
265 | 'cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S' +
266 | 'psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a' +
267 | 'sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS' +
268 | 'tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj' +
269 | 'YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw',
270 | 'e': 'AQAB',
271 | 'd': 'kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N' +
272 | 'WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9' +
273 | '3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk' +
274 | 'qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl' +
275 | 't3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd' +
276 | 'VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ',
277 | 'p': '1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-' +
278 | 'SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lf' +
279 | 'fNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0',
280 | 'q': 'wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBm' +
281 | 'UDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aX' +
282 | 'IWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc',
283 | 'dp': 'ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KL' +
284 | 'hMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827' +
285 | 'rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE',
286 | 'dq': 'Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCj' +
287 | 'ywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDB' +
288 | 'UfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis',
289 | 'qi': 'VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7' +
290 | 'AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3' +
291 | 'eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY'
292 | };
293 | const publicRsaKey = Jose.Utils.importRsaPublicKey(rsaKey, 'RSA-OAEP');
294 | const plaintext = '古池や・蛙飛びこむ・水の音 --松尾芭蕉';
295 | const encrypter = new Jose.JoseJWE.Encrypter(cryptographer, publicRsaKey);
296 | const cipherTextPromise = encrypter.encrypt(plaintext);
297 |
298 | const privateRsaKey = Jose.Utils.importRsaPrivateKey(rsaKey, 'RSA-OAEP');
299 | const decrypter = new Jose.JoseJWE.Decrypter(cryptographer, privateRsaKey);
300 | const decryptedPlaintextPromise = cipherTextPromise.then(function (ciphertext) {
301 | return decrypter.decrypt(ciphertext);
302 | });
303 | assert.willEqual(decryptedPlaintextPromise, plaintext, 'Error: got expected decrypted plain text');
304 | });
305 |
306 | QUnit.test('unicode direct A256GCM', function (assert) {
307 | const cryptographer = new Jose.WebCryptographer();
308 | cryptographer.setKeyEncryptionAlgorithm('dir');
309 |
310 | const sharedJwk = { 'alg': 'A256GCM', 'ext': true, 'k': 'Wx5b1Z2nZFgZ8wrEXHo497ZWuvpej1m3PVCgTReiMic', 'key_ops': ['encrypt', 'decrypt'], 'kty': 'oct' };
311 | const plaintext = '古池や・蛙飛びこむ・水の音 --松尾芭蕉';
312 | let sharedKey;
313 |
314 | var roundtrip = crypto.subtle.importKey('jwk', sharedJwk, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt'])
315 | .then(function (key) {
316 | sharedKey = key;
317 | const encrypter = new Jose.JoseJWE.Encrypter(cryptographer, sharedKey);
318 | return encrypter.encrypt(plaintext);
319 | })
320 | .then(function (ciphertext) {
321 | const decrypter = new Jose.JoseJWE.Decrypter(cryptographer, sharedKey);
322 | return decrypter.decrypt(ciphertext);
323 | });
324 |
325 | assert.willEqual(roundtrip, plaintext, 'got expected decrypted plain text');
326 | });
327 |
328 | QUnit.test('setting invalid algorithm', function (assert) {
329 | const cryptographer = new Jose.WebCryptographer();
330 | assert.throws(function () { cryptographer.setKeyEncryptionAlgorithm('blah'); }, 'got exception when setting invalid algorithm');
331 | });
332 |
333 | QUnit.test('importing public key', function (assert) {
334 | let rsaKey = {
335 | 'e': 65537
336 | };
337 | assert.throws(function () { Jose.Utils.importRsaPublicKey(rsaKey, 'RSA-OAEP'); }, 'got exception when importing invalid key');
338 |
339 | rsaKey = {
340 | 'alg': 'foo',
341 | 'e': 65537,
342 | 'n': 'bf:53:bf:5b:19:bc:80'
343 | };
344 | assert.throws(function () { Jose.Utils.importRsaPublicKey(rsaKey, 'RSA-OAEP'); }, 'got exception when importing invalid key');
345 |
346 | rsaKey = {
347 | 'e': 65537,
348 | 'n': []
349 | };
350 | assert.throws(function () { Jose.Utils.importRsaPublicKey(rsaKey, 'RSA-OAEP'); }, 'got exception when importing invalid key');
351 | });
352 |
353 | QUnit.test('various decryption failures', function (assert) {
354 | const cryptographer = new Jose.WebCryptographer();
355 | cryptographer.setKeyEncryptionAlgorithm('A128KW');
356 |
357 | let sharedKey = { 'kty': 'oct', 'k': 'GawgguFyGrWKav7AX4VKUg' };
358 | sharedKey = crypto.subtle.importKey('jwk', sharedKey, { name: 'AES-KW' }, true, ['wrapKey', 'unwrapKey']);
359 |
360 | const plaintext = 'A yawn is a silent scream for coffee. --unknown';
361 | const encrypter = new Jose.JoseJWE.Encrypter(cryptographer, sharedKey);
362 | const cipherTextPromise = encrypter.encrypt(plaintext);
363 |
364 | const decrypter = new Jose.JoseJWE.Decrypter(cryptographer, sharedKey);
365 | const decryptTruncatedInputPromise = cipherTextPromise.then(function (ciphertext) {
366 | ciphertext = ciphertext.split('.').slice(1).join('.');
367 | return decrypter.decrypt(ciphertext);
368 | });
369 | assert.wontEqual(decryptTruncatedInputPromise, 'Error: decrypt: invalid input', 'truncated input did not cause failure');
370 |
371 | const decryptMissingAlgPromise = cipherTextPromise.then(function (ciphertext) {
372 | ciphertext = ciphertext.split('.');
373 | ciphertext[0] = 'eyJlbmMiOiJBMjU2R0NNIn0=';
374 | return decrypter.decrypt(ciphertext.join('.'));
375 | });
376 | assert.wontEqual(decryptMissingAlgPromise, 'Error: decrypt: missing alg', 'missing alg in header did not cause failure');
377 |
378 | const decryptMissingEncPromise = cipherTextPromise.then(function (ciphertext) {
379 | ciphertext = ciphertext.split('.');
380 | ciphertext[0] = 'eyJhbGciOiJBMTI4S1cifQ==';
381 | return decrypter.decrypt(ciphertext.join('.'));
382 | });
383 | assert.wontEqual(decryptMissingEncPromise, 'Error: decrypt: missing enc', 'missing enc in header did not cause failure');
384 |
385 | const decryptCritHeaderPromise = cipherTextPromise.then(function (ciphertext) {
386 | ciphertext = ciphertext.split('.');
387 | ciphertext[0] = 'eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiQTEyOEtXIiwiY3JpdCI6ImJsYWgifQ==';
388 | return decrypter.decrypt(ciphertext.join('.'));
389 | });
390 | assert.wontEqual(decryptCritHeaderPromise, 'Error: decrypt: crit is not supported', 'crit in header did not cause failure');
391 |
392 | const decryptInvalidIvPromise = cipherTextPromise.then(function (ciphertext) {
393 | ciphertext = ciphertext.split('.');
394 | ciphertext[2] = 'w4kHDJEum_fHW-U';
395 | return decrypter.decrypt(ciphertext.join('.'));
396 | });
397 | assert.wontEqual(decryptInvalidIvPromise, 'Error: decryptCiphertext: invalid IV', 'invalid IV did not cause failure');
398 |
399 | const cryptographer2 = new Jose.WebCryptographer();
400 | cryptographer2.setKeyEncryptionAlgorithm('A128KW');
401 | cryptographer2.createIV = function () { return new Uint8Array(new Array(11)); };
402 | const encrypter2 = new Jose.JoseJWE.Encrypter(cryptographer2, sharedKey);
403 | assert.wontEqual(encrypter2.encrypt(plaintext), 'Error: invalid IV length', "fails to encrypt when we don't have the right IV length");
404 | });
405 |
406 | QUnit.test('caniuse', function (assert) {
407 | assert.equal(Jose.caniuse(), true);
408 | });
409 |
--------------------------------------------------------------------------------
/test/jose-jws-ecdsa-test.js:
--------------------------------------------------------------------------------
1 | QUnit.test('signature using ECDSA P-256 with SHA-256 (keys from appendix-A.3.1)', function (assert) {
2 | const ecKey = {
3 | 'kty': 'EC',
4 | 'crv': 'P-256',
5 | 'x': 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
6 | 'y': 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
7 | 'd': 'jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI'
8 | };
9 | const cryptographer = new Jose.WebCryptographer();
10 | cryptographer.setContentSignAlgorithm('ES256');
11 | const signer = new Jose.JoseJWS.Signer(cryptographer);
12 | const plaintext = 'The true sign of intelligence is not knowledge but imagination.';
13 | const verified = signer
14 | .addSigner(ecKey, 'A.3.1')
15 | .then(function () {
16 | return signer.sign(plaintext);
17 | })
18 | .then(function (signature) {
19 | const verifier = new Jose.JoseJWS.Verifier(cryptographer, signature);
20 | delete ecKey.d;
21 | return verifier.addRecipient(ecKey, 'A.3.1').then(function () { return verifier.verify(); });
22 | })
23 | .then(function (result) {
24 | return result[0].verified && result[0].payload === plaintext;
25 | });
26 | assert.willEqual(verified, true, 'JWS message has been correctly verified');
27 | });
28 |
--------------------------------------------------------------------------------
/test/jose-jws-rsa-test.js:
--------------------------------------------------------------------------------
1 | QUnit.test('signature using RSASSA-PKCS1-v1_5 with SHA-256 (keys from appendix-A.2.1)', function (assert) {
2 | const rsaKey = {
3 | 'n': 'A1:F8:16:0A:E2:E3:C9:B4:65:CE:8D:2D:65:62:63:36:2B:92:7D:BE:29:E1:F0:24:77:FC:16:25:CC:90:A1:36:E3:8B:D9:34:97:C5:B6:EA:63:DD:77:11:E6:7C:74:29:F9:56:B0:FB:8A:8F:08:9A:DC:4B:69:89:3C:C1:33:3F:53:ED:D0:19:B8:77:84:25:2F:EC:91:4F:E4:85:77:69:59:4B:EA:42:80:D3:2C:0F:55:BF:62:94:4F:13:03:96:BC:6E:9B:DF:6E:BD:D2:BD:A3:67:8E:EC:A0:C6:68:F7:01:B3:8D:BF:FB:38:C8:34:2C:E2:FE:6D:27:FA:DE:4A:5A:48:74:97:9D:D4:B9:CF:9A:DE:C4:C7:5B:05:85:2C:2C:0F:5E:F8:A5:C1:75:03:92:F9:44:E8:ED:64:C1:10:C6:B6:47:60:9A:A4:78:3A:EB:9C:6C:9A:D7:55:31:30:50:63:8B:83:66:5C:6F:6F:7A:82:A3:96:70:2A:1F:64:1B:82:D3:EB:F2:39:22:19:49:1F:B6:86:87:2C:57:16:F5:0A:F8:35:8D:9A:8B:9D:17:C3:40:72:8F:7F:87:D8:9A:18:D8:FC:AB:67:AD:84:59:0C:2E:CF:75:93:39:36:3C:07:03:4D:6F:60:6F:9E:21:E0:54:56:CA:E5:E9:A1',
4 | 'e': 65537,
5 | 'd': '12:AE:71:A4:69:CD:0A:2B:C3:7E:52:6C:45:00:57:1F:1D:61:75:1D:64:E9:49:70:7B:62:59:0F:9D:0B:A5:7C:96:3C:40:1E:3F:CF:2F:2C:D3:BD:EC:88:E5:03:BF:C6:43:9B:0B:28:C8:2F:7D:37:97:67:1F:52:13:EE:D8:C1:5A:25:D8:D5:CE:A0:02:5E:E3:AB:2E:8B:7F:79:21:6F:C6:3B:EA:56:27:53:B4:06:44:C6:A1:51:27:D9:B2:95:45:40:A0:BB:E1:A3:05:56:98:2D:4E:9F:DE:5F:64:25:F1:4D:4B:71:34:41:B5:5D:C7:3B:9B:4A:ED:CC:92:AC:E3:92:7E:37:F5:7D:0C:FD:5E:75:81:FA:51:2C:8F:49:61:A9:EB:0B:80:F8:A8:07:46:72:8A:55:FF:46:47:1F:34:25:06:3B:9D:53:64:2F:5E:DE:1E:84:D6:13:08:1A:FA:5C:22:D0:51:28:5B:D6:3B:94:3B:56:5D:89:8A:05:68:54:13:E5:3C:3C:6C:65:25:FF:1F:E3:4E:3D:DC:70:F0:D5:64:50:FD:A4:8B:A1:2E:10:4E:9D:EB:9F:B8:18:81:E1:C4:BD:F2:5D:92:47:F4:50:C8:65:92:79:68:E7:73:34:F4:41:4F:75:A7:50:E1:39:54:6E:3A:8A:73:9D',
6 | 'p': 'E0:1C:C4:10:EB:48:A6:65:5D:54:46:4D:0A:A4:BB:6D:A0:B8:72:B7:74:A6:A9:D1:1F:FE:48:07:78:F0:DD:B7:31:1A:7E:90:2E:F9:C4:B5:F7:54:76:26:2B:A8:17:6C:B3:59:79:F0:14:37:2A:1A:28:29:E4:13:6B:D0:01:D0:7C:3F:3F:2E:4F:25:D4:C9:34:F6:3C:71:09:FB:A2:E6:76:28:A0:DC:9A:73:C6:05:8E:6D:EF:22:DC:D5:09:50:E7:11:DD:C1:6D:55:86:F2:A7:FA:75:EA:84:94:C1:80:83:E0:B6:57:42:F8:A0:D5:E7:3F:B2:D9:70:F0:19:47',
7 | 'q': 'B9:03:C4:7E:09:95:B6:32:F4:53:2C:B1:F3:C1:99:14:5D:5F:3A:E9:C7:DF:EE:DC:7A:92:A6:B4:7E:29:C6:1B:42:A0:7A:A9:5A:64:FC:60:09:99:C5:A7:B0:1E:01:C0:8C:B6:2C:A7:56:52:7B:BC:C5:60:78:FB:0B:AB:A5:ED:38:DE:2D:07:99:0F:F6:30:0F:AE:AD:EB:6C:03:9A:97:BF:1E:50:7E:4E:70:40:FA:78:DB:82:57:C8:B1:12:ED:5E:59:C3:CD:09:2F:E5:D4:E5:B5:12:75:D4:12:81:07:2A:5E:78:5E:BA:F3:DA:E3:70:92:03:13:3F:51:59:D7',
8 | 'dp': '07:02:9F:57:70:24:AB:9F:CC:15:90:C5:64:29:D6:FB:0C:E5:F8:20:A8:F3:75:A8:66:F9:CB:43:00:93:78:3B:FC:BB:39:6E:45:29:E6:EF:52:37:40:22:DD:86:BA:84:D9:EF:58:93:1B:EE:C5:D0:5F:A5:3F:CF:23:B6:33:F8:53:8A:9E:ED:51:E8:7B:09:78:30:A3:9F:5D:92:93:7B:E6:02:4B:55:DB:36:F7:E0:C0:9D:CB:B7:29:75:38:7F:61:5A:FB:B6:CB:36:BB:AB:E7:79:3C:2B:03:CE:AB:66:DB:B9:31:BA:F5:0B:55:EC:9A:F9:31:1D:00:1D:62:8D',
9 | 'dq': '87:FF:7A:FA:62:B5:47:FE:E0:96:1B:2E:9B:CD:5D:67:18:D3:9D:8C:A7:3D:B6:69:1F:38:99:8D:E7:87:71:76:2C:5D:A6:8C:C2:43:A5:38:3B:16:6B:B2:3D:C5:70:E8:47:06:CA:80:1E:F5:F6:BA:E6:23:6A:0A:AF:A3:77:0E:8F:54:D1:A8:DA:1C:5F:8D:28:99:F0:82:33:1D:DB:0F:5C:8F:3D:FF:FA:4C:8D:97:10:2B:DA:FE:08:2A:11:8D:A6:63:39:88:88:0E:4B:55:59:9C:E6:7A:F2:6E:BF:A5:B2:C1:4A:9D:E7:B2:C4:DD:96:AB:DD:D2:D2:22:4C:75',
10 | 'qi': '21:87:7B:0C:73:A1:AD:6B:F1:93:03:D0:B1:13:36:B4:E8:2B:8D:B7:2B:7E:FB:50:26:2A:5D:F8:39:5C:C7:25:6E:B8:CF:6C:40:B7:60:8D:59:36:A3:2D:BA:17:41:26:A5:27:06:2E:AD:8C:A3:05:FB:7E:17:7F:40:94:37:C9:DC:B9:71:8E:D8:20:18:BC:EF:0F:77:20:A2:C4:75:F9:DB:26:DA:0E:3C:B5:16:D0:84:EB:25:17:8E:82:8D:5C:AB:D4:9B:B3:16:1A:C0:18:1F:A7:F8:21:E8:0E:7A:D3:79:36:F9:94:30:54:AD:09:29:21:EE:2C:59:2E:43:75'
11 | };
12 | const cryptographer = new Jose.WebCryptographer();
13 | const signer = new Jose.JoseJWS.Signer(cryptographer);
14 | const plaintext = 'The true sign of intelligence is not knowledge but imagination.';
15 | cryptographer.setContentSignAlgorithm('RS256');
16 | const adsPromise = signer.addSigner(rsaKey, 'A.2.1');
17 |
18 | assert.willEqual(adsPromise.then(function () {
19 | return signer.sign(plaintext, null, {}).then(function (signature) {
20 | const b64uxp = /^[a-z0-9_-]+$/i;
21 | return b64uxp.test(signature.protected) &&
22 | b64uxp.test(signature.payload) &&
23 | b64uxp.test(signature.signature);
24 | });
25 | }), true, 'got right JSON serialization format');
26 |
27 | assert.willEqual(adsPromise.then(function () {
28 | return signer.sign(plaintext, null, {}).then(function (signature) {
29 | const xp = /([a-z0-9_-]+\.){2}[a-z0-9_-]+/i;
30 | return xp.test(signature.CompactSerialize());
31 | });
32 | }), true, 'got correct compact serialization format');
33 |
34 | assert.willEqual(adsPromise.then(function () {
35 | return signer.sign(plaintext, null, {}).then(function (signature) {
36 | const verifier = new Jose.JoseJWS.Verifier(cryptographer, signature);
37 | return verifier.addRecipient(rsaKey, 'A.2.1').then(function () {
38 | return verifier.verify().then(function (result) {
39 | return result.filter(function (value) {
40 | return !value.verified;
41 | }).length === 0;
42 | });
43 | });
44 | });
45 | }), true, 'JWS message has been correctly verified');
46 |
47 | assert.willEqual(adsPromise.then(function () {
48 | return signer.sign(plaintext, null, {}).then(function (signature) {
49 | const verifier = new Jose.JoseJWS.Verifier(cryptographer, signature);
50 | return verifier.addRecipient(rsaKey, 'A.2.1').then(function () {
51 | return verifier.verify().then(function (result) {
52 | return result[0].verified && result[0].payload === plaintext;
53 | });
54 | });
55 | });
56 | }), true, 'JWS message payload matched expected plaintext');
57 | });
58 |
59 | QUnit.test('keyfinder', function (assert) {
60 | const prvjwk = {
61 | 'alg': 'RS256',
62 | 'd': 'l0-Bq8hePldybBsBUon7eGgwD53XfzZNXKB9yq6V6DyOxpAP2KpASdCcTYlDWJb_tqdWSjqxDuWox0l6S3l-f6xjWu5Du1u0DATjzgtdGyrNw-yX0SZlVq_Nbotw0-0GT1VpyUmvNvCS_gy9tONDv4PIrfMe0fwaRoMFmVo40o18At6y4DMxWkH63wUTtXYFlGLH9dNd1yq4odIoqWYpA_aE0pxz1aGpqJzY0_-mXfB8oH-uAY6a474kAJV2PE25_mLxFRmT0qB-0qcpjoTn1SVQfMRaeWvh4YzU-o30mCKWmDgU5Obc0fK9buMoM3uQKFUkRNsCOg1o1_cjx6epsQ',
63 | 'dp': 'R5dAKUQsBjKUCcoczkn5KOI3du5SU0F_p_2lpogHtR5vJcTZjluSm2lB7qgFheB80ZcVPtFiRN5h9A8qWmhHh5DIHibaQ0J_dVLH4CmmlKOBORzNJPicy54G99gsBzvTeqZ8_CYsrj1fRREMJxiHRk1uqe1VQ19nZQDZwrd8Q5E',
64 | 'dq': 'pfXdH9NJdsMkKVUKruaw4K5jYONKVz499dbdTT8LgkkxoQy1Tvm_C1xJYO4KiK1qvjV9_jazqvGkvdxddPQOIc6bOmtm_UfSoMAdussi2spTK6eO5EAzDRcPfXqdgSc7ihjBdklma33q9iMhtIA2SE3IDG0QXswjKDfQ435n8Qk',
65 | 'e': 'AQAB',
66 | 'ext': true,
67 | 'key_ops': ['sign'],
68 | 'kid': '76d4d722-4398-4fc4-a47e-df3d15de7fab',
69 | 'kty': 'RSA',
70 | 'n': 'xw6vxkASH4Jz23XLQMAj3FB55Dq4xCBwaTtUZpKSddMx3hvVeZb_nWv2Ogu18vL-x8LaYGYYG_LSirVA_tJ3ArEw_rqQDJOvsHTAuGrSOJkusJeC46Z5ZP0TndpunV6-04rxltjUVriwO8XzZdH4RnX_kSUEz3qeUc5j-OhTj59sG1-Os51iYiUEwDa6z8fn20wHRtEvPzG0MNQBX58CPuIM9bS8eNOZKUG-oVWAoWcbMfqEE5D8YsW_cZUWY9OLRa5C6xNRMfwlxYeMedJgehXJmxqaxSn0fe6-VZk9qcJDLiWg6vy1NYOJ8UOgtKtSPr7uL4Kc8K8akYVq-qHXAQ',
71 | 'p': '7ZYCLz4jFWW2ody4tPPOrDJn7dKXvkytUC6jL0_iGHNgjbcV3zEDkJC9eJWZaCXIwFKjnzlP4iQTHRqvE8pOFtHLaCj4MpEe9EIg6cZSoc866dVAD9aKS74TeCIQ5YBEvOAa_3h3Uecui0g_Xd0Hm3cr6VcNIMer2OffRmGTJcU',
72 | 'q': '1nw5av78m5eTQv9IgOuxzvy9NQfm2hw8eq2eBduYalIvpa_3QTWXzvJ7_iOomNzZyEm6AjmKQu1Ualfgqq5YDU-jKBY4Pd27SQXHFwO75dsOZc7Xn2QQbHLNNOL8vNSy9ZeEkvDJtwX1lJ9x9XLXztRqJZr_UqS5680zCPO__A0',
73 | 'qi': 'Zv9pwzMgWoAFu9hwp-foLoCYaFdVGj8vV-QovqW507TzhRiZuaP9SsYEF5ZizhEDLtjSxX2KycQUt-EEP9piUvgPiF_K7zXmQ-clvh_qzXrOE4UhfXbAjLFCUBf8YcgFsCYzvvqom3ktxOC0FPhfJr7s2EXQIQjzK5Drm-fZTZc'
74 | };
75 | const pubjwk = {
76 | 'alg': 'RS256',
77 | 'e': 'AQAB',
78 | 'ext': true,
79 | 'key_ops': ['verify'],
80 | 'kid': '76d4d722-4398-4fc4-a47e-df3d15de7fab',
81 | 'kty': 'RSA',
82 | 'n': 'xw6vxkASH4Jz23XLQMAj3FB55Dq4xCBwaTtUZpKSddMx3hvVeZb_nWv2Ogu18vL-x8LaYGYYG_LSirVA_tJ3ArEw_rqQDJOvsHTAuGrSOJkusJeC46Z5ZP0TndpunV6-04rxltjUVriwO8XzZdH4RnX_kSUEz3qeUc5j-OhTj59sG1-Os51iYiUEwDa6z8fn20wHRtEvPzG0MNQBX58CPuIM9bS8eNOZKUG-oVWAoWcbMfqEE5D8YsW_cZUWY9OLRa5C6xNRMfwlxYeMedJgehXJmxqaxSn0fe6-VZk9qcJDLiWg6vy1NYOJ8UOgtKtSPr7uL4Kc8K8akYVq-qHXAQ'
83 | };
84 | const plaintext = "When you make the finding yourself - even if you're the last person on Earth to see the light - you'll never forget it. --Carl Sagan";
85 |
86 | const pubjwks = {};
87 | pubjwks[pubjwk.kid] = pubjwk;
88 |
89 | const keyfinder = function (kid) {
90 | const jwk = pubjwks[kid];
91 | if (jwk) {
92 | return window.crypto.subtle.importKey('jwk', jwk,
93 | { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-256' } },
94 | true, ['verify']);
95 | } else {
96 | return Promise.reject(Error('unknown sender key id ' + kid));
97 | }
98 | };
99 |
100 | let cryptographer = new Jose.WebCryptographer();
101 | cryptographer.setContentSignAlgorithm('RS256');
102 | let signer = new Jose.JoseJWS.Signer(cryptographer);
103 |
104 | assert.willEqual(signer.addSigner(prvjwk, prvjwk.kid)
105 | .then(function () {
106 | return signer.sign(plaintext);
107 | })
108 | .then(function (signed) {
109 | const verifier = new Jose.JoseJWS.Verifier(cryptographer, signed, keyfinder);
110 | return verifier.verify().then(function (result) {
111 | return result.filter(function (value) {
112 | return !value.verified;
113 | }).length === 0;
114 | });
115 | }),
116 | true, 'keyfinder found key');
117 |
118 | cryptographer = new Jose.WebCryptographer();
119 | cryptographer.setContentSignAlgorithm('RS256');
120 | signer = new Jose.JoseJWS.Signer(cryptographer);
121 |
122 | assert.wont(signer.addSigner(prvjwk, prvjwk.kid)
123 | .then(function (prvkey) {
124 | cryptographer = new Jose.WebCryptographer();
125 | cryptographer.setContentSignAlgorithm('RS256');
126 | signer = new Jose.JoseJWS.Signer(cryptographer);
127 | return signer.addSigner(prvkey, 'unknown');
128 | })
129 | .then(function () {
130 | return signer.sign(plaintext);
131 | })
132 | .then(function (signed) {
133 | const verifier = new Jose.JoseJWS.Verifier(cryptographer, signed, keyfinder);
134 | return verifier.verify().then(function (result) {
135 | return result.filter(function (value) {
136 | return !value.verified;
137 | }).length === 0;
138 | });
139 | }), 'keyfinder rejected properly on unknown key');
140 | });
141 |
--------------------------------------------------------------------------------
/test/qunit-promises.js:
--------------------------------------------------------------------------------
1 | (function (QUnit) {
2 | // jQuery promise objects have .then and .always methods
3 | // Q promise objects have .then and .finally methods
4 | function verifyPromise (promise) {
5 | if (!promise) {
6 | throw new Error('expected a promise object');
7 | }
8 | if (typeof promise.then !== 'function') {
9 | throw new Error('promise object does not have .then function');
10 | }
11 | }
12 |
13 | QUnit.extend(QUnit.assert, {
14 | // resolved promises
15 | will: function (promise, message) {
16 | verifyPromise(promise);
17 |
18 | const assert = this;
19 | const done = assert.async();
20 | promise.then(function () {
21 | assert.pushResult({
22 | result: true,
23 | actual: undefined,
24 | expected: undefined,
25 | message: message
26 | });
27 | done();
28 | }, function () {
29 | assert.pushResult({
30 | result: false,
31 | actual: undefined,
32 | expected: undefined,
33 | message: 'promise rejected (but should have been resolved)'
34 | });
35 | done();
36 | });
37 | },
38 |
39 | willEqual: function (promise, expected, message) {
40 | verifyPromise(promise);
41 |
42 | const assert = this;
43 | const done = assert.async();
44 | promise.then(function (actual) {
45 | assert.pushResult({
46 | result: actual == expected,
47 | actual: actual,
48 | expected: expected,
49 | message: message
50 | });
51 | done();
52 | }, function (actual) {
53 | assert.pushResult({
54 | result: false,
55 | actual: actual,
56 | expected: expected,
57 | message: 'promise rejected (but should have been resolved)'
58 | });
59 | done();
60 | });
61 | },
62 |
63 | willDeepEqual: function (promise, expected, message) {
64 | var always = verifyPromise(promise);
65 |
66 | const assert = this;
67 | const done = assert.async();
68 | promise.then(function (actual) {
69 | if (typeof QUnit.equiv !== 'function') {
70 | throw new Error('Missing QUnit.equiv function');
71 | }
72 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
73 | assert.pushResult({
74 | result: QUnit.equiv(actual, expected),
75 | actual: actual,
76 | expected: expected,
77 | message: message
78 | });
79 | done();
80 | }, function (actual) {
81 | assert.pushResult({
82 | result: false,
83 | actual: actual,
84 | expected: expected,
85 | message: 'promise rejected (but should have been resolved)'
86 | });
87 | done();
88 | });
89 | },
90 |
91 | // rejected promises
92 | wont: function (promise, message) {
93 | var always = verifyPromise(promise);
94 |
95 | const assert = this;
96 | const done = assert.async();
97 | promise.then(function () {
98 | assert.pushResult({
99 | result: false,
100 | actual: undefined,
101 | expected: undefined,
102 | message: 'promise resolved (but should have been rejected)'
103 | });
104 | done();
105 | }, function () {
106 | assert.pushResult({
107 | result: true,
108 | actual: undefined,
109 | expected: undefined,
110 | message: message
111 | });
112 | done();
113 | });
114 | },
115 |
116 | wontEqual: function (promise, expected, message) {
117 | var always = verifyPromise(promise);
118 |
119 | const assert = this;
120 | const done = assert.async();
121 | promise.then(function (actual) {
122 | assert.pushResult({
123 | result: false,
124 | actual: actual,
125 | expected: expected,
126 | message: 'promise resolved (but should have been rejected)'
127 | });
128 | done();
129 | }, function (actual) {
130 | assert.pushResult({
131 | result: actual == expected,
132 | actual: actual,
133 | expected: expected,
134 | message: message
135 | });
136 | done();
137 | });
138 | }
139 | });
140 | }(QUnit));
141 |
--------------------------------------------------------------------------------
/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const path = require('path')
3 |
4 | module.exports = {
5 | module: {
6 | rules: [{
7 | use: {
8 | loader: 'babel-loader',
9 | options: {
10 | presets: ['@babel/preset-env']
11 | }
12 | },
13 | test: /\.js$/
14 | }]
15 | },
16 |
17 | entry: './lib/jose-core',
18 |
19 | output: {
20 | filename: 'jose.js',
21 | library:'Jose',
22 | libraryTarget: 'var',
23 | path: path.resolve(__dirname, 'dist')
24 | },
25 |
26 | mode: 'development',
27 | devtool: 'source-map',
28 |
29 | optimization: {
30 | splitChunks: {
31 | cacheGroups: {
32 | vendors: {
33 | priority: -10,
34 | test: /[\\/]node_modules[\\/]/
35 | }
36 | },
37 |
38 | chunks: 'async',
39 | minChunks: 1,
40 | minSize: 30000,
41 | name: true
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const path = require('path')
3 |
4 | module.exports = {
5 | module: {
6 | rules: [{
7 | use: {
8 | loader: 'babel-loader',
9 | options: {
10 | presets: ['@babel/preset-env']
11 | }
12 | },
13 | test: /\.js$/
14 | }]
15 | },
16 |
17 | entry: './lib/jose-core',
18 |
19 | output: {
20 | filename: 'jose.min.js',
21 | library: 'Jose',
22 | libraryTarget: 'umd',
23 | path: path.resolve(__dirname, 'dist')
24 | },
25 |
26 | mode: 'production',
27 |
28 | optimization: {
29 | splitChunks: {
30 | cacheGroups: {
31 | vendors: {
32 | priority: -10,
33 | test: /[\\/]node_modules[\\/]/
34 | }
35 | },
36 |
37 | chunks: 'async',
38 | minChunks: 1,
39 | minSize: 30000,
40 | name: true
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------