Compare commits

...

44 Commits

Author SHA1 Message Date
7a97b360be v2.0.8: bugfix for node v6 2019-06-03 03:27:38 -06:00
92463a6ec2 bugfix: throw error when key gen entirely fails 2019-06-03 01:17:25 -06:00
be782fbdfa v2.0.6: bump npm latest 2019-03-15 09:14:39 -06:00
e822c625c5 v2.0.5: fixed case typo (didn't affect tests on my mac) 2019-03-14 21:40:04 -06:00
fd6a46e88a v2.0.4: use keypairs (over rasha) 2019-03-14 12:07:48 -06:00
2e7134dd8a use keypairs (over rasha) 2019-03-14 11:59:31 -06:00
4d11f99d88 v2.0.3: show error message rather than throwing when attempting ursa 2018-12-28 08:39:25 -07:00
0b5850cf6a v2.0.2: add node engine restriction 2018-12-17 01:19:45 -07:00
ef8f61b3c3 v2.0.1: version bump (bad prior publish) 2018-12-16 03:10:27 -07:00
3db8b49929 v2.0.0: remove old deps, light cleanup 2018-12-16 03:08:59 -07:00
7580d700bf v1.9.0: switch to Rasha.js for everything except backcompat 2018-12-16 02:54:57 -07:00
70bfcd04bf export both Private and Public, and PEMs and JWKs 2018-12-15 23:16:27 -07:00
0a7734c6da copy over ARM/MIPS warning 2018-12-15 22:50:16 -07:00
c3ceffd2e2 use ursa-optional or ursa 2018-12-15 22:39:14 -07:00
ff05912dfe add new keypair generation 2018-12-15 22:31:17 -07:00
e26d479cf0 v1.6.1: make ursa-optional itself optional 2018-11-04 23:10:47 -07:00
3f7339a491 v1.6.0: use non-deprecated features, update deps 2018-08-16 18:42:47 -06:00
718c3eb42f Merge branch 'master' of mkg20001/rsa-compat.js into master 2018-08-17 00:33:36 +00:00
Maciej Krüger
d824611283
feat: Use ursa-optional as dependency 2018-08-07 11:53:35 +02:00
aae4ad4e3f v1.5.1: don't cause ursa-optional to install due to error messages 2018-07-13 04:33:34 -06:00
023b0b7948 v1.5.0: add ursa-optional, deprecate non-abstract function sig 2018-07-13 04:17:04 -06:00
3d6b6e7f5e show correct download count 2018-07-10 19:55:08 +00:00
f34332b5a6 Update 'README.md' 2018-07-10 19:52:06 +00:00
c145c8afc1 Merge branch 'feat/ursa-optional' of mkg20001/rsa-compat.js into master 2018-07-10 19:50:13 +00:00
Maciej Krüger
5e1b014940
feat: use ursa-optional and add it as dependency 2018-07-08 14:06:27 +02:00
81f5961d8e v1.4.2 2018-06-29 03:36:55 -06:00
ab5e7bea1b meh 2018-06-29 03:36:36 -06:00
b3f02b480e v1.4.1 2018-06-29 02:40:26 -06:00
8cee5a1b4e merge 2018-06-29 02:40:11 -06:00
0dc03f86e5 lighten the load 2018-06-29 02:39:10 -06:00
f0adccbc31 1024 deprecated, use 2048 2018-05-10 21:01:54 +00:00
AJ ONeal
7776aaa785 backwards compat fix 2018-03-21 15:16:44 -06:00
AJ ONeal
ec4118a988 break backwards compat for acme-v2 2018-03-21 15:12:51 -06:00
AJ ONeal
248f0d4ab1 update urls 2018-03-20 21:55:27 -06:00
AJ ONeal
93096c6ebd updated for acme-v2 2018-03-20 21:33:26 -06:00
AJ ONeal
09593f3778 auto-update banner 2016-12-30 02:41:10 -07:00
AJ ONeal
c761a7f99f auto-update ad 2016-12-30 02:22:58 -07:00
AJ ONeal
e205e912c1 Update README.md 2016-11-25 10:31:04 -07:00
AJ ONeal
f24c88c61e Update README.md 2016-11-21 16:25:14 -07:00
AJ ONeal
3ccad1b1e0 Update README.md 2016-10-20 14:06:11 -06:00
AJ ONeal
4fe1f4b358 v1.2.7 2016-08-17 00:22:15 -06:00
AJ ONeal
f3d8bb85c1 add global bin for generating keypairs 2016-08-17 00:22:07 -06:00
AJ ONeal
982c13948a v1.2.6 2016-08-10 11:18:38 -06:00
AJ ONeal
c728f25bc8 workaround for #9, try {} catch () {} on Buffer.from 2016-08-10 11:18:36 -06:00
43 changed files with 2031 additions and 964 deletions

388
LICENSE
View File

@ -1,21 +1,375 @@
The MIT License (MIT) Copyright 2016-2018 AJ ONeal
Copyright (c) 2016 Daplie, Inc Mozilla Public License Version 2.0
==================================
Permission is hereby granted, free of charge, to any person obtaining a copy 1. Definitions
of this software and associated documentation files (the "Software"), to deal --------------
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all 1.1. "Contributor"
copies or substantial portions of the Software. means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1.2. "Contributor Version"
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, means the combination of the Contributions of others (if any) used
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE by a Contributor and that particular Contributor's Contribution.
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1.3. "Contribution"
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE means Covered Software of a particular Contributor.
SOFTWARE.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

168
README.md
View File

@ -1,13 +1,46 @@
# rsa-compat.js # [rsa-compat.js](https://git.coolaj86.com/coolaj86/rsa-compat.js)
!["Lifetime Downloads"](https://img.shields.io/npm/dt/rsa-compat.svg "Lifetime Download Count can't be shown")
!["Monthly Downloads"](https://img.shields.io/npm/dm/rsa-compat.svg "Monthly Download Count can't be shown")
!["Weekly Downloads"](https://img.shields.io/npm/dw/rsa-compat.svg "Weekly Download Count can't be shown")
| A [Root](https://therootcompany.com) Project.
JavaScript RSA utils that work on Windows, Mac, and Linux with or without C compiler JavaScript RSA utils that work on Windows, Mac, and Linux with or without C compiler
In order to provide a module that "just works" everywhere, we mix and match methods This was built for the [ACME.js](https://git.coolaj86.com/coolaj86/acme.js) and
from `node.js` core, `ursa`, `forge`, and others. [Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js) **Let's Encrypt** clients
and is particularly suitable for building **certbot**-like clients.
This is useful for **certbot** and **letsencrypt**. (if you're looking for similar tools in the browser, consider [Bluecrypt](https://www.npmjs.com/search?q=bluecrypt))
(in the future we'd like to provide the same API to the browser) # Install
node.js
```bash
npm install --save rsa-compat
```
If you need compatibility with older versions of node, you may need to `npm install --save ursa-optional node-forge`.
### CLI
```bash
npm install --global rsa-compat
```
# Usage
CLI
---
You can generate keypairs on Windows, Mac, and Linux using rsa-keygen-js:
```bash
# generates a new keypair in the current directory
rsa-keypiar-js
```
Examples Examples
-------- --------
@ -17,11 +50,9 @@ Generate an RSA Keypair:
```javascript ```javascript
var RSA = require('rsa-compat').RSA; var RSA = require('rsa-compat').RSA;
var bitlen = 1024; var options = { bitlen: 2048, exp: 65537, public: true, pem: true, internal: true };
var exp = 65537;
var options = { public: true, pem: true, internal: true };
RSA.generateKeypair(bitlen, exp, options, function (err, keypair) { RSA.generateKeypair(options, function (err, keypair) {
console.log(keypair); console.log(keypair);
}); });
``` ```
@ -49,47 +80,41 @@ Here's what the object might look like:
, n: '/*base64 modulus n = pq*/' , n: '/*base64 modulus n = pq*/'
, e: '/*base64 exponent (usually 65537)*/' , e: '/*base64 exponent (usually 65537)*/'
} }
, _ursa: '/*undefined or intermediate ursa object*/'
, _ursaPublic: '/*undefined or intermediate ursa object*/'
, _forge: '/*undefined or intermediate forge object*/'
, _forgePublic: '/*undefined or intermediate forge object*/'
} }
``` ```
NOTE: this object is JSON safe as _ursa and _forge will be ignored
See http://crypto.stackexchange.com/questions/6593/what-data-is-saved-in-rsa-private-key to learn a little more about the meaning of the specific fields in the JWK. See http://crypto.stackexchange.com/questions/6593/what-data-is-saved-in-rsa-private-key to learn a little more about the meaning of the specific fields in the JWK.
API # API Summary
---
* `RSA.generateKeypair(bitlen, exp, options, cb)` * `RSA.generateKeypair(options, cb)`
* `RSA.import(keypair, options)` * (deprecated `RSA.generateKeypair(bitlen, exp, options, cb)`)
* `RSA.import(options)`
* (deprecated `RSA.import(keypair, options)`)
* `RSA.exportPrivatePem(keypair)` * `RSA.exportPrivatePem(keypair)`
* `RSA.exportPublicPem(keypair)` * `RSA.exportPublicPem(keypair)`
* `RSA.exportPrivateJwk(keypair)` * `RSA.exportPrivateJwk(keypair)`
* `RSA.exportPublicJwk(keypair)` * `RSA.exportPublicJwk(keypair)`
* `RSA.signJws(keypair, payload, nonce)` * `RSA.signJws(keypair, header, protect, payload)`
* (deprecated `RSA.signJws(keypair, payload, nonce)`)
* `RSA.generateCsrPem(keypair, names)` * `RSA.generateCsrPem(keypair, names)`
* `RSA.generateCsrDerWeb64(keypair, names)` * `RSA.generateCsrDerWeb64(keypair, names)`
* `RSA.thumbprint(keypair)`
`keypair` can be any object with any of these keys `publicKeyPem, privateKeyPem, publicKeyJwk, privateKeyJwk` `keypair` can be any object with any of these keys `publicKeyPem, privateKeyPem, publicKeyJwk, privateKeyJwk`
### RSA.generateKeypair(bitlen, exp, options, cb) ### RSA.generateKeypair(options, cb)
Create a private keypair and export it as PEM, JWK, and/or internal formats Create a private keypair and export it as PEM, JWK, and/or internal formats
```javascript ```javascript
RSA.generateKeypair(null, null, null, function (keypair) { /*...*/ }); RSA.generateKeypair(null, function (keypair) { /*...*/ });
RSA.generateKeypair(1024, 65537, { pem: false, public: false, internal: false }, function (keypair) { /*...*/ }); RSA.generateKeypair({
bitlen: 2048, exp: 65537, pem: false, public: false, internal: false
}, function (keypair) { /*...*/ });
``` ```
`bitlen`: *1024* (default), 2048, or 4096
`exp`: *65537* (default)
`options`: `options`:
```javascript ```javascript
{ public: false // export public keys { public: false // export public keys
@ -101,12 +126,12 @@ RSA.generateKeypair(1024, 65537, { pem: false, public: false, internal: false },
} }
``` ```
### RSA.import(keypair, options) ### RSA.import(options)
Imports keypair as JWKs and internal values `_ursa` and `_forge`. Imports keypair as JWKs and internal values `_ursa` and `_forge`.
```javascript ```javascript
var keypair = RSA.import({ privateKeyPem: '...'}); var keypair = RSA.import({ type: 'RSA', privateKeyPem: '...' });
console.log(keypair); console.log(keypair);
``` ```
@ -164,6 +189,21 @@ The result looks like this:
} }
``` ```
### RSA.thumbprint(keypair)
Generates a JWK thumbprint.
`RSA.thumbprint(keypair)`:
```javascript
var thumb = RSA.thumbprint(keypair);
console.log(thumb);
```
```
// kK4OXp5CT1FEkHi6WkegldmeTJecSTyJN-DxZ91nQ30
```
### RSA.generateCsr*(keypair, names) ### RSA.generateCsr*(keypair, names)
You can generate the CSR in human-readable or binary / base64 formats: You can generate the CSR in human-readable or binary / base64 formats:
@ -183,3 +223,71 @@ var web64 = RSA.generateCsrDerWeb64(keypair, [ 'example.com', 'www.example.com'
console.log(web64); console.log(web64);
``` ```
# Old Node Versions
In recent versions of node >= v10.12 native RSA key generation is fairly quick for 2048-bit keys
(though it may still be too slow for some applications with 4096-bit keys).
In old versions, however, and especially on ARM and/or MIPS procesors, RSA key generation can be
very, very slow.
In old node versions `ursa` can provide faster key generation, but it must be compiled.
`ursa` will not compile for new node versions, but they already include the same openssl bindings anyawy.
```bash
npm install --save ursa
```
Also, if you need **Node < v6** support:
```bash
npm install --save buffer-v6-polyfill
```
## Security and Compatibility
**TL;DR**: Use the default values 2048 and 65537 unless you have a really, really good reason to do otherwise.
Various platforms *require* these values.
Most security experts agree that 4096-bit is no more "secure" than 2048-bit -
a fundamental vulnerability in the RSA algorithm which causes 2048 to be broken
will most likely also cause 4096 to be broken
(i.e. if someone can prove mathematically prove P=NP or a way to predict prime numbers).
Also, many platforms
only support 2048 bit keys due to the insecurity of 1024-bit keys (which are not 1/2 secure
but rather 1/(2^1028) less secure) and the excess computational
cost of 4096-bit keys (it's not a 2x increase, it's more like a 2^2048 increase).
As to why 65537 is even optional as a prime exponent or why it matters... no idea,
but it does matter.
# ChangeLog:
* v2.0
* remove ursa and node-forge deps
* mark for node v10.11+
* v1.9
* consistently handle key generation across node crypto, ursa, and forge
* move all other operations to rasha.js and rsa-csr.js
* bugfix non-standard JWKs output (which *mostly* worked)
* move dependencies to optional
* v1.4.0
* remove ursa as dependency (just causes confusion), but note in docs
* drop node < v6 support
# Legal
rsa-compat.js directly includes code from
[Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js)
and
[RSA-CSR.js](https://git.coolaj86.com/coolaj86/rsa-csr.js)
(also [Root](https://therootcompany.com) projects),
retrofitted for rsa-compat.
[rsa-compat.js](https://git.coolaj86.com/coolaj86/rsa-compat.js) |
MPL-2.0 |
[Terms of Use](https://therootcompany.com/legal/#terms) |
[Privacy Policy](https://therootcompany.com/legal/#privacy)

35
bin/rsa-keygen.js Executable file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env node
'use strict';
var RSA = require('../').RSA;
var path = require('path');
var fs = require('fs');
var bitlen = 2048;
var exp = 65537;
var opts = { public: true, pem: true };
var cwd = process.cwd();
var privkeyPath = path.join(cwd, 'privkey.pem');
var pubkeyPath = path.join(cwd, 'pubkey.pem');
if (fs.existsSync(privkeyPath)) {
console.error(privkeyPath, "already exists");
process.exit(1);
}
RSA.generateKeypair(bitlen, exp, opts, function (err, keypair) {
console.info('');
console.info('');
fs.writeFileSync(privkeyPath, keypair.privateKeyPem, 'ascii');
console.info(privkeyPath + ':');
console.info('');
console.info(keypair.privateKeyPem);
console.info('');
fs.writeFileSync(pubkeyPath, keypair.publicKeyPem, 'ascii');
console.info(pubkeyPath + ':');
console.info('');
console.info(keypair.publicKeyPem);
});

11
fixtures/csr.pem Normal file
View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBjzCB+QIBADAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0B
AQEFAAOBjQAwgYkCgYEAwm5tN860BqucnK0sTx+E2wQjzCemNG8FcYr8qnTrvknX
Q5HPHIwurMhkXe1ytSQoGu11zv27hfQahwNSC6Sl+Rj7ZQ9RL8bF6FRhtislhY4u
SglbPGfvB+ij1fUmC8ExjrAdCdMq+fNl2SibYUyEbGQtoRQYNJ+w3OdNNk0EGD0C
AwEAAaA6MDgGCSqGSIb3DQEJDjErMCkwJwYDVR0RBCAwHoILZXhhbXBsZS5jb22C
D3d3dy5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOBgQCv1nj7zL0vnP11GEDb
TWlXPyT2XaFH9lYOm8By3BKrtk6rhGojlLppmNGaCEBqztebCY2T5LT6mi5lz0xc
YHJ9YC4w1yKz94//gmEMYJ+7fuILexnaeZudp9GjR3OeRWIWr/+3H0U3XnZpazGP
/qK4BCIl9HOOJbF5tyTd/CeeBg==
-----END CERTIFICATE REQUEST-----

1
fixtures/csr.urlbase64 Normal file
View File

@ -0,0 +1 @@
MIIBjzCB-QIBADAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwm5tN860BqucnK0sTx-E2wQjzCemNG8FcYr8qnTrvknXQ5HPHIwurMhkXe1ytSQoGu11zv27hfQahwNSC6Sl-Rj7ZQ9RL8bF6FRhtislhY4uSglbPGfvB-ij1fUmC8ExjrAdCdMq-fNl2SibYUyEbGQtoRQYNJ-w3OdNNk0EGD0CAwEAAaA6MDgGCSqGSIb3DQEJDjErMCkwJwYDVR0RBCAwHoILZXhhbXBsZS5jb22CD3d3dy5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOBgQCv1nj7zL0vnP11GEDbTWlXPyT2XaFH9lYOm8By3BKrtk6rhGojlLppmNGaCEBqztebCY2T5LT6mi5lz0xcYHJ9YC4w1yKz94__gmEMYJ-7fuILexnaeZudp9GjR3OeRWIWr_-3H0U3XnZpazGP_qK4BCIl9HOOJbF5tyTd_CeeBg

11
fixtures/privkey.jwk.json Normal file
View File

@ -0,0 +1,11 @@
{
"kty": "RSA",
"n": "wm5tN860BqucnK0sTx-E2wQjzCemNG8FcYr8qnTrvknXQ5HPHIwurMhkXe1ytSQoGu11zv27hfQahwNSC6Sl-Rj7ZQ9RL8bF6FRhtislhY4uSglbPGfvB-ij1fUmC8ExjrAdCdMq-fNl2SibYUyEbGQtoRQYNJ-w3OdNNk0EGD0",
"e": "AQAB",
"d": "HT8DCrv69G3n9uFNovE4yMEMqW7lX0m75eJkMze3Jj5xNOa_4qlrc-4IuuA2uuyfY72IVQRxqqqXOuvS8ZForZZk-kWSd6z45hrpbNAAHH2Rf7XwnwHY8VJrOQF3UtbktTWqHX36ITZb9Hmf18hWsIeEp8Ng7Ru9h7hNuVxKMjk",
"p": "42M69lUC-EIzYmcsZYkbf7kGhqvcwHk_h7N8TEOa7IYRP_DQL49LrSNuMHxOK9CxJ0RwajsY5o6WYBfoRC0B5w",
"q": "2uWWAmzLitMx9reZDnSQwhw1qGIQ55quEBwmBBQIe46O4SeFT0V8TED-bkVeOYQVCFHCS6GTRBoipMZtTPEYOw",
"dp": "u7Ec6lghq2p5n7AqJWWXHUZM7LzP6tAqcIjnAMyNBM9lTbIpJhjSDohAXCU_IUuR7ye-4vEFDMqFtawGPMAp4Q",
"dq": "XLhDAmPzE6rBzy-VtXnKl247jEd9wZzTfh9uOuwBa9TG0Lhcz2cvb11YaH0ZnGNGRW_cTQzzxDUN1531TlIRYQ",
"qi": "jZqnPoQJ8bCGy8hxTf7IW37cIDvwJRWxfg1S6XmbcKWzab7kxsZAbkrSEanGMMLc6ZdOrVjmCd6nnJRm9U9kjg"
}

View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDCbm03zrQGq5ycrSxPH4TbBCPMJ6Y0bwVxivyqdOu+SddDkc8c
jC6syGRd7XK1JCga7XXO/buF9BqHA1ILpKX5GPtlD1EvxsXoVGG2KyWFji5KCVs8
Z+8H6KPV9SYLwTGOsB0J0yr582XZKJthTIRsZC2hFBg0n7Dc5002TQQYPQIDAQAB
AoGAHT8DCrv69G3n9uFNovE4yMEMqW7lX0m75eJkMze3Jj5xNOa/4qlrc+4IuuA2
uuyfY72IVQRxqqqXOuvS8ZForZZk+kWSd6z45hrpbNAAHH2Rf7XwnwHY8VJrOQF3
UtbktTWqHX36ITZb9Hmf18hWsIeEp8Ng7Ru9h7hNuVxKMjkCQQDjYzr2VQL4QjNi
ZyxliRt/uQaGq9zAeT+Hs3xMQ5rshhE/8NAvj0utI24wfE4r0LEnRHBqOxjmjpZg
F+hELQHnAkEA2uWWAmzLitMx9reZDnSQwhw1qGIQ55quEBwmBBQIe46O4SeFT0V8
TED+bkVeOYQVCFHCS6GTRBoipMZtTPEYOwJBALuxHOpYIatqeZ+wKiVllx1GTOy8
z+rQKnCI5wDMjQTPZU2yKSYY0g6IQFwlPyFLke8nvuLxBQzKhbWsBjzAKeECQFy4
QwJj8xOqwc8vlbV5ypduO4xHfcGc034fbjrsAWvUxtC4XM9nL29dWGh9GZxjRkVv
3E0M88Q1Dded9U5SEWECQQCNmqc+hAnxsIbLyHFN/shbftwgO/AlFbF+DVLpeZtw
pbNpvuTGxkBuStIRqcYwwtzpl06tWOYJ3qeclGb1T2SO
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMJubTfOtAarnJyt
LE8fhNsEI8wnpjRvBXGK/Kp0675J10ORzxyMLqzIZF3tcrUkKBrtdc79u4X0GocD
UgukpfkY+2UPUS/GxehUYbYrJYWOLkoJWzxn7wfoo9X1JgvBMY6wHQnTKvnzZdko
m2FMhGxkLaEUGDSfsNznTTZNBBg9AgMBAAECgYAdPwMKu/r0bef24U2i8TjIwQyp
buVfSbvl4mQzN7cmPnE05r/iqWtz7gi64Da67J9jvYhVBHGqqpc669LxkWitlmT6
RZJ3rPjmGuls0AAcfZF/tfCfAdjxUms5AXdS1uS1NaodffohNlv0eZ/XyFawh4Sn
w2DtG72HuE25XEoyOQJBAONjOvZVAvhCM2JnLGWJG3+5Boar3MB5P4ezfExDmuyG
ET/w0C+PS60jbjB8TivQsSdEcGo7GOaOlmAX6EQtAecCQQDa5ZYCbMuK0zH2t5kO
dJDCHDWoYhDnmq4QHCYEFAh7jo7hJ4VPRXxMQP5uRV45hBUIUcJLoZNEGiKkxm1M
8Rg7AkEAu7Ec6lghq2p5n7AqJWWXHUZM7LzP6tAqcIjnAMyNBM9lTbIpJhjSDohA
XCU/IUuR7ye+4vEFDMqFtawGPMAp4QJAXLhDAmPzE6rBzy+VtXnKl247jEd9wZzT
fh9uOuwBa9TG0Lhcz2cvb11YaH0ZnGNGRW/cTQzzxDUN1531TlIRYQJBAI2apz6E
CfGwhsvIcU3+yFt+3CA78CUVsX4NUul5m3Cls2m+5MbGQG5K0hGpxjDC3OmXTq1Y
5gnep5yUZvVPZI4=
-----END PRIVATE KEY-----

13
fixtures/signed.jwk.json Normal file
View File

@ -0,0 +1,13 @@
{
"header": {
"alg": "RS256",
"jwk": {
"kty": "RSA",
"n": "AMJubTfOtAarnJytLE8fhNsEI8wnpjRvBXGK_Kp0675J10ORzxyMLqzIZF3tcrUkKBrtdc79u4X0GocDUgukpfkY-2UPUS_GxehUYbYrJYWOLkoJWzxn7wfoo9X1JgvBMY6wHQnTKvnzZdkom2FMhGxkLaEUGDSfsNznTTZNBBg9",
"e": "AQAB"
}
},
"protected": "eyJub25jZSI6IjhlZjU2MjRmNWVjOWQzZWYifQ",
"payload": "JLzF1NBNCV3kfbJ5sFaFyX94fJuL2H-IzaoBN-ciiHk",
"signature": "Wb2al5SDyh5gjmkV79MK9m3sfNBBPjntSKor-34BBoGwr6n8qEnBmqB1Y4zbo-5rmvsoPmJsnRlP_hRiUY86zSAQyfbisTGrGBl0IQ7ditpkfYVm0rBWJ8WnYNqYNp8K3qcD7NW72tsy-XoWEjNlz4lWJeRdEG2Nt4CJgnREH4Y"
}

7
index.js Normal file
View File

@ -0,0 +1,7 @@
// Copyright 2016-2018 AJ ONeal. All rights reserved
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
module.exports.RSA = require('./lib/rsa.js');

View File

@ -0,0 +1,57 @@
// Copyright 2016-2018 AJ ONeal. All rights reserved
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
var Keypairs = require('keypairs');
module.exports = function (bitlen, exp) {
var k = require('node-forge').pki.rsa
.generateKeyPair({ bits: bitlen || 2048, e: exp || 0x10001 }).privateKey;
var jwk = {
kty: "RSA"
, n: _toUrlBase64(k.n)
, e: _toUrlBase64(k.e)
, d: _toUrlBase64(k.d)
, p: _toUrlBase64(k.p)
, q: _toUrlBase64(k.q)
, dp: _toUrlBase64(k.dP)
, dq: _toUrlBase64(k.dQ)
, qi: _toUrlBase64(k.qInv)
};
return {
publicKeyPem: Keypairs._exportSync({ jwk: jwk, public: true })
, privateKeyPem: Keypairs._exportSync({ jwk: jwk })
, privateKeyJwk: jwk
, publicKeyJwk: {
kty: jwk.kty
, n: jwk.n
, e: jwk.e
}
};
};
function _toUrlBase64(fbn) {
var hex = fbn.toRadix(16);
if (hex.length % 2) {
// Invalid hex string
hex = '0' + hex;
}
while ('00' === hex.slice(0, 2)) {
hex = hex.slice(2);
}
return Buffer.from(hex, 'hex').toString('base64')
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g,"")
;
}
if (require.main === module) {
var keypair = module.exports(2048, 0x10001);
console.info(keypair.privateKeyPem);
console.warn(keypair.publicKeyPem);
//console.info(keypair.privateKeyJwk);
//console.warn(keypair.publicKeyJwk);
}

View File

@ -0,0 +1,39 @@
// Copyright 2016-2018 AJ ONeal. All rights reserved
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
var Keypairs = require('keypairs');
module.exports = function (bitlen, exp) {
var keypair = require('crypto').generateKeyPairSync(
'rsa'
, { modulusLength: bitlen
, publicExponent: exp
, privateKeyEncoding: {
type: 'pkcs1'
, format: 'pem'
}
, publicKeyEncoding: {
type: 'pkcs1'
, format: 'pem'
}
}
);
var result = {
publicKeyPem: keypair.publicKey.trim()
, privateKeyPem: keypair.privateKey.trim()
};
result.publicKeyJwk = Keypairs._importSync({ pem: result.publicKeyPem, public: true });
result.privateKeyJwk = Keypairs._importSync({ pem: result.privateKeyPem });
return result;
};
if (require.main === module) {
var keypair = module.exports(2048, 0x10001);
console.info(keypair.privateKeyPem);
console.warn(keypair.publicKeyPem);
//console.info(keypair.privateKeyJwk);
//console.warn(keypair.publicKeyJwk);
}

View File

@ -0,0 +1,32 @@
// Copyright 2016-2018 AJ ONeal. All rights reserved
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
var Keypairs = require('keypairs');
module.exports = function (bitlen, exp) {
var ursa;
try {
ursa = require('ursa');
} catch(e) {
ursa = require('ursa-optional');
}
var keypair = ursa.generatePrivateKey(bitlen, exp);
var result = {
publicKeyPem: keypair.toPublicPem().toString('ascii').trim()
, privateKeyPem: keypair.toPrivatePem().toString('ascii').trim()
};
result.publicKeyJwk = Keypairs._importSync({ pem: result.publicKeyPem, public: true });
result.privateKeyJwk = Keypairs._importSync({ pem: result.privateKeyPem });
return result;
};
if (require.main === module) {
var keypair = module.exports(2048, 0x10001);
console.info(keypair.privateKeyPem);
console.warn(keypair.publicKeyPem);
//console.info(keypair.privateKeyJwk);
//console.warn(keypair.publicKeyJwk);
}

66
lib/generate-privkey.js Normal file
View File

@ -0,0 +1,66 @@
// Copyright 2016-2018 AJ ONeal. All rights reserved
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
var oldver = false;
module.exports = function (bitlen, exp) {
bitlen = parseInt(bitlen, 10) || 2048;
exp = parseInt(exp, 10) || 65537;
try {
return require('./generate-privkey-node.js')(bitlen, exp);
} catch(e) {
if (!/generateKeyPairSync is not a function/.test(e.message)) {
throw e;
}
try {
return require('./generate-privkey-ursa.js')(bitlen, exp);
} catch(e) {
if (e.code !== 'MODULE_NOT_FOUND') {
console.error("[rsa-compat] Unexpected error when using 'ursa':");
console.error(e);
}
if (!oldver) {
oldver = true;
console.warn("[WARN] rsa-compat: Your version of node does not have crypto.generateKeyPair()");
console.warn("[WARN] rsa-compat: Please update to node >= v10.12 or 'npm install ursa'");
console.warn("[WARN] rsa-compat: Using node-forge as a fallback, but it may be unacceptably slow.");
if (/arm|mips/i.test(require('os').arch)) {
console.warn("================================================================");
console.warn(" WARNING");
console.warn("================================================================");
console.warn("");
console.warn("WARNING: You are generating an RSA key using pure JavaScript on");
console.warn(" a VERY SLOW cpu. This could take DOZENS of minutes!");
console.warn("");
console.warn(" We recommend installing node >= v10.12, or 'gcc' and 'ursa'");
console.warn("");
console.warn("EXAMPLE:");
console.warn("");
console.warn(" sudo apt-get install build-essential && npm install ursa");
console.warn("");
console.warn("================================================================");
}
}
try {
return require('./generate-privkey-forge.js')(bitlen, exp);
} catch(e) {
console.error("[ERROR] rsa-compat: could not generate a private key.");
console.error("None of crypto.generateKeyPair, ursa, nor node-forge are present");
console.error("");
throw e;
}
}
}
};
if (require.main === module) {
var keypair = module.exports(2048, 0x10001);
console.info(keypair.privateKeyPem);
console.warn(keypair.publicKeyPem);
//console.info(keypair.privateKeyJwk);
//console.warn(keypair.publicKeyJwk);
}

View File

@ -1,76 +0,0 @@
// Copyright 2014 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
'use strict';
var utils = {
fromStandardB64: function(x) {
return x.replace(/[+]/g, "-").replace(/\//g, "_").replace(/=/g,"");
},
toStandardB64: function(x) {
var b64 = x.replace(/-/g, "+").replace(/_/g, "/").replace(/=/g, "");
switch (b64.length % 4) {
case 2: b64 += "=="; break;
case 3: b64 += "="; break;
}
return b64;
},
b64enc: function(buffer) {
return utils.fromStandardB64(buffer.toString("base64"));
},
b64dec: function(str) {
return new Buffer(utils.toStandardB64(str), "base64");
},
isB64String: function(x) {
return ("string" === typeof x) && !x.match(/[^a-zA-Z0-9_-]/);
},
fieldsPresent: function(fields, object) {
for (var i in fields) {
if (!(fields[i] in object)) {
return false;
}
}
return true;
},
validSignature: function(sig) {
return (("object" === typeof sig) &&
("alg" in sig) && ("string" === typeof sig.alg) &&
("nonce" in sig) && utils.isB64String(sig.nonce) &&
("sig" in sig) && utils.isB64String(sig.sig) &&
("jwk" in sig) && utils.validJWK(sig.jwk));
},
validJWK: function(jwk) {
return (("object" === typeof jwk) && ("kty" in jwk) && (
((jwk.kty === "RSA")
&& ("n" in jwk) && utils.isB64String(jwk.n)
&& ("e" in jwk) && utils.isB64String(jwk.e)) ||
((jwk.kty === "EC")
&& ("crv" in jwk)
&& ("x" in jwk) && utils.isB64String(jwk.x)
&& ("y" in jwk) && utils.isB64String(jwk.y))
) && !("d" in jwk));
},
// A simple, non-standard fingerprint for a JWK,
// just so that we don't have to store objects
keyFingerprint: function(jwk) {
switch (jwk.kty) {
case "RSA": return jwk.n;
case "EC": return jwk.crv + jwk.x + jwk.y;
}
throw "Unrecognized key type";
}
};
module.exports = utils;

View File

@ -1,42 +0,0 @@
/*!
* rsa-compat
* Copyright(c) 2016 AJ ONeal <aj@daplie.com> https://daplie.com
* Apache-2.0 OR MIT (and hence also MPL 2.0)
*/
'use strict';
var cryptoc = module.exports;
var rsaExtra = require('./rsa-extra');
var rsaForge = require('./rsa-forge');
var rsaUrsa;
try {
rsaUrsa = require('./rsa-ursa');
} catch(e) {
rsaUrsa = {};
// things will run a little slower on keygen, but it'll work on windows
// (but don't try this on raspberry pi - 20+ MINUTES key generation)
}
// order of crypto precdence is
// * native
// * ursa
// * forge extra (the new one aimed to be less-forgey)
// * forge (fallback)
Object.keys(rsaUrsa).forEach(function (key) {
if (!cryptoc[key]) {
cryptoc[key] = rsaUrsa[key];
}
});
Object.keys(rsaForge).forEach(function (key) {
if (!cryptoc[key]) {
cryptoc[key] = rsaForge[key];
}
});
Object.keys(rsaExtra).forEach(function (key) {
if (!cryptoc[key]) {
cryptoc[key] = rsaExtra[key];
}
});

214
lib/rsa-csr/README.md Normal file
View File

@ -0,0 +1,214 @@
[RSA-CSR.js](https://git.coolaj86.com/coolaj86/rsa-csr.js)
==========
Sponsored by [Root](https://therootcompany.com),
built for [ACME.js](https://git.coolaj86.com/coolaj86/acme.js)
and [Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock-express.js)
A focused, **zero-dependency** library that can do exactly one thing really, really well:
* Generate a Certificate Signing Requests (CSR), and sign it!
| < 300 lines of code | 1.7k gzipped | 4.7k minified | 8.5k with comments |
Need JWK-to-PEM? Try [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js)
Need to generate an EC CSR? Try [ECSDA-CSR.js](https://git.coolaj86.com/coolaj86/ecdsa-csr.js)
Features
========
* [x] Universal CSR support (RSA signing) that Just Works&trade;
* Common Name (CN) Subject
* Subject Alternative Names (SANs / altnames)
* 2048, 3072, and 4096 bit JWK RSA
* RSASSA PKCS1 v1.5
* [x] Zero Dependencies
* (no ASN1.js, PKI.js, forge, jrsasign - not even elliptic.js!)
* [x] Quality
* Focused
* Lightweight
* Well-Commented, Well-Documented
* Secure
* [x] Vanilla Node.js
* no school like the old school
* easy to read and understand
Usage
-----
Given an array of domains it uses the first for the Common Name (CN),
also known as Subject, and all of them as the Subject Alternative Names (SANs or altnames).
```js
'use strict';
var rsacsr = require('rsa-csr');
var key = {
"kty": "RSA",
"n": "m2tt...-CNw",
"e": "AQAB",
"d": "Cpfo...HMQQ",
"p": "ynG-...sTCE",
"q": "xIkA...1Q1c",
"dp": "tzDG...B1QE",
"dq": "kh5d...aL48",
"qi": "AlHW...HhFU"
};
var domains = [ 'example.com', 'www.example.com' ];
return rsacsr({ key: key, domains: domains }).then(function (csr) {
console.log('CSR PEM:');
console.log(csr);
});
```
The output will look something like this (but much longer):
```
-----BEGIN CERTIFICATE REQUEST-----
MIIClTCCAX0CAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCba21UHE+VbDTpmYYFZUOV+OQ8AngOCdjROsPC
0KiEfMvEaEM3NQl58u6QL7G7QsEr.....3pIpUUkx5WbwJY6xDrCyFKG8ktpnee6
WjpTOBnpgHUI1/5ydnf0v29L9N+ALIJGKQxhub3iqB6EhCl93iiQtf4e7M/lzX7l
c1xqsSwVZ3RQVY9bRP9NdGuW4hVvscy5ypqRtXPXQpxMnYwfi9qW5Uo=
-----END CERTIFICATE REQUEST-----
```
#### PEM-to-JWK
If you need to convert a PEM to JWK first, do so:
```js
var Rasha = require('rasha');
Rasha.import({ pem: "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAI..." }).then(function (jwk) {
console.log(jwk);
})
```
#### CLI
You're probably better off using OpenSSL for most commandline tasks,
but the `rsa-csr` and `rasha` CLIs are useful for testing and debugging.
```bash
npm install -g rsa-csr
npm install -g rasha
rasha ./privkey.pem > ./privkey.jwk.json
rsa-csr ./privkey.jwk.json example.com,www.example.com > csr.pem
```
### Options
* `key` should be a JWK
* Need PEM support? Use [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js).
* (supports PEM, DER, PKCS#1 and PKCS#8)
* `domains` must be a list of strings representing domain names
* correctly handles utf-8
* you may also use punycoded, if needed
* `subject` will be `domains[0]` by default
* you shouldn't use this unless you need to
* you may need to if you need utf-8 for domains, but punycode for the subject
### Testing
You can double check that the CSR you get out is actually valid:
```bash
# Generate a key, if needed
openssl genrsa -out ./privkey-rsa.pkcs1.pem $keysize
# Convert to JWK
rasha ./privkey-rsa.pkcs1.pem > ./privkey-rsa.jwk.json
# Create a CSR with your domains
npx rsa-csr ./privkey-rsa.jwk.json example.com,www.example.com > csr.pem
# Verify
openssl req -text -noout -verify -in csr.pem
```
New to Crypto?
--------------
Just a heads up in case you have no idea what you're doing:
First of all, [don't panic](https://coolaj86.com/articles/dont-panic.html).
Next:
* RSA stands for... well, that doesn't matter, actually.
* DSA stands for _Digital Signing Algorithm_.
* RSA a separate standard from EC/ECDSA, but both are *asymmetric*
* Private keys are actually keypairs (they contain the public key)
In many cases the terms get used (and misused) interchangably,
which can be confusing. You'll survive, I promise.
* PEM is just a Base64-encoded DER (think JSON as hex or base64)
* DER is an binary _object notation_ for ASN.1 (think actual stringified JSON or XML)
* ASN.1 is _object notation_ standard (think JSON, the standard)
* X.509 is a suite of schemas (think XLST or json-schema.org)
* PKCS#8, PKIK, SPKI are all X.509 schemas (think defining `firstName` vs `first_name` vs `firstname`)
Now forget about all that and just know this:
**This library solves your problem if** you need RSA _something-or-other_ and CSR _something-or-other_
in order to deal with SSL certificates in an internal organization.
If that's not what you're doing, you may want HTTPS and SSL through
[Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock-express.js),
or you may be looking for something else entirely.
Goals vs Non-Goals
-----
This was built for use by [ACME.js](https://git.coolaj86.com/coolaj86/acme.js)
and [Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock-express.js).
Rather than trying to make a generic implementation that works with everything under the sun,
this library is intentionally focused on around the use case of generating certificates for
ACME services (such as Let's Encrypt).
That said, [please tell me](https://git.coolaj86.com/coolaj86/rsa-csr.js/issues/new) if it doesn't
do what you need, it may make sense to add it (or otherwise, perhaps to help you create a fork).
The primary goal of this project is for this code to do exactly (and all of)
what it needs to do - No more, no less.
* Support RSA JWKs
* 2048-bit
* 3072-bit
* 4096-bit
* Support PEM and DER via Rasha.js
* PKCS#1 (traditional)
* PKCS#8
* RSASSA-PKCS1-v1_5
* Vanilla node.js (ECMAScript 5.1)
* No babel
* No dependencies
However, there are a few areas where I'd be willing to stretch:
* Type definition files for altscript languages
It is not a goal of this project to support any RSA profiles
except those that are universally supported by browsers and
are sufficiently secure (overkill is overkill).
> A little copying is better than a little dependency. - [Go Proverbs](https://go-proverbs.github.io) by Rob Pike
This code is considered small and focused enough that,
rather than making it a dependency in other small projects,
I personally just copy over the code.
Hence, all of these projects are MPL-2.0 licensed.
Legal
-----
[RSA-CSR.js](https://git.coolaj86.com/coolaj86/rsa-csr.js) |
MPL-2.0 |
[Terms of Use](https://therootcompany.com/legal/#terms) |
[Privacy Policy](https://therootcompany.com/legal/#privacy)

27
lib/rsa-csr/bin/rsa-csr.js Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env node
'use strict';
var fs = require('fs');
var rsacsr = require('../index.js');
var keyname = process.argv[2];
var domains = process.argv[3].split(/,/);
var key = fs.readFileSync(keyname, 'ascii');
try {
key = JSON.parse(key);
} catch(e) {
// ignore
}
var csr = rsacsr.sync({ key: key, domains: domains });
console.log(csr);
/*
.then(function (csr) {
// Using error so that we can redirect stdout to file
//console.error("CN=" + domains[0]);
//console.error("subjectAltName=" + domains.join(','));
console.log(csr);
});
*/

View File

@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIClTCCAX0CAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCba21UHE+VbDTpmYYFZUOV+OQ8AngOCdjROsPC
0KiEfMvEaEM3NQl58u6QL7G7QsErKViiNPm9OTFo6HF5JijfWzK7haHFuRMEsgI4
VwIYyhvqlJDfw/wt0AiVvSmoMfEQn1p1aiaO4V/RJSE3Vw/uz2bxiT22uSkSqOyS
hyfYE6dMHnuoBkzr4jvSifT+INmbv6Nyo4+AAMCZtYeHLrsFeSTjLL9jMPjI4ZkV
dlw2n3Xn9NbltF3/8Ao8dQfElqw+LIQWqU0oFHYNIP4ttfl5ObMKHaKSvBMyNruZ
R0El/ZsrcHLkAHRCLj07KRQJ81l5CUTPtQ02P1Eamz/nT4I3AgMBAAGgOjA4Bgkq
hkiG9w0BCQ4xKzApMCcGA1UdEQQgMB6CC2V4YW1wbGUuY29tgg93d3cuZXhhbXBs
ZS5jb20wDQYJKoZIhvcNAQELBQADggEBAGOFydCZPtRqnEidrB3vkpPp1GmQVqrl
XhqVM3X7UppsD3QDtJfgoSuBuVy3X/mPvy/Ly8WqEwJ7Ur+h76e7rgeFLvN5fQIr
frlpfCXrKaxxl6vxqKJ8s3Mn2LT8VmSVNig34NCtn99/SHNAlr3aU9L3b1+R25VI
FwFbOz/i92gOchT7Xat3fOiQ02k9GrHT33pIpUUkx5WbwJY6xDrCyFKG8ktpnee6
WjpTOBnpgHUI1/5ydnf0v29L9N+ALIJGKQxhub3iqB6EhCl93iiQtf4e7M/lzX7l
c1xqsSwVZ3RQVY9bRP9NdGuW4hVvscy5ypqRtXPXQpxMnYwfi9qW5Uo=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,11 @@
{
"kty": "RSA",
"n": "m2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJefLukC-xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57qAZM6-I70on0_iDZm7-jcqOPgADAmbWHhy67BXkk4yy_YzD4yOGZFXZcNp915_TW5bRd__AKPHUHxJasPiyEFqlNKBR2DSD-LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0Qi49OykUCfNZeQlEz7UNNj9RGps_50-CNw",
"e": "AQAB",
"d": "Cpfo7Mm9Nu8YMC_xrZ54W9mKHPkCG9rZ93Ds9PNp-RXUgb-ljTbFPZWsYxGNKLllFz8LNosr1pT2ZDMrwNk0Af1iWNvD6gkyXaiQdCyiDPSBsJyNv2LJZon-e85X74nv53UlIkmo9SYxdLz2JaJ-iIWEe8Qh-7llLktrTJV_xr98_tbhgSppz_IeOymq3SEZaQHM8pTU7w7XvCj2pb9r8fN0M0XcgWZIaf3LGEfkhF_WtX67XJ0C6-LbkT51jtlLRNGX6haGdscXS0OWWjKOJzKGuV-NbthEn5rmRtVnjRZ3yaxQ0ud8vC-NONn7yvGUlOur1IdDzJ_YfHPt9sHMQQ",
"p": "ynG-t9HwKCN3MWRYFdnFzi9-02Qcy3p8B5pu3ary2E70hYn2pHlUG2a9BNE8c5xHQ3Hx43WoWf6s0zOunPV1G28LkU_UYEbAtPv_PxSmzpQp9n9XnYvBLBF8Y3z7gxgLn1vVFNARrQdRtj87qY3aw7E9S4DsGcAarIuOT2TsTCE",
"q": "xIkAjgUzB1zaUzJtW2Zgvp9cYYr1DmpH30ePZl3c_8397_DZDDo46fnFYjs6uPa03HpmKUnbjwr14QHlfXlntJBEuXxcqLjkdKdJ4ob7xueLTK4suo9V8LSrkLChVxlZQwnFD2E5ll0sVeeDeMJHQw38ahSrBFEVnxjpnPh1Q1c",
"dp": "tzDGjECFOU0ehqtuqhcuT63a7h8hj19-7MJqoFwY9HQ-ALkfXyYLXeBSGxHbyiIYuodZg6LsfMNgUJ3r3Eyhc_nAVfYPEC_2IdAG4WYmq7iXYF9LQV09qEsKbFykm7QekE3hO7wswo5k-q2tp3ieBYdVGAXJoGOdv5VpaZ7B1QE",
"dq": "kh5dyDk7YCz7sUFbpsmuAeuPjoH2ghooh2u3xN7iUVmAg-ToKjwbVnG5-7eXiC779rQVwnrD_0yh1AFJ8wjRPqDIR7ObXGHikIxT1VSQWqiJm6AfZzDsL0LUD4YS3iPdhob7-NxLKWzqao_u4lhnDQaX9PKa12HFlny6K1daL48",
"qi": "AlHWbx1gp6Z9pbw_1hlS7HuXAgWoX7IjbTUelldf4gkriDWLOrj3QCZcO4ZvZvEwJhVlsny9LO8IkbwGJEL6cXraK08ByVS2mwQyflgTgGNnpzixyEUL_mrQLx6y145FHcxfeqNInMhep-0Mxn1D5nlhmIOgRApS0t9VoXtHhFU"
}

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAm2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhD
NzUJefLukC+xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ
38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57
qAZM6+I70on0/iDZm7+jcqOPgADAmbWHhy67BXkk4yy/YzD4yOGZFXZcNp915/TW
5bRd//AKPHUHxJasPiyEFqlNKBR2DSD+LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By
5AB0Qi49OykUCfNZeQlEz7UNNj9RGps/50+CNwIDAQABAoIBAAqX6OzJvTbvGDAv
8a2eeFvZihz5Ahva2fdw7PTzafkV1IG/pY02xT2VrGMRjSi5ZRc/CzaLK9aU9mQz
K8DZNAH9Yljbw+oJMl2okHQsogz0gbCcjb9iyWaJ/nvOV++J7+d1JSJJqPUmMXS8
9iWifoiFhHvEIfu5ZS5La0yVf8a/fP7W4YEqac/yHjspqt0hGWkBzPKU1O8O17wo
9qW/a/HzdDNF3IFmSGn9yxhH5IRf1rV+u1ydAuvi25E+dY7ZS0TRl+oWhnbHF0tD
lloyjicyhrlfjW7YRJ+a5kbVZ40Wd8msUNLnfLwvjTjZ+8rxlJTrq9SHQ8yf2Hxz
7fbBzEECgYEAynG+t9HwKCN3MWRYFdnFzi9+02Qcy3p8B5pu3ary2E70hYn2pHlU
G2a9BNE8c5xHQ3Hx43WoWf6s0zOunPV1G28LkU/UYEbAtPv/PxSmzpQp9n9XnYvB
LBF8Y3z7gxgLn1vVFNARrQdRtj87qY3aw7E9S4DsGcAarIuOT2TsTCECgYEAxIkA
jgUzB1zaUzJtW2Zgvp9cYYr1DmpH30ePZl3c/8397/DZDDo46fnFYjs6uPa03Hpm
KUnbjwr14QHlfXlntJBEuXxcqLjkdKdJ4ob7xueLTK4suo9V8LSrkLChVxlZQwnF
D2E5ll0sVeeDeMJHQw38ahSrBFEVnxjpnPh1Q1cCgYEAtzDGjECFOU0ehqtuqhcu
T63a7h8hj19+7MJqoFwY9HQ+ALkfXyYLXeBSGxHbyiIYuodZg6LsfMNgUJ3r3Eyh
c/nAVfYPEC/2IdAG4WYmq7iXYF9LQV09qEsKbFykm7QekE3hO7wswo5k+q2tp3ie
BYdVGAXJoGOdv5VpaZ7B1QECgYEAkh5dyDk7YCz7sUFbpsmuAeuPjoH2ghooh2u3
xN7iUVmAg+ToKjwbVnG5+7eXiC779rQVwnrD/0yh1AFJ8wjRPqDIR7ObXGHikIxT
1VSQWqiJm6AfZzDsL0LUD4YS3iPdhob7+NxLKWzqao/u4lhnDQaX9PKa12HFlny6
K1daL48CgYACUdZvHWCnpn2lvD/WGVLse5cCBahfsiNtNR6WV1/iCSuINYs6uPdA
Jlw7hm9m8TAmFWWyfL0s7wiRvAYkQvpxetorTwHJVLabBDJ+WBOAY2enOLHIRQv+
atAvHrLXjkUdzF96o0icyF6n7QzGfUPmeWGYg6BEClLS31Whe0eEVQ==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICqjCCAZICAQAwFzEVMBMGA1UEAwwMd2hhdGV2ZXIubmV0MIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrD
wtCohHzLxGhDNzUJefLukC+xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLIC
OFcCGMob6pSQ38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjs
kocn2BOnTB57qAZM6+I70on0/iDZm7+jcqOPgADAmbWHhy67BXkk4yy/YzD4yOGZ
FXZcNp915/TW5bRd//AKPHUHxJasPiyEFqlNKBR2DSD+LbX5eTmzCh2ikrwTMja7
mUdBJf2bK3By5AB0Qi49OykUCfNZeQlEz7UNNj9RGps/50+CNwIDAQABoE4wTAYJ
KoZIhvcNAQkOMT8wPTA7BgNVHREENDAyggx3aGF0ZXZlci5uZXSCEHd3dy53aGF0
ZXZlci5uZXSCEGFwaS53aGF0ZXZlci5uZXQwDQYJKoZIhvcNAQELBQADggEBAB21
KZYjarfd8nUAbwhH8dWZOo4rFcdYFo3xcXPQ11b1Wa79dtG67cgD/dplKFis5qD3
6h4m818w9ESBA3Q1ZUy6HgDPMhCjg2fmCnSsZ5epo47wzvelYonfOX5DAwxgfYsa
335olrXJ0qsTiNmaS7RxDT53vfMOp41NyEAkFmpIAkaHgW/+xFPUSCBXIUWbaCG+
pK3FVNmK3VCVCAP6UvVKYQUWSC6FRG/Q8MHoecdo+bbMlr2s2GPxq9TKInwe8JqT
E9pD7QMsN7uWpMaXNKCje4+Q88Br4URNcGAiYoy4/6hcF2Ki1saTYVIk/DG1P4hX
G5f0ezDLtsC22xe6jHI=
-----END CERTIFICATE REQUEST-----

2
lib/rsa-csr/index.js Normal file
View File

@ -0,0 +1,2 @@
'use strict';
module.exports = require('./lib/csr.js');

72
lib/rsa-csr/lib/asn1.js Normal file
View File

@ -0,0 +1,72 @@
'use strict';
//
// A dumbed-down, minimal ASN.1 parser / packer combo
//
// Note: generally I like to write congruent code
// (i.e. output can be used as input and vice-versa)
// However, this seemed to be more readable and easier
// to use written as-is, asymmetrically.
// (I also generally prefer to export objects rather
// functions but, yet again, asthetics one in this case)
var Enc = require('./encoding.js');
//
// Packer
//
// Almost every ASN.1 type that's important for CSR
// can be represented generically with only a few rules.
var ASN1 = module.exports = function ASN1(/*type, hexstrings...*/) {
var args = Array.prototype.slice.call(arguments);
var typ = args.shift();
var str = args.join('').replace(/\s+/g, '').toLowerCase();
var len = (str.length/2);
var lenlen = 0;
var hex = typ;
// We can't have an odd number of hex chars
if (len !== Math.round(len)) {
console.error(arguments);
throw new Error("invalid hex");
}
// The first byte of any ASN.1 sequence is the type (Sequence, Integer, etc)
// The second byte is either the size of the value, or the size of its size
// 1. If the second byte is < 0x80 (128) it is considered the size
// 2. If it is > 0x80 then it describes the number of bytes of the size
// ex: 0x82 means the next 2 bytes describe the size of the value
// 3. The special case of exactly 0x80 is "indefinite" length (to end-of-file)
if (len > 127) {
lenlen += 1;
while (len > 255) {
lenlen += 1;
len = len >> 8;
}
}
if (lenlen) { hex += Enc.numToHex(0x80 + lenlen); }
return hex + Enc.numToHex(str.length/2) + str;
};
// The Integer type has some special rules
ASN1.UInt = function UINT() {
var str = Array.prototype.slice.call(arguments).join('');
var first = parseInt(str.slice(0, 2), 16);
// If the first byte is 0x80 or greater, the number is considered negative
// Therefore we add a '00' prefix if the 0x80 bit is set
if (0x80 & first) { str = '00' + str; }
return ASN1('02', str);
};
// The Bit String type also has a special rule
ASN1.BitStr = function BITSTR() {
var str = Array.prototype.slice.call(arguments).join('');
// '00' is a mask of how many bits of the next byte to ignore
return ASN1('03', '00' + str);
};

152
lib/rsa-csr/lib/csr.js Normal file
View File

@ -0,0 +1,152 @@
'use strict';
var crypto = require('crypto');
var ASN1 = require('./asn1.js');
var Enc = require('./encoding.js');
var PEM = require('./pem.js');
var X509 = require('./x509.js');
var RSA = {};
/*global Promise*/
var CSR = module.exports = function rsacsr(opts) {
// We're using a Promise here to be compatible with the browser version
// which will probably use the webcrypto API for some of the conversions
opts = CSR._prepare(opts);
return CSR.create(opts).then(function (bytes) {
return CSR._encode(opts, bytes);
});
};
CSR._prepare = function (opts) {
var Rasha;
opts = JSON.parse(JSON.stringify(opts));
var pem, jwk;
// We do a bit of extra error checking for user convenience
if (!opts) { throw new Error("You must pass options with key and domains to rsacsr"); }
if (!Array.isArray(opts.domains) || 0 === opts.domains.length) {
new Error("You must pass options.domains as a non-empty array");
}
// I need to check that 例.中国 is a valid domain name
if (!opts.domains.every(function (d) {
// allow punycode? xn--
if ('string' === typeof d /*&& /\./.test(d) && !/--/.test(d)*/) {
return true;
}
})) {
throw new Error("You must pass options.domains as strings");
}
if (opts.pem) {
pem = opts.pem;
} else if (opts.jwk) {
jwk = opts.jwk;
} else {
if (!opts.key) {
throw new Error("You must pass options.key as a JSON web key");
} else if (opts.key.kty) {
jwk = opts.key;
} else {
pem = opts.key;
}
}
if (pem) {
try {
Rasha = require('rasha');
} catch(e) {
throw new Error("Rasha.js is an optional dependency for PEM-to-JWK.\n"
+ "Install it if you'd like to use it:\n"
+ "\tnpm install --save rasha\n"
+ "Otherwise supply a jwk as the private key."
);
}
jwk = Rasha.importSync({ pem: pem });
}
opts.jwk = jwk;
return opts;
};
CSR.sync = function (opts) {
opts = CSR._prepare(opts);
var bytes = CSR.createSync(opts);
return CSR._encode(opts, bytes);
};
CSR._encode = function (opts, bytes) {
if ('der' === (opts.encoding||'').toLowerCase()) {
return bytes;
}
return PEM.packBlock({
type: "CERTIFICATE REQUEST"
, bytes: bytes /* { jwk: jwk, domains: opts.domains } */
});
};
CSR.createSync = function createCsr(opts) {
var hex = CSR.request(opts.jwk, opts.domains);
var csr = CSR.signSync(opts.jwk, hex);
return Enc.hexToBuf(csr);
};
CSR.create = function createCsr(opts) {
var hex = CSR.request(opts.jwk, opts.domains);
return CSR.sign(opts.jwk, hex).then(function (csr) {
return Enc.hexToBuf(csr);
});
};
CSR.request = function createCsrBodyEc(jwk, domains) {
var asn1pub = X509.packCsrPublicKey(jwk);
return X509.packCsr(asn1pub, domains);
};
CSR.signSync = function csrEcSig(jwk, request) {
var keypem = PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: X509.packPkcs1(jwk) });
var sig = RSA.signSync(keypem, Enc.hexToBuf(request));
return CSR.toDer({ request: request, signature: sig });
};
CSR.sign = function csrEcSig(jwk, request) {
var keypem = PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: X509.packPkcs1(jwk) });
return RSA.sign(keypem, Enc.hexToBuf(request)).then(function (sig) {
return CSR.toDer({ request: request, signature: sig });
});
};
CSR.toDer = function encode(opts) {
var sty = ASN1('30'
// 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
, ASN1('06', '2a864886f70d01010b')
, ASN1('05')
);
return ASN1('30'
// The Full CSR Request Body
, opts.request
// The Signature Type
, sty
// The Signature
, ASN1.BitStr(Enc.bufToHex(opts.signature))
);
};
//
// RSA
//
// Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
RSA.signSync = function signRsaSync(keypem, ab) {
// Signer is a stream
var sign = crypto.createSign('SHA256');
sign.write(ab);
sign.end();
// The signature is ASN1 encoded, as it turns out
var sig = sign.sign(keypem);
// Convert to a JavaScript ArrayBuffer just because
return sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength);
};
RSA.sign = function signRsa(keypem, ab) {
return Promise.resolve().then(function () {
return RSA.signSync(keypem, ab);
});
};

View File

@ -0,0 +1,34 @@
'use strict';
var Enc = module.exports;
Enc.base64ToHex = function base64ToHex(b64) {
return Buffer.from(b64, 'base64').toString('hex').toLowerCase();
};
Enc.bufToBase64 = function bufToBase64(u8) {
// we want to maintain api compatability with browser APIs,
// so we assume that this could be a Uint8Array
return Buffer.from(u8).toString('base64');
};
Enc.bufToHex = function toHex(u8) {
return Buffer.from(u8).toString('hex').toLowerCase();
};
Enc.hexToBuf = function (hex) {
return Buffer.from(hex, 'hex');
};
Enc.numToHex = function numToHex(d) {
d = d.toString(16);
if (d.length % 2) {
return '0' + d;
}
return d;
};
Enc.utf8ToHex = function utf8ToHex(str) {
// node can properly handle utf-8 strings
return Buffer.from(str).toString('hex').toLowerCase();
};

12
lib/rsa-csr/lib/pem.js Normal file
View File

@ -0,0 +1,12 @@
'use strict';
var Enc = require('./encoding.js');
var PEM = module.exports;
PEM.packBlock = function (opts) {
// TODO allow for headers?
return '-----BEGIN ' + opts.type + '-----\n'
+ Enc.bufToBase64(opts.bytes).match(/.{1,64}/g).join('\n') + '\n'
+ '-----END ' + opts.type + '-----'
;
};

View File

@ -0,0 +1,111 @@
'use strict';
// We believe in a proactive approach to sustainable open source.
// As part of that we make it easy for you to opt-in to following our progress
// and we also stay up-to-date on telemetry such as operating system and node
// version so that we can focus our efforts where they'll have the greatest impact.
//
// Want to learn more about our Terms, Privacy Policy, and Mission?
// Check out https://therootcompany.com/legal/
var os = require('os');
var crypto = require('crypto');
var https = require('https');
var pkg = require('../package.json');
// to help focus our efforts in the right places
var data = {
package: pkg.name
, version: pkg.version
, node: process.version
, arch: process.arch || os.arch()
, platform: process.platform || os.platform()
, release: os.release()
};
function addCommunityMember(opts) {
setTimeout(function () {
var req = https.request({
hostname: 'api.therootcompany.com'
, port: 443
, path: '/api/therootcompany.com/public/community'
, method: 'POST'
, headers: { 'Content-Type': 'application/json' }
}, function (resp) {
// let the data flow, so we can ignore it
resp.on('data', function () {});
//resp.on('data', function (chunk) { console.log(chunk.toString()); });
resp.on('error', function () { /*ignore*/ });
//resp.on('error', function (err) { console.error(err); });
});
var obj = JSON.parse(JSON.stringify(data));
obj.action = 'updates';
try {
obj.ppid = ppid(obj.action);
} catch(e) {
// ignore
//console.error(e);
}
obj.name = opts.name || undefined;
obj.address = opts.email;
obj.community = 'node.js@therootcompany.com';
req.write(JSON.stringify(obj, 2, null));
req.end();
req.on('error', function () { /*ignore*/ });
//req.on('error', function (err) { console.error(err); });
}, 50);
}
function ping(action) {
setTimeout(function () {
var req = https.request({
hostname: 'api.therootcompany.com'
, port: 443
, path: '/api/therootcompany.com/public/ping'
, method: 'POST'
, headers: { 'Content-Type': 'application/json' }
}, function (resp) {
// let the data flow, so we can ignore it
resp.on('data', function () { });
//resp.on('data', function (chunk) { console.log(chunk.toString()); });
resp.on('error', function () { /*ignore*/ });
//resp.on('error', function (err) { console.error(err); });
});
var obj = JSON.parse(JSON.stringify(data));
obj.action = action;
try {
obj.ppid = ppid(obj.action);
} catch(e) {
// ignore
//console.error(e);
}
req.write(JSON.stringify(obj, 2, null));
req.end();
req.on('error', function (/*e*/) { /*console.error('req.error', e);*/ });
}, 50);
}
// to help identify unique installs without getting
// the personally identifiable info that we don't want
function ppid(action) {
var parts = [ action, data.package, data.version, data.node, data.arch, data.platform, data.release ];
var ifaces = os.networkInterfaces();
Object.keys(ifaces).forEach(function (ifname) {
if (/^en/.test(ifname) || /^eth/.test(ifname) || /^wl/.test(ifname)) {
if (ifaces[ifname] && ifaces[ifname].length) {
parts.push(ifaces[ifname][0].mac);
}
}
});
return crypto.createHash('sha1').update(parts.join(',')).digest('base64');
}
module.exports.ping = ping;
module.exports.joinCommunity = addCommunityMember;
if (require.main === module) {
ping('install');
//addCommunityMember({ name: "AJ ONeal", email: 'coolaj86@gmail.com' });
}

66
lib/rsa-csr/lib/x509.js Normal file
View File

@ -0,0 +1,66 @@
'use strict';
var ASN1 = require('./asn1.js');
var Enc = require('./encoding.js');
var X509 = module.exports;
X509.packCsr = function (asn1pubkey, domains) {
return ASN1('30'
// Version (0)
, ASN1.UInt('00')
// 2.5.4.3 commonName (X.520 DN component)
, ASN1('30', ASN1('31', ASN1('30', ASN1('06', '550403'), ASN1('0c', Enc.utf8ToHex(domains[0])))))
// Public Key (RSA or EC)
, asn1pubkey
// Request Body
, ASN1('a0'
, ASN1('30'
// 1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
, ASN1('06', '2a864886f70d01090e')
, ASN1('31'
, ASN1('30'
, ASN1('30'
// 2.5.29.17 subjectAltName (X.509 extension)
, ASN1('06', '551d11')
, ASN1('04'
, ASN1('30', domains.map(function (d) {
return ASN1('82', Enc.utf8ToHex(d));
}).join(''))))))))
);
};
X509.packPkcs1 = function (jwk) {
var n = ASN1.UInt(Enc.base64ToHex(jwk.n));
var e = ASN1.UInt(Enc.base64ToHex(jwk.e));
if (!jwk.d) {
return Enc.hexToBuf(ASN1('30', n, e));
}
return Enc.hexToBuf(ASN1('30'
, ASN1.UInt('00')
, n
, e
, ASN1.UInt(Enc.base64ToHex(jwk.d))
, ASN1.UInt(Enc.base64ToHex(jwk.p))
, ASN1.UInt(Enc.base64ToHex(jwk.q))
, ASN1.UInt(Enc.base64ToHex(jwk.dp))
, ASN1.UInt(Enc.base64ToHex(jwk.dq))
, ASN1.UInt(Enc.base64ToHex(jwk.qi))
));
};
X509.packCsrPublicKey = function (jwk) {
// Sequence the key
var n = ASN1.UInt(Enc.base64ToHex(jwk.n));
var e = ASN1.UInt(Enc.base64ToHex(jwk.e));
var asn1pub = ASN1('30', n, e);
//var asn1pub = X509.packPkcs1({ kty: jwk.kty, n: jwk.n, e: jwk.e });
// Add the CSR pub key header
return ASN1('30', ASN1('30', ASN1('06', '2a864886f70d010101'), ASN1('05')), ASN1.BitStr(asn1pub));
};

34
lib/rsa-csr/package.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "rsa-csr",
"version": "1.0.7",
"description": "💯 A focused, zero-dependency library to generate a Certificate Signing Request (CSR) and sign it!",
"homepage": "https://git.coolaj86.com/coolaj86/rsa-csr.js",
"main": "index.js",
"bin": {
"rsa-csr": "bin/rsa-csr.js"
},
"files": [
"bin",
"fixtures",
"lib"
],
"directories": {
"lib": "lib"
},
"scripts": {
"postinstall": "node lib/telemetry.js event:install",
"test": "bash test.sh"
},
"repository": {
"type": "git",
"url": "https://git.coolaj86.com/coolaj86/rsa-csr.js"
},
"keywords": [
"zero-dependency",
"CSR",
"RSA",
"x509"
],
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "MPL-2.0"
}

View File

@ -1,143 +0,0 @@
'use strict';
// for forge
function _bigIntToBase64Url(fbin) {
var hex = fbin.toRadix(16);
if (hex.length % 2) {
// Invalid hex string
hex = '0' + hex;
}
var buf = Buffer.from(hex, 'hex');
var b64 = buf.toString('base64');
var b64Url = b64.replace(/[+]/g, "-").replace(/\//g, "_").replace(/=/g,"");
return b64Url;
}
/*
// I think this doesn't work because toByteArray() returns signed bytes
function _xxx_bigIntToBase64Url(fbin) {
if (!fbin.toByteArray) {
console.log('fbin');
console.log(fbin);
}
var byteArray = fbin.toByteArray();
var buf = Buffer.from(byteArray);
var b64 = buf.toString('base64');
var b64Url = b64.replace(/[+]/g, "-").replace(/\//g, "_").replace(/=/g,"");
return b64Url;
}
*/
var extrac = module.exports = {
//
// internals
//
_forgeToPrivateJwk: function (keypair) {
var k = keypair._forge;
return {
kty: "RSA"
, n: _bigIntToBase64Url(k.n)
, e: _bigIntToBase64Url(k.e)
, d: _bigIntToBase64Url(k.d)
, p: _bigIntToBase64Url(k.p)
, q: _bigIntToBase64Url(k.q)
, dp: _bigIntToBase64Url(k.dP)
, dq: _bigIntToBase64Url(k.dQ)
, qi: _bigIntToBase64Url(k.qInv)
};
}
, _forgeToPublicJwk: function (keypair) {
var k = keypair._forge || keypair._forgePublic;
return {
kty: "RSA"
, n: _bigIntToBase64Url(k.n)
, e: _bigIntToBase64Url(k.e)
};
}
//
// Import Forge
//
, _forgeImportJwk: require('./rsa-forge')._forgeImportJwk
, _forgeImportPublicJwk: require('./rsa-forge')._forgeImportPublicJwk
, _forgeImportPem: require('./rsa-forge')._forgeImportPem
, _forgeImportPublicPem: require('./rsa-forge')._forgeImportPublicPem
, importForge: function (keypair) {
extrac._forgeImportJwk(keypair);
if (keypair.privateKeyPem) {
extrac._forgeImportPem(keypair);
}
if (keypair.publicKeyPem) {
extrac._forgeImportPublicPem(keypair);
}
return keypair;
}
//
// Export JWK
//
, exportPrivateJwk: function (keypair) {
var hasUrsaPrivate = keypair._ursa && true;
var hasPrivatePem = keypair.privateKeyPem && true;
var hasForgePrivate = keypair._forge && true;
if (keypair.privateKeyJwk) {
return keypair.privateKeyJwk;
}
if (!hasForgePrivate) {
if (hasUrsaPrivate && !hasPrivatePem) {
keypair.privateKeyPem = keypair._ursa.toPrivatePem().toString('ascii');
}
if (keypair.privateKeyPem) {
extrac._forgeImportPem(keypair);
}
}
if (keypair._forge) {
return extrac._forgeToPrivateJwk(keypair);
}
throw new Error("None of privateKeyPem, _ursa, _forge, or privateKeyJwk found. No way to export private key Jwk");
}
, exportPublicJwk: function (keypair) {
var hasUrsaPublic = (keypair._ursa || keypair._ursaPublic) && true;
var hasPublicPem = (keypair.privateKeyPem || keypair.publicKeyPem) && true;
var hasForgePublic = keypair._forge && true;
if (keypair.publicKeyJwk) {
return keypair.publicKeyJwk;
}
if (keypair.privateKeyJwk) {
return {
kty: 'RSA'
, n: keypair.privateKeyJwk.n
, e: keypair.privateKeyJwk.e
};
}
if (!hasForgePublic) {
if (hasUrsaPublic && !hasPublicPem) {
keypair.publicKeyPem = (keypair._ursa || keypair._ursaPublic).toPublicPem().toString('ascii');
}
if (keypair.publicKeyPem) {
extrac._forgeImportPublicPem(keypair);
}
}
if (keypair._forge || keypair._forgePublic) {
return extrac._forgeToPublicJwk(keypair);
}
throw new Error("None of publicKeyPem privateKeyPem, _ursa, _forge, publicKeyJwk, or privateKeyJwk found. No way to export private key Jwk");
}
};

View File

@ -1,181 +0,0 @@
'use strict';
var forge = require('node-forge');
function notToJson() {
return undefined;
}
var forgec = module.exports = {
//
// to components
//
_toStandardBase64: function (str) {
var b64 = str.replace(/-/g, "+").replace(/_/g, "/").replace(/=/g, "");
switch (b64.length % 4) {
case 2: b64 += "=="; break;
case 3: b64 += "="; break;
}
return b64;
}
, _base64UrlToBin: function (base64) {
var std64 = forgec._toStandardBase64(base64);
var hex = new Buffer(std64, 'base64').toString("hex");
return new forge.jsbn.BigInteger(hex, 16);
}
, _privateJwkToComponents: function (jwk) {
var components = [];
// [ 'n', 'e', 'd', 'p', 'q', 'dP', 'dQ', 'qInv' ]
[ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi' ].forEach(function (key) {
components.push(forgec._base64UrlToBin(jwk[key]));
});
return components;
}
, _publicJwkToComponents: function (jwk) {
var components = [];
[ 'n', 'e' ].forEach(function (key) {
components.push(forgec._base64UrlToBin(jwk[key]));
});
return components;
}
//
// Generate New Keypair
//
, generateKeypair: function (bitlen, exp, options, cb) {
var fkeypair = forge.pki.rsa.generateKeyPair({ bits: bitlen || 2048, e: exp || 0x10001 });
var result = {
_forge: fkeypair.privateKey
, _forgePublic: fkeypair.publicKey
};
result._forge.toJSON = notToJson;
result._forgePublic.toJSON = notToJson;
cb(null, result);
}
//
// Import (no-op)
//
, _forgeImportJwk: function (keypair) {
if (!keypair._forge && keypair.privateKeyJwk) {
keypair._forge = forge.pki.rsa.setPrivateKey.apply(
forge.pki.rsa
, forgec._privateJwkToComponents(keypair.privateKeyJwk)
);
keypair._forge.toJSON = notToJson;
}
forgec._forgeImportPublicJwk(keypair);
}
, _forgeImportPublicJwk: function (keypair) {
if (keypair._forgePublic) {
return;
}
if (keypair._forge) {
keypair._forgePublic = forge.pki.rsa.setPublicKey(keypair._forge.n, keypair._forge.e);
}
else if (keypair.publicKeyJwk) {
keypair._forgePublic = forge.pki.rsa.setPublicKey.apply(
forge.pki.rsa
, forgec._publicJwkToComponents(keypair.publicKeyJwk || keypair.privateKeyJwk)
);
}
if (keypair._forgePublic) {
keypair._forgePublic.toJSON = notToJson;
}
}
, _forgeImportPem: function (keypair) {
if (!keypair._forge && keypair.privateKeyPem) {
keypair._forge = forge.pki.privateKeyFromPem(keypair.privateKeyPem);
keypair._forge.toJSON = notToJson;
}
forgec._forgeImportPublicPem(keypair);
}
, _forgeImportPublicPem: function (keypair) {
if (keypair._forgePublic) {
return;
}
if (keypair._forge) {
keypair._forgePublic = forge.pki.rsa.setPublicKey(keypair._forge.n, keypair._forge.e);
}
else if (keypair.publicKeyPem) {
keypair._forgePublic = keypair._forgePublic || forge.pki.publicKeyFromPem(keypair.publicKeyPem);
}
if (keypair._forgePublic) {
keypair._forgePublic.toJSON = notToJson;
}
}
, import: function (keypair) {
// no-op since this must be done anyway in extra
return keypair;
}
//
// Export Public / Private PEMs
//
, exportPrivatePem: function (keypair) {
if (keypair.privateKeyPem) {
return keypair.privateKeyPem;
}
if (keypair.privateKeyJwk && !keypair._forge) {
forgec._forgeImportJwk(keypair);
}
if (keypair._forge) {
return forge.pki.privateKeyToPem(keypair._forge);
}
throw new Error("None of privateKeyPem, _forge, or privateKeyJwk found. No way to export private key PEM");
}
, exportPublicPem: function (keypair) {
if (keypair.publicKeyPem) {
return keypair.publicKeyPem;
}
if ((keypair.privateKeyJwk || keypair.publicKeyJwk)
&& !(keypair._forge || keypair._forgePublic)
) {
forgec._forgeImportPublicJwk(keypair);
}
if (!keypair._forge) {
if (keypair.privateKeyPem) {
forgec._forgeImportPem(keypair);
}
}
if (keypair.publicKeyPem) {
return keypair.publicKeyPem;
}
if (keypair._forge || keypair._forgePublic) {
return forge.pki.publicKeyToPem(keypair._forgePublic || keypair._forge);
}
throw new Error("None of publicKeyPem, _forge, _forgePublic, publicKeyJwk, privateKeyPem, or privateKeyJwk found. No way to export public key PEM");
}
//, exportPrivateKeyJwk: NOT IMPLEMENTED HERE
//, exportPublicKeyJwk: NOT IMPLEMENTED HERE
};

View File

@ -1,172 +0,0 @@
'use strict';
var ursa = require('ursa');
function notToJson() {
return undefined;
}
var ursac = module.exports = {
//
// to components
//
_privateJwkToComponents: function (jwk) {
var components = [];
[ 'n', 'e', 'p', 'q', 'dp', 'dq', 'qi', 'd' ].forEach(function (key) {
components.push(new Buffer(jwk[key], 'base64'));
});
return components;
}
, _publicJwkToComponents: function (jwk) {
var components = [];
[ 'n', 'e' ].forEach(function (key) {
components.push(new Buffer(jwk[key], 'base64'));
});
return components;
}
//
// Generate New Keypair
//
, generateKeypair: function (bitlen, exp, options, cb) {
var keypair = ursa.generatePrivateKey(bitlen || 2048, exp || 65537);
keypair.toJSON = notToJson;
cb(null, {
_ursa: keypair
});
}
//
// Import
//
, _ursaImportPem: function (keypair) {
if (keypair._ursa) {
return;
}
if (keypair.privateKeyPem) {
keypair._ursa = ursa.createPrivateKey(keypair.privateKeyPem);
keypair._ursa.toJSON = notToJson;
}
else if (keypair.publicKeyPem) {
ursac._ursaImportPublicPem(keypair);
}
}
, _ursaImportPublicPem: function (keypair) {
if (keypair._ursa || keypair._ursaPublic) {
return;
}
if (keypair.publicKeyPem) {
keypair._ursaPublic = ursa.createPublicKey(keypair.publicKeyPem);
keypair._ursaPublic.toJSON = notToJson;
}
}
, _ursaImportJwk: function (keypair) {
if (keypair._ursa) {
return;
}
if (keypair.privateKeyJwk) {
keypair._ursa = ursa.createPrivateKeyFromComponents.apply(
ursa
, ursac._privateJwkToComponents(keypair.privateKeyJwk)
);
keypair._ursa.toJSON = notToJson;
}
else if (keypair.publicKeyJwk) {
ursac._ursaImportPublicJwk(keypair);
}
}
, _ursaImportPublicJwk: function (keypair) {
if (keypair._ursa || keypair._ursaPublic) {
return;
}
if (keypair.publicKeyJwk) {
keypair._ursaPublic = ursa.createPublicKeyFromComponents.apply(
ursa
, ursac._publicJwkToComponents(keypair.publicKeyJwk)
);
keypair._ursaPublic.toJSON = notToJson;
}
}
, import: function (keypair) {
ursac._ursaImportJwk(keypair);
ursac._ursaImportPem(keypair);
return keypair;
}
//
// Export Public / Private PEMs
//
, _pemBinToPem: function (pem) {
return pem.toString('ascii').replace(/[\n\r]+/g, '\r\n');
}
, exportPrivatePem: function (keypair) {
if (keypair.privateKeyPem) {
return keypair.privateKeyPem;
}
if (keypair._ursa) {
return ursac._pemBinToPem(keypair._ursa.toPrivatePem());
}
if (keypair.privateKeyJwk) {
ursac._ursaImportJwk(keypair);
return ursac._pemBinToPem(keypair._ursa.toPrivatePem());
}
throw new Error("None of privateKeyPem, _ursa, or privateKeyJwk found. No way to export private key PEM");
}
, exportPublicPem: function (keypair) {
if (keypair.publicKeyPem) {
return keypair.publicKeyPem;
}
if (keypair._ursa || keypair._ursaPublic) {
return ursac._pemBinToPem((keypair._ursa || keypair._ursaPublic).toPublicPem());
}
if (keypair.publicKeyJwk) {
ursac._ursaImportPublicJwk(keypair);
return ursac._pemBinToPem(keypair._ursaPublic.toPublicPem());
}
if (keypair.privateKeyJwk) {
ursac._ursaImportJwk(keypair);
return ursac._pemBinToPem(keypair._ursa.toPublicPem());
}
if (keypair.privateKeyPem) {
ursac._ursaImportPem(keypair);
return ursac._pemBinToPem(keypair._ursa.toPublicPem());
}
throw new Error("None of publicKeyPem, _ursa, publicKeyJwk, privateKeyPem, or privateKeyJwk found. No way to export public key PEM");
}
//, exportPrivateKeyJwk: NOT IMPLEMENTED HERE
//, exportPublicKeyJwk: NOT IMPLEMENTED HERE
};

213
lib/rsa.js Normal file
View File

@ -0,0 +1,213 @@
// Copyright 2016-2018 AJ ONeal. All rights reserved
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
var RSA = module.exports;
RSA.utils = {};
try {
require('buffer-v6-polyfill');
} catch(e) {
/* ignore */
}
var Keypairs = require('keypairs');
var RSACSR = require('./rsa-csr');
var NOBJ = {};
var DEFAULT_BITLEN = 2048;
var DEFAULT_EXPONENT = 0x10001;
RSA.generateKeypair = function (options, cb, extra1, extra2) {
var length;
var exponent;
if ('function' === typeof extra2) {
length = options || DEFAULT_BITLEN;
exponent = cb || DEFAULT_EXPONENT;
options = extra1 || NOBJ;
cb = extra2;
} else {
if (!options) { options = NOBJ; }
length = options.bitlen || DEFAULT_BITLEN;
exponent = options.exp || DEFAULT_EXPONENT;
}
try {
var keypair = require('./generate-privkey.js')(length, exponent);
keypair.thumbprint = RSA.thumbprint(keypair);
cb(null, keypair);
} catch(e) {
cb(e);
}
};
RSA.import = function (options) {
options = JSON.parse(JSON.stringify(options));
// Private Keys
if (options.privateKeyPem) {
if (!options.privateKeyJwk) {
options.privateKeyJwk = Keypairs._importSync({ pem: options.privateKeyPem });
}
}
if (options.privateKeyJwk) {
if (!options.privateKeyPem) {
options.privateKeyPem = Keypairs._exportSync({
jwk: options.privateKeyJwk
, format: options.format || 'pkcs1'
, encoding: options.encoding || 'pem'
});
}
}
// Public Keys
if (options.publicKeyPem || options.privateKeyPem) {
if (!options.publicKeyJwk) {
options.publicKeyJwk = Keypairs._importSync({
pem: options.publicKeyPem || options.privateKeyPem
, public: true
});
}
}
if (options.publicKeyJwk || options.privateKeyJwk) {
if (!options.publicKeyPem) {
options.publicKeyPem = Keypairs._exportSync({
jwk: options.publicKeyJwk || options.privateKeyJwk
, format: options.format || 'pkcs1'
, encoding: options.encoding || 'pem'
, public: true
});
}
}
if (!options.publicKeyPem) {
throw new Error("Error: no keys were present to import");
}
// Consistent CRLF
if (options.privateKeyPem) {
options.privateKeyPem = options.privateKeyPem
.trim().replace(/[\r\n]+/g, '\r\n') + '\r\n';
}
options.publicKeyPem = options.publicKeyPem
.trim().replace(/[\r\n]+/g, '\r\n') + '\r\n';
// Thumbprint
if (!options.thumbprint) {
options.thumbprint = RSA._thumbprint(options);
}
return options;
};
RSA.exportPrivatePem = function (keypair) {
keypair = RSA.import(keypair);
return keypair.privateKeyPem;
};
RSA.exportPublicPem = function (keypair) {
keypair = RSA.import(keypair);
return keypair.publicKeyPem;
};
RSA.exportPrivateJwk = function (keypair) {
keypair = RSA.import(keypair);
return keypair.privateKeyJwk;
};
RSA.exportPublicJwk = function (keypair) {
if (!keypair.publicKeyJwk) {
keypair = RSA.import(keypair);
}
return keypair.publicKeyJwk;
};
RSA.signJws = RSA.generateJws = RSA.generateSignatureJws = RSA.generateSignatureJwk =
function (keypair, header, protect, payload) {
// old (keypair, payload, nonce)
var nonce;
keypair = RSA.import(keypair);
keypair.publicKeyJwk = RSA.exportPublicJwk(keypair);
if ('string' === typeof protect || ('undefined' === typeof protect && 'undefined' === typeof payload)) {
console.warn("deprecation notice: new signature for signJws(keypair, header, protect, payload)");
// old API
payload = header;
nonce = protect;
protect = undefined;
header = {
alg: "RS256"
, jwk: keypair.publicKeyJwk
};
protect = { nonce: nonce };
}
// Compute JWS signature
var protectedHeader = "";
if (protect) {
protectedHeader = JSON.stringify(protect); // { alg: prot.alg, nonce: prot.nonce, url: prot.url });
}
var protected64 = RSA.utils.toWebsafeBase64(Buffer.from(protectedHeader).toString('base64'));
var payload64 = RSA.utils.toWebsafeBase64(payload.toString('base64'));
var raw = protected64 + "." + payload64;
var pem = RSA.exportPrivatePem(keypair);
var signer = require('crypto').createSign("RSA-SHA256");
signer.update(raw);
return {
header: header
, protected: protected64
, payload: payload64
, signature: signer.sign(pem, 'base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '')
};
};
RSA.generateCsrPem = function (keypair, domains) {
keypair = RSA.import(keypair);
return RSACSR.sync({ jwk: keypair.privateKeyJwk, domains: domains });
};
RSA.generateCsrDer = function (keypair, domains) {
keypair = RSA.import(keypair);
return RSACSR.sync({
jwk: keypair.privateKeyJwk
, domains: domains
, encoding: 'der'
});
};
RSA.generateCsrDerWeb64 =RSA.generateCsrWeb64 = function (keypair, names) {
var buf = RSA.generateCsrDer(keypair, names);
var b64 = buf.toString('base64');
return RSA.utils.toWebsafeBase64(b64);
};
RSA._thumbprintInput = function (n, e) {
// #L147 const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
return Buffer.from('{"e":"'+ e + '","kty":"RSA","n":"'+ n +'"}', 'ascii');
};
RSA._thumbprint = function (keypair) {
var publicKeyJwk = keypair.publicKeyJwk;
if (!publicKeyJwk.e || !publicKeyJwk.n) {
throw new Error("You must provide an RSA jwk with 'e' and 'n' (the public components)");
}
var input = RSA._thumbprintInput(publicKeyJwk.n, publicKeyJwk.e);
var base64Digest = require('crypto').createHash('sha256').update(input).digest('base64');
return RSA.utils.toWebsafeBase64(base64Digest);
};
RSA.thumbprint = function (keypair) {
if (!keypair.publicKeyJwk) {
keypair.publicKeyJwk = RSA.exportPublicJwk(keypair);
}
return RSA._thumbprint(keypair);
};
RSA.utils.toWebsafeBase64 = function (b64) {
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g,"");
};
RSA.exportPrivateKey = RSA.exportPrivatePem;
RSA.exportPublicKey = RSA.exportPublicPem;

111
lib/telemetry.js Normal file
View File

@ -0,0 +1,111 @@
'use strict';
// We believe in a proactive approach to sustainable open source.
// As part of that we make it easy for you to opt-in to following our progress
// and we also stay up-to-date on telemetry such as operating system and node
// version so that we can focus our efforts where they'll have the greatest impact.
//
// Want to learn more about our Terms, Privacy Policy, and Mission?
// Check out https://therootcompany.com/legal/
var os = require('os');
var crypto = require('crypto');
var https = require('https');
var pkg = require('../package.json');
// to help focus our efforts in the right places
var data = {
package: pkg.name
, version: pkg.version
, node: process.version
, arch: process.arch || os.arch()
, platform: process.platform || os.platform()
, release: os.release()
};
function addCommunityMember(opts) {
setTimeout(function () {
var req = https.request({
hostname: 'api.therootcompany.com'
, port: 443
, path: '/api/therootcompany.com/public/community'
, method: 'POST'
, headers: { 'Content-Type': 'application/json' }
}, function (resp) {
// let the data flow, so we can ignore it
resp.on('data', function () {});
//resp.on('data', function (chunk) { console.log(chunk.toString()); });
resp.on('error', function () { /*ignore*/ });
//resp.on('error', function (err) { console.error(err); });
});
var obj = JSON.parse(JSON.stringify(data));
obj.action = 'updates';
try {
obj.ppid = ppid(obj.action);
} catch(e) {
// ignore
//console.error(e);
}
obj.name = opts.name || undefined;
obj.address = opts.email;
obj.community = 'node.js@therootcompany.com';
req.write(JSON.stringify(obj, 2, null));
req.end();
req.on('error', function () { /*ignore*/ });
//req.on('error', function (err) { console.error(err); });
}, 50);
}
function ping(action) {
setTimeout(function () {
var req = https.request({
hostname: 'api.therootcompany.com'
, port: 443
, path: '/api/therootcompany.com/public/ping'
, method: 'POST'
, headers: { 'Content-Type': 'application/json' }
}, function (resp) {
// let the data flow, so we can ignore it
resp.on('data', function () { });
//resp.on('data', function (chunk) { console.log(chunk.toString()); });
resp.on('error', function () { /*ignore*/ });
//resp.on('error', function (err) { console.error(err); });
});
var obj = JSON.parse(JSON.stringify(data));
obj.action = action;
try {
obj.ppid = ppid(obj.action);
} catch(e) {
// ignore
//console.error(e);
}
req.write(JSON.stringify(obj, 2, null));
req.end();
req.on('error', function (/*e*/) { /*console.error('req.error', e);*/ });
}, 50);
}
// to help identify unique installs without getting
// the personally identifiable info that we don't want
function ppid(action) {
var parts = [ action, data.package, data.version, data.node, data.arch, data.platform, data.release ];
var ifaces = os.networkInterfaces();
Object.keys(ifaces).forEach(function (ifname) {
if (/^en/.test(ifname) || /^eth/.test(ifname) || /^wl/.test(ifname)) {
if (ifaces[ifname] && ifaces[ifname].length) {
parts.push(ifaces[ifname][0].mac);
}
}
});
return crypto.createHash('sha1').update(parts.join(',')).digest('base64');
}
module.exports.ping = ping;
module.exports.joinCommunity = addCommunityMember;
if (require.main === module) {
ping('install');
//addCommunityMember({ name: "AJ ONeal", email: 'coolaj86@gmail.com' });
}

260
node.js
View File

@ -1,260 +0,0 @@
/*!
* rsa-compat
* Copyright(c) 2016 AJ ONeal <aj@daplie.com> https://daplie.com
* Apache-2.0 OR MIT (and hence also MPL 2.0)
*/
'use strict';
require('buffer-v6-polyfill');
var RSA = {};
var NOBJ = {};
function create(deps) {
var crypto = require('crypto');
deps = deps || {};
deps.NOBJ = {};
deps.RSA = RSA;
try {
RSA._URSA = require('ursa');
} catch(e) {
// ignore
}
RSA.utils = require('./lib/key-utils.js');
RSA.utils.toWebsafeBase64 = function (b64) {
return b64.replace(/[+]/g, "-").replace(/\//g, "_").replace(/=/g,"");
};
RSA.utils._forgeBytesToBuf = function (bytes) {
var forge = require("node-forge");
return new Buffer(forge.util.bytesToHex(bytes), "hex");
};
RSA._internal = require('./lib/node');//.create(deps);
RSA._thumbprintInput = function (n, e) {
// #L147 const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
return new Buffer('{"e":"'+ e + '","kty":"RSA","n":"'+ n +'"}', 'ascii');
};
RSA.thumbprint = function (keypair) {
var publicKeyJwk = RSA.exportPublicJwk(keypair);
if (!publicKeyJwk.e || !publicKeyJwk.n) {
throw new Error("You must provide an RSA jwk with 'e' and 'n' (the public components)");
}
var input = RSA._thumbprintInput(publicKeyJwk.n, publicKeyJwk.e);
var base64Digest = crypto.createHash('sha256').update(input).digest('base64');
return RSA.utils.toWebsafeBase64(base64Digest);
};
RSA.generateKeypair = function (length, exponent, options, cb) {
if (!RSA._URSA && /arm|mips/i.test(require('os').arch) && !RSA._SLOW_WARN) {
console.warn("================================================================");
console.warn(" WARNING");
console.warn("================================================================");
console.warn("");
console.warn("WARNING: You are generating an RSA key using pure JavaScript on");
console.warn(" a VERY SLOW cpu. This could take DOZENS of minutes!");
console.warn("");
console.warn(" We recommend installing a C compiler and 'ursa'");
console.warn("");
console.warn("EXAMPLE:");
console.warn("");
console.warn(" sudo apt-get install build-essential && npm install ursa");
console.warn("");
console.warn("================================================================");
RSA._SLOW_WARN = true;
}
var keypair = {
privateKeyPem: undefined
, publicKeyPem: undefined
, privateKeyJwk: undefined
, publicKeyJwk: undefined
, _ursa: undefined
, _ursaPublic: undefined
, _forge: undefined
, _forgePublic: undefined
};
options = options || NOBJ;
RSA._internal.generateKeypair(length, exponent, options, function (err, keys) {
if (false !== options.jwk || options.thumbprint) {
keypair.privateKeyJwk = RSA._internal.exportPrivateJwk(keys);
if (options.public) {
keypair.publicKeyJwk = RSA._internal.exportPublicJwk(keys);
}
}
if (options.pem) {
keypair.privateKeyPem = RSA._internal.exportPrivatePem(keys);
if (options.public) {
keypair.publicKeyPem = RSA._internal.exportPublicPem(keys);
}
}
if (options.thumprint) {
keypair.thumbprint = RSA.thumbprint(keypair);
}
if (options.internal) {
//keypair._ursa = undefined;
//keypair._forge = undefined;
keypair._ursa = keys._ursa;
keypair._forge = keys._forge;
}
cb(null, keypair);
return;
});
};
RSA.import = function (keypair/*, options*/) {
keypair = RSA._internal.import(keypair, { internal: true });
keypair = RSA._internal.importForge(keypair, { internal: true });
//options = options || NOBJ; // ignore
if (keypair.privateKeyJwk || keypair.privateKeyPem || keypair._ursa || keypair._forge) {
keypair.privateKeyJwk = RSA._internal.exportPrivateJwk(keypair, { internal: true });
//keypair.privateKeyPem = RSA._internal.exportPrivatePem(keypair, { internal: true });
return keypair;
}
if (keypair.publicKeyJwk || keypair.publicKeyPem || keypair._ursaPublic || keypair._forgePublic) {
keypair.publicKeyJwk = RSA._internal.exportPublicJwk(keypair, { internal: true });
//keypair.publicKeyPem = RSA._internal.exportPublicPem(keypair, { internal: true });
return keypair;
}
throw new Error('found neither private nor public keypair in any supported format');
};
RSA._ursaGenerateSig = function (keypair, sha256Buf) {
var sig = keypair._ursa.sign('sha256', sha256Buf);
var sig64 = RSA.utils.toWebsafeBase64(sig.toString('base64'));
return sig64;
};
RSA._forgeGenerateSig = function (keypair, sha256Buf) {
var forge = require('node-forge');
var bufF = forge.util.createBuffer(sha256Buf.toString('binary'), 'binary');
var md = {
algorithm: 'sha256'
, blockLength: 64
, digestLength: 20
, digest: function () {
return bufF;
}
};
var sigF = keypair._forge.sign(md);
var sig64 = RSA.utils.toWebsafeBase64(
new Buffer(forge.util.bytesToHex(sigF), "hex").toString('base64')
);
return sig64;
};
RSA.signJws = RSA.generateJws = RSA.generateSignatureJws = RSA.generateSignatureJwk =
function (keypair, payload, nonce) {
keypair = RSA._internal.import(keypair);
keypair = RSA._internal.importForge(keypair);
keypair.publicKeyJwk = RSA.exportPublicJwk(keypair);
// Compute JWS signature
var protectedHeader = "";
if (nonce) {
protectedHeader = JSON.stringify({nonce: nonce});
}
var protected64 = RSA.utils.toWebsafeBase64(new Buffer(protectedHeader).toString('base64'));
var payload64 = RSA.utils.toWebsafeBase64(payload.toString('base64'));
var raw = protected64 + "." + payload64;
var sha256Buf = crypto.createHash('sha256').update(raw).digest();
var sig64;
if (RSA._URSA) {
sig64 = RSA._ursaGenerateSig(keypair, sha256Buf);
} else {
sig64 = RSA._forgeGenerateSig(keypair, sha256Buf);
}
return {
header: {
alg: "RS256"
, jwk: keypair.publicKeyJwk
}
, protected: protected64
, payload: payload64
, signature: sig64
};
};
//
// Generate CSR
//
RSA._generateCsrForge = function (keypair, names) {
var forge = require('node-forge');
keypair = RSA._internal.importForge(keypair);
// Create and sign the CSR
var csr = forge.pki.createCertificationRequest();
csr.publicKey = keypair._forgePublic;
// TODO should the commonName be shift()ed off so that it isn't also in altNames?
// http://stackoverflow.com/questions/5935369/ssl-how-do-common-names-cn-and-subject-alternative-names-san-work-together
csr.setSubject([{ name: 'commonName', value: names[0] }]);
var sans = names.map(function (name) {
return { type: 2, value: name };
});
csr.setAttributes([{
name: 'extensionRequest',
extensions: [{name: 'subjectAltName', altNames: sans}]
}]);
// TODO wrap with node crypto (as done for signature)
csr.sign(keypair._forge, forge.md.sha256.create());
return csr;
};
RSA.generateCsrAsn1 = function (keypair, names) {
var forge = require('node-forge');
var csr = RSA._generateCsrForge(keypair, names);
var asn1 = forge.pki.certificationRequestToAsn1(csr);
return RSA.utils._forgeBytesToBuf(asn1);
};
RSA.generateCsrPem = function (keypair, names) {
var forge = require('node-forge');
var csr = RSA._generateCsrForge(keypair, names);
return forge.pki.certificationRequestToPem(csr);
};
RSA.generateCsrDer = function (keypair, names) {
var forge = require('node-forge');
var csr = RSA._generateCsrForge(keypair, names);
var asn1 = forge.pki.certificationRequestToAsn1(csr);
var der = forge.asn1.toDer(asn1);
return RSA.utils._forgeBytesToBuf(der);
};
RSA.generateCsrDerWeb64 =RSA.generateCsrWeb64 = function (keypair, names) {
var buf = RSA.generateCsrDer(keypair, names);
var b64 = buf.toString('base64');
var web64 = RSA.utils.toWebsafeBase64(b64);
return web64;
};
RSA.exportPrivateKey = RSA._internal.exportPrivatePem;
RSA.exportPublicKey = RSA._internal.exportPublicPem;
RSA.exportPrivatePem = RSA._internal.exportPrivatePem;
RSA.exportPublicPem = RSA._internal.exportPublicPem;
RSA.exportPrivateJwk = RSA._internal.exportPrivateJwk;
RSA.exportPublicJwk = RSA._internal.exportPublicJwk;
return RSA;
}
module.exports.RSA = create(/*require('./lib/node')*/);
//module.exports.RSA.create = create;

27
package-lock.json generated Normal file
View File

@ -0,0 +1,27 @@
{
"name": "rsa-compat",
"version": "2.0.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"eckles": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.1.tgz",
"integrity": "sha512-auWyk/k8oSkVHaD4RxkPadKsLUcIwKgr/h8F7UZEueFDBO7BsE4y+H6IMUDbfqKIFPg/9MxV6KcBdJCmVVcxSA=="
},
"keypairs": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/keypairs/-/keypairs-1.2.14.tgz",
"integrity": "sha512-ZoZfZMygyB0QcjSlz7Rh6wT2CJasYEHBPETtmHZEfxuJd7bnsOG5AdtPZqHZBT+hoHvuWCp/4y8VmvTvH0Y9uA==",
"requires": {
"eckles": "^1.4.1",
"rasha": "^1.2.4"
}
},
"rasha": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.4.tgz",
"integrity": "sha512-GsIwKv+hYSumJyK9wkTDaERLwvWaGYh1WuI7JMTBISfYt13TkKFU/HFzlY4n72p8VfXZRUYm0AqaYhkZVxOC3Q=="
}
}
}

View File

@ -1,45 +1,47 @@
{ {
"name": "rsa-compat", "name": "rsa-compat",
"version": "1.2.5", "version": "2.0.8",
"engines": {
"node": ">=10.12"
},
"description": "RSA utils that work on Windows, Mac, and Linux with or without C compiler", "description": "RSA utils that work on Windows, Mac, and Linux with or without C compiler",
"main": "node.js", "main": "index.js",
"bin": {
"rsa-keygen-js": "bin/rsa-keygen.js"
},
"scripts": { "scripts": {
"test": "node tests" "test": "bash test.sh"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/Daplie/rsa-compat.js.git" "url": "https://git.coolaj86.com/coolaj86/rsa-compat.js.git"
}, },
"keywords": [ "keywords": [
"RSA", "RSA",
"ursa", "ursa",
"forge", "forge",
"certificate", "certificate",
"csr",
"tls", "tls",
"ssl", "ssl",
"javascript",
"js",
"node",
"node.js",
"windows", "windows",
"mac", "mac",
"linux", "linux",
"macOS",
"win",
"key", "key",
"jwk" "jwk"
], ],
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "(MIT OR Apache-2.0)", "license": "MPL-2.0",
"bugs": { "bugs": {
"url": "https://github.com/Daplie/rsa-compat.js/issues" "url": "https://git.coolaj86.com/coolaj86/rsa-compat.js/issues"
},
"homepage": "https://git.coolaj86.com/coolaj86/rsa-compat.js#readme",
"trulyOptionalDependencies": {
"buffer-v6-polyfill": "^1.0.3",
"node-forge": "^0.7.6",
"ursa-optional": "^0.9.10"
}, },
"homepage": "https://github.com/Daplie/rsa-compat.js#readme",
"dependencies": { "dependencies": {
"buffer-v6-polyfill": "^1.0.1", "keypairs": "^1.2.14"
"node-forge": "^0.6.41"
},
"optionalDependencies": {
"ursa": "^0.9.4"
} }
} }

8
test.sh Normal file
View File

@ -0,0 +1,8 @@
#!/bin/bash
set -e
node tests/generate-csr.js
node tests/generate-key.js
node tests/generate-key-new.js
node tests/generate-sig.js
node tests/reciprocate.js

33
tests/generate-key-new.js Normal file
View File

@ -0,0 +1,33 @@
'use strict';
var RSA = require('../').RSA;
RSA.generateKeypair(null, function (err, keys) {
if (!keys || !keys.privateKeyJwk) {
throw new Error("Expected privateKeyJwk, but it is missing");
}
var options = {
public: true // export public keys
, pem: true // export pems
, jwk: false // export jwks
, internal: true // preserve internal intermediate formats (_ursa, _forge)
//, thumbprint: true // JWK sha256 thumbprint
, bitlen: 2048
, exp: 65537
};
RSA.generateKeypair(options, function (err, keys) {
if (
(keys.publicKeyJwk && !keys.thumbprint)
|| !keys.privateKeyPem
|| !keys.publicKeyPem
//|| !keys.thumbprint
) {
console.error(Object.keys(keys));
throw new Error("Missing expected keys");
}
console.log('All is well!');
});
});

View File

@ -7,18 +7,6 @@ RSA.generateKeypair(null, null, null, function (err, keys) {
throw new Error("Expected privateKeyJwk, but it is missing"); throw new Error("Expected privateKeyJwk, but it is missing");
} }
if (
keys.publicKeyJwk
|| keys.privateKeyPem
|| keys.publicKeyPem
|| keys.thumbprint
|| keys._ursa
|| keys._forge
) {
console.error(Object.keys(keys));
throw new Error("Got unexpected keys");
}
var options = { var options = {
public: true // export public keys public: true // export public keys
, pem: true // export pems , pem: true // export pems
@ -32,7 +20,6 @@ RSA.generateKeypair(null, null, null, function (err, keys) {
|| !keys.privateKeyPem || !keys.privateKeyPem
|| !keys.publicKeyPem || !keys.publicKeyPem
//|| !keys.thumbprint //|| !keys.thumbprint
|| !(keys._ursa || keys._forge)
) { ) {
console.error(Object.keys(keys)); console.error(Object.keys(keys));
throw new Error("Missing expected keys"); throw new Error("Missing expected keys");

View File

@ -5,14 +5,14 @@ var RSA = require('../').RSA;
var keypair = { var keypair = {
privateKeyJwk: { privateKeyJwk: {
"kty": "RSA", "kty": "RSA",
"n": "AMJubTfOtAarnJytLE8fhNsEI8wnpjRvBXGK/Kp0675J10ORzxyMLqzIZF3tcrUkKBrtdc79u4X0GocDUgukpfkY+2UPUS/GxehUYbYrJYWOLkoJWzxn7wfoo9X1JgvBMY6wHQnTKvnzZdkom2FMhGxkLaEUGDSfsNznTTZNBBg9", "n": "AMJubTfOtAarnJytLE8fhNsEI8wnpjRvBXGK_Kp0675J10ORzxyMLqzIZF3tcrUkKBrtdc79u4X0GocDUgukpfkY-2UPUS_GxehUYbYrJYWOLkoJWzxn7wfoo9X1JgvBMY6wHQnTKvnzZdkom2FMhGxkLaEUGDSfsNznTTZNBBg9",
"e": "AQAB", "e": "AQAB",
"d": "HT8DCrv69G3n9uFNovE4yMEMqW7lX0m75eJkMze3Jj5xNOa/4qlrc+4IuuA2uuyfY72IVQRxqqqXOuvS8ZForZZk+kWSd6z45hrpbNAAHH2Rf7XwnwHY8VJrOQF3UtbktTWqHX36ITZb9Hmf18hWsIeEp8Ng7Ru9h7hNuVxKMjk=", "d": "HT8DCrv69G3n9uFNovE4yMEMqW7lX0m75eJkMze3Jj5xNOa_4qlrc-4IuuA2uuyfY72IVQRxqqqXOuvS8ZForZZk-kWSd6z45hrpbNAAHH2Rf7XwnwHY8VJrOQF3UtbktTWqHX36ITZb9Hmf18hWsIeEp8Ng7Ru9h7hNuVxKMjk=",
"p": "AONjOvZVAvhCM2JnLGWJG3+5Boar3MB5P4ezfExDmuyGET/w0C+PS60jbjB8TivQsSdEcGo7GOaOlmAX6EQtAec=", "p": "AONjOvZVAvhCM2JnLGWJG3-5Boar3MB5P4ezfExDmuyGET_w0C-PS60jbjB8TivQsSdEcGo7GOaOlmAX6EQtAec=",
"q": "ANrllgJsy4rTMfa3mQ50kMIcNahiEOearhAcJgQUCHuOjuEnhU9FfExA/m5FXjmEFQhRwkuhk0QaIqTGbUzxGDs=", "q": "ANrllgJsy4rTMfa3mQ50kMIcNahiEOearhAcJgQUCHuOjuEnhU9FfExA_m5FXjmEFQhRwkuhk0QaIqTGbUzxGDs=",
"dp": "ALuxHOpYIatqeZ+wKiVllx1GTOy8z+rQKnCI5wDMjQTPZU2yKSYY0g6IQFwlPyFLke8nvuLxBQzKhbWsBjzAKeE=", "dp": "ALuxHOpYIatqeZ-wKiVllx1GTOy8z-rQKnCI5wDMjQTPZU2yKSYY0g6IQFwlPyFLke8nvuLxBQzKhbWsBjzAKeE=",
"dq": "XLhDAmPzE6rBzy+VtXnKl247jEd9wZzTfh9uOuwBa9TG0Lhcz2cvb11YaH0ZnGNGRW/cTQzzxDUN1531TlIRYQ==", "dq": "XLhDAmPzE6rBzy-VtXnKl247jEd9wZzTfh9uOuwBa9TG0Lhcz2cvb11YaH0ZnGNGRW_cTQzzxDUN1531TlIRYQ==",
"qi": "AI2apz6ECfGwhsvIcU3+yFt+3CA78CUVsX4NUul5m3Cls2m+5MbGQG5K0hGpxjDC3OmXTq1Y5gnep5yUZvVPZI4=" "qi": "AI2apz6ECfGwhsvIcU3-yFt-3CA78CUVsX4NUul5m3Cls2m-5MbGQG5K0hGpxjDC3OmXTq1Y5gnep5yUZvVPZI4="
} }
}; };
@ -22,7 +22,7 @@ var ursaResult = {
"alg": "RS256", "alg": "RS256",
"jwk": { "jwk": {
"kty": "RSA", "kty": "RSA",
"n": "AMJubTfOtAarnJytLE8fhNsEI8wnpjRvBXGK/Kp0675J10ORzxyMLqzIZF3tcrUkKBrtdc79u4X0GocDUgukpfkY+2UPUS/GxehUYbYrJYWOLkoJWzxn7wfoo9X1JgvBMY6wHQnTKvnzZdkom2FMhGxkLaEUGDSfsNznTTZNBBg9", "n": "AMJubTfOtAarnJytLE8fhNsEI8wnpjRvBXGK_Kp0675J10ORzxyMLqzIZF3tcrUkKBrtdc79u4X0GocDUgukpfkY-2UPUS_GxehUYbYrJYWOLkoJWzxn7wfoo9X1JgvBMY6wHQnTKvnzZdkom2FMhGxkLaEUGDSfsNznTTZNBBg9",
"e": "AQAB" "e": "AQAB"
} }
}, },
@ -35,7 +35,7 @@ var forgeResult = {
"alg": "RS256", "alg": "RS256",
"jwk": { "jwk": {
"kty": "RSA", "kty": "RSA",
"n": "AMJubTfOtAarnJytLE8fhNsEI8wnpjRvBXGK/Kp0675J10ORzxyMLqzIZF3tcrUkKBrtdc79u4X0GocDUgukpfkY+2UPUS/GxehUYbYrJYWOLkoJWzxn7wfoo9X1JgvBMY6wHQnTKvnzZdkom2FMhGxkLaEUGDSfsNznTTZNBBg9", "n": "AMJubTfOtAarnJytLE8fhNsEI8wnpjRvBXGK_Kp0675J10ORzxyMLqzIZF3tcrUkKBrtdc79u4X0GocDUgukpfkY-2UPUS_GxehUYbYrJYWOLkoJWzxn7wfoo9X1JgvBMY6wHQnTKvnzZdkom2FMhGxkLaEUGDSfsNznTTZNBBg9",
"e": "AQAB" "e": "AQAB"
} }
}, },
@ -47,7 +47,7 @@ var forgeResult = {
var jws = RSA.signJws( var jws = RSA.signJws(
keypair keypair
, new Buffer('24bcc5d4d04d095de47db279b05685c97f787c9b8bd87f88cdaa0137e7228879', 'hex') , Buffer.from('24bcc5d4d04d095de47db279b05685c97f787c9b8bd87f88cdaa0137e7228879', 'hex')
, '8ef5624f5ec9d3ef' , '8ef5624f5ec9d3ef'
); );

View File

@ -45,7 +45,7 @@ if (hasUrsa) {
} }
else { else {
imported = RSA.import({ privateKeyPem: privkeyPemRef }); imported = RSA.import({ privateKeyPem: privkeyPemRef });
refs.privPem2 = RSA.exportPrivatePem({ _forge: imported._forge }); refs.privPem2 = RSA.exportPrivatePem(imported);
} }
if (privkeyPemRef !== refs.privPem2) { if (privkeyPemRef !== refs.privPem2) {
console.log('REF:'); console.log('REF:');
@ -73,7 +73,7 @@ if (![ 'kty', 'n', 'e', 'p', 'q', 'dp', 'dq', 'qi', 'd' ].every(function (k) {
} }
imported = RSA.import({ privateKeyJwk: privkeyJwkRef }); imported = RSA.import({ privateKeyJwk: privkeyJwkRef });
refs.privJwk2 = RSA.exportPrivateJwk({ _forge: imported._forge }); refs.privJwk2 = RSA.exportPrivateJwk(imported);
console.log('JWK -> _ -> JWK ?', privkeyJwkRef.n === refs.privJwk2.n); console.log('JWK -> _ -> JWK ?', privkeyJwkRef.n === refs.privJwk2.n);
if (privkeyJwkRef.n !== refs.privJwk2.n) { if (privkeyJwkRef.n !== refs.privJwk2.n) {
console.log('REF:'); console.log('REF:');