mirror of
https://github.com/hanzoai/jose.git
synced 2026-09-24 17:00:01 +00:00
chore: initial commit
This commit is contained in:
+41
@@ -0,0 +1,41 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory and lockfiles
|
||||
node_modules
|
||||
yarn.lock
|
||||
npm-shrinkwrap.json
|
||||
package-lock.json
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# TODOs
|
||||
TODO*.md
|
||||
.nvmrc
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Filip Skokan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,43 @@
|
||||
# WIP
|
||||
|
||||
```
|
||||
$ nvm use stable
|
||||
$ node removeme/some.js
|
||||
```
|
||||
|
||||
# ultimate-jose
|
||||
|
||||
[![build][travis-image]][travis-url] [![codecov][codecov-image]][codecov-url]
|
||||
|
||||
TODO:
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
TODO:
|
||||
|
||||
## Implemented specs & features
|
||||
|
||||
TODO:
|
||||
|
||||
Missing a feature? - If it wasn't already discussed before, [ask for it][suggest-feature].
|
||||
Found a bug? - [report it][bug].
|
||||
|
||||
<h2>Support</h2>
|
||||
|
||||
[<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160" align="right">][support-patreon]
|
||||
If you or your business use ultimate-jose, please consider becoming a [Patron][support-patreon] so I can continue maintaining it and adding new features carefree. You may also donate one-time via [PayPal][support-paypal].
|
||||
[<img src="https://cdn.jsdelivr.net/gh/gregoiresgt/payment-icons@183140a5ff8f39b5a19d59ebeb2c77f03c3a24d3/Assets/Payment/PayPal/Paypal@2x.png" width="100" align="right">][support-paypal]
|
||||
|
||||
## Usage
|
||||
|
||||
TODO:
|
||||
|
||||
|
||||
[travis-image]: https://api.travis-ci.com/panva/ultimate-jose.svg?branch=master
|
||||
[travis-url]: https://travis-ci.com/panva/ultimate-jose
|
||||
[codecov-image]: https://img.shields.io/codecov/c/github/panva/ultimate-jose/master.svg
|
||||
[codecov-url]: https://codecov.io/gh/panva/ultimate-jose
|
||||
[suggest-feature]: https://github.com/panva/ultimate-jose/issues/new?template=feature-request.md
|
||||
[bug]: https://github.com/panva/ultimate-jose/issues/new?template=bug-report.md
|
||||
[support-patreon]: https://www.patreon.com/panva
|
||||
[support-paypal]: https://www.paypal.me/panva
|
||||
@@ -0,0 +1,19 @@
|
||||
const CODES = {
|
||||
JWSMissingAlg: 'ERR_JWS_ALG_MISSING',
|
||||
JWSInvalidHeader: 'ERR_JWS_INVALID_HEADER',
|
||||
JWSNoRecipients: 'ERR_JWS_NO_RECIPIENTS'
|
||||
}
|
||||
|
||||
class JoseError extends Error {
|
||||
constructor (message) {
|
||||
super(message)
|
||||
this.name = this.constructor.name
|
||||
this.code = CODES[this.constructor.name]
|
||||
Error.captureStackTrace(this, this.constructor)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.JWSMissingAlg = class JWSMissingAlg extends JoseError {}
|
||||
module.exports.JWSInvalidHeader = class JWSInvalidHeader extends JoseError {}
|
||||
module.exports.JWSNoRecipients = class JWSNoRecipients extends JoseError {}
|
||||
module.exports.JWSVerificationFailed = class JWSVerificationFailed extends JoseError {}
|
||||
@@ -0,0 +1,36 @@
|
||||
const fromBase64 = base64 => base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
|
||||
|
||||
const toBase64 = base64url => base64url.replace(/-/g, '+').replace(/_/g, '/')
|
||||
|
||||
const encode = (input, encoding = 'utf8') => {
|
||||
if (Buffer.isBuffer(input)) {
|
||||
return fromBase64(input.toString('base64'))
|
||||
}
|
||||
|
||||
return fromBase64(Buffer.from(input, encoding).toString('base64'))
|
||||
}
|
||||
|
||||
const decode = (input, encoding = 'utf8') => {
|
||||
const buf = Buffer.from(toBase64(input), 'base64')
|
||||
return encoding ? buf.toString(encoding) : buf
|
||||
}
|
||||
|
||||
const decodeToBuffer = input => Buffer.from(toBase64(input), 'base64')
|
||||
|
||||
const b64uJSON = {
|
||||
encode: input => encode(JSON.stringify(input)),
|
||||
decode: input => JSON.parse(decode(input))
|
||||
}
|
||||
|
||||
b64uJSON.decode.try = (input) => {
|
||||
try {
|
||||
return b64uJSON.decode(input)
|
||||
} catch (err) {
|
||||
return decode(input)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.decodeToBuffer = decodeToBuffer
|
||||
module.exports.decode = decode
|
||||
module.exports.encode = encode
|
||||
module.exports.JSON = b64uJSON
|
||||
@@ -0,0 +1,165 @@
|
||||
// TODO: include license from https://github.com/Brightspace/node-ecdsa-sig-formatter
|
||||
|
||||
const MAX_OCTET = 0x80
|
||||
const CLASS_UNIVERSAL = 0
|
||||
const PRIMITIVE_BIT = 0x20
|
||||
const TAG_SEQ = 0x10
|
||||
const TAG_INT = 0x02
|
||||
const ENCODED_TAG_SEQ = (TAG_SEQ | PRIMITIVE_BIT) | (CLASS_UNIVERSAL << 6)
|
||||
const ENCODED_TAG_INT = TAG_INT | (CLASS_UNIVERSAL << 6)
|
||||
|
||||
const getParamSize = keySize => ((keySize / 8) | 0) + (keySize % 8 === 0 ? 0 : 1)
|
||||
|
||||
const paramBytesForAlg = {
|
||||
ES256: getParamSize(256),
|
||||
ES384: getParamSize(384)
|
||||
// ES512: getParamSize(521)
|
||||
}
|
||||
|
||||
const countPadding = (buf, start, stop) => {
|
||||
let padding = 0
|
||||
while (start + padding < stop && buf[start + padding] === 0) {
|
||||
++padding
|
||||
}
|
||||
|
||||
const needsSign = buf[start + padding] >= MAX_OCTET
|
||||
if (needsSign) {
|
||||
--padding
|
||||
}
|
||||
|
||||
return padding
|
||||
}
|
||||
|
||||
module.exports.derToJose = (signature, alg) => {
|
||||
const paramBytes = paramBytesForAlg[alg]
|
||||
|
||||
// the DER encoded param should at most be the param size, plus a padding
|
||||
// zero, since due to being a signed integer
|
||||
const maxEncodedParamLength = paramBytes + 1
|
||||
|
||||
const inputLength = signature.length
|
||||
|
||||
let offset = 0
|
||||
if (signature[offset++] !== ENCODED_TAG_SEQ) {
|
||||
throw new Error('Could not find expected "seq"')
|
||||
}
|
||||
|
||||
let seqLength = signature[offset++]
|
||||
if (seqLength === (MAX_OCTET | 1)) {
|
||||
seqLength = signature[offset++]
|
||||
}
|
||||
|
||||
if (inputLength - offset < seqLength) {
|
||||
throw new Error('"seq" specified length of "' + seqLength + '", only "' + (inputLength - offset) + '" remaining')
|
||||
}
|
||||
|
||||
if (signature[offset++] !== ENCODED_TAG_INT) {
|
||||
throw new Error('Could not find expected "int" for "r"')
|
||||
}
|
||||
|
||||
const rLength = signature[offset++]
|
||||
|
||||
if (inputLength - offset - 2 < rLength) {
|
||||
throw new Error('"r" specified length of "' + rLength + '", only "' + (inputLength - offset - 2) + '" available')
|
||||
}
|
||||
|
||||
if (maxEncodedParamLength < rLength) {
|
||||
throw new Error('"r" specified length of "' + rLength + '", max of "' + maxEncodedParamLength + '" is acceptable')
|
||||
}
|
||||
|
||||
const rOffset = offset
|
||||
offset += rLength
|
||||
|
||||
if (signature[offset++] !== ENCODED_TAG_INT) {
|
||||
throw new Error('Could not find expected "int" for "s"')
|
||||
}
|
||||
|
||||
const sLength = signature[offset++]
|
||||
|
||||
if (inputLength - offset !== sLength) {
|
||||
throw new Error('"s" specified length of "' + sLength + '", expected "' + (inputLength - offset) + '"')
|
||||
}
|
||||
|
||||
if (maxEncodedParamLength < sLength) {
|
||||
throw new Error('"s" specified length of "' + sLength + '", max of "' + maxEncodedParamLength + '" is acceptable')
|
||||
}
|
||||
|
||||
const sOffset = offset
|
||||
offset += sLength
|
||||
|
||||
if (offset !== inputLength) {
|
||||
throw new Error('Expected to consume entire buffer, but "' + (inputLength - offset) + '" bytes remain')
|
||||
}
|
||||
|
||||
const rPadding = paramBytes - rLength
|
||||
|
||||
const sPadding = paramBytes - sLength
|
||||
|
||||
let dst = Buffer.allocUnsafe(rPadding + rLength + sPadding + sLength)
|
||||
|
||||
for (offset = 0; offset < rPadding; ++offset) {
|
||||
dst[offset] = 0
|
||||
}
|
||||
signature.copy(dst, offset, rOffset + Math.max(-rPadding, 0), rOffset + rLength)
|
||||
|
||||
offset = paramBytes
|
||||
|
||||
for (const o = offset; offset < o + sPadding; ++offset) {
|
||||
dst[offset] = 0
|
||||
}
|
||||
signature.copy(dst, offset, sOffset + Math.max(-sPadding, 0), sOffset + sLength)
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
module.exports.joseToDer = (signature, alg) => {
|
||||
const paramBytes = paramBytesForAlg[alg]
|
||||
|
||||
const signatureBytes = signature.length
|
||||
if (signatureBytes !== paramBytes * 2) {
|
||||
throw new TypeError('"' + alg + '" signatures must be "' + paramBytes * 2 + '" bytes, saw "' + signatureBytes + '"')
|
||||
}
|
||||
|
||||
const rPadding = countPadding(signature, 0, paramBytes)
|
||||
const sPadding = countPadding(signature, paramBytes, signature.length)
|
||||
const rLength = paramBytes - rPadding
|
||||
const sLength = paramBytes - sPadding
|
||||
|
||||
const rsBytes = 1 + 1 + rLength + 1 + 1 + sLength
|
||||
|
||||
const shortLength = rsBytes < MAX_OCTET
|
||||
|
||||
let dst = Buffer.allocUnsafe((shortLength ? 2 : 3) + rsBytes)
|
||||
|
||||
let offset = 0
|
||||
dst[offset++] = ENCODED_TAG_SEQ
|
||||
if (shortLength) {
|
||||
// Bit 8 has value "0"
|
||||
// bits 7-1 give the length.
|
||||
dst[offset++] = rsBytes
|
||||
} else {
|
||||
// Bit 8 of first octet has value "1"
|
||||
// bits 7-1 give the number of additional length octets.
|
||||
dst[offset++] = MAX_OCTET | 1 // eslint-disable-line no-tabs
|
||||
// length, base 256
|
||||
dst[offset++] = rsBytes & 0xff
|
||||
}
|
||||
dst[offset++] = ENCODED_TAG_INT
|
||||
dst[offset++] = rLength
|
||||
if (rPadding < 0) {
|
||||
dst[offset++] = 0
|
||||
offset += signature.copy(dst, offset, 0, paramBytes)
|
||||
} else {
|
||||
offset += signature.copy(dst, offset, rPadding, paramBytes)
|
||||
}
|
||||
dst[offset++] = ENCODED_TAG_INT
|
||||
dst[offset++] = sLength
|
||||
if (sPadding < 0) {
|
||||
dst[offset++] = 0
|
||||
signature.copy(dst, offset, paramBytes)
|
||||
} else {
|
||||
signature.copy(dst, offset, paramBytes + sPadding)
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
const { generateKeyPairSync, createSecretKey } = require('crypto')
|
||||
|
||||
// TODO: reach out to @tniessen to request constructors being exposed for this exact purpose
|
||||
// (verifying inputs are already KeyObjects)
|
||||
// TODO: what's the least blocking way to get to the constructors
|
||||
const { publicKey, privateKey } = generateKeyPairSync('ec', { namedCurve: 'P-256' })
|
||||
|
||||
const PrivateKeyObject = privateKey.constructor
|
||||
const PublicKeyObject = publicKey.constructor
|
||||
const SecretKeyObject = createSecretKey(Buffer.allocUnsafe(1)).constructor
|
||||
|
||||
module.exports = { PrivateKeyObject, PublicKeyObject, SecretKeyObject }
|
||||
@@ -0,0 +1,26 @@
|
||||
const utils = {
|
||||
rsa: require('rasha'),
|
||||
ec: require('eckles')
|
||||
}
|
||||
const { PrivateKeyObject, PublicKeyObject } = require('./key_objects')
|
||||
|
||||
const SUPPORTED = new Set(Object.keys(utils))
|
||||
|
||||
module.exports.pemToJwk = (keyObject) => {
|
||||
if (!(keyObject instanceof PrivateKeyObject) && !(keyObject instanceof PublicKeyObject)) {
|
||||
throw new TypeError('invalid keyObject type')
|
||||
}
|
||||
|
||||
const type = keyObject.type === 'private' ? 'pkcs8' : 'spki'
|
||||
const format = 'pem'
|
||||
|
||||
return utils[keyObject.asymmetricKeyType].importSync({ pem: keyObject.export({ type, format }) })
|
||||
}
|
||||
|
||||
module.exports.jwkToPem = (jwk) => {
|
||||
if (!SUPPORTED.has(jwk.kty)) {
|
||||
throw new TypeError('unsupported kty')
|
||||
}
|
||||
|
||||
return utils[jwk.kty.toLowerCase()].exportSync({ jwk })
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
JWT: require('./jwt'),
|
||||
JWE: require('./jwe'),
|
||||
JWS: require('./jws'),
|
||||
JWK: require('./jwk'),
|
||||
JWKS: require('./jwks')
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports.TODO = 'TODO'
|
||||
@@ -0,0 +1,89 @@
|
||||
const { pemToJwk } = require('../help/key_utils')
|
||||
const thumbprint = require('./thumbprint')
|
||||
|
||||
const RSA_PUBLIC = ['e', 'n']
|
||||
const RSA_PRIVATE = [...RSA_PUBLIC, 'd', 'p', 'q', 'dp', 'dq', 'qi']
|
||||
const EC_PUBLIC = ['crv', 'x', 'y']
|
||||
const EC_PRIVATE = [...EC_PUBLIC, 'd']
|
||||
|
||||
const props = {
|
||||
EC: {
|
||||
public: EC_PUBLIC,
|
||||
private: EC_PRIVATE
|
||||
},
|
||||
RSA: {
|
||||
public: RSA_PUBLIC,
|
||||
private: RSA_PRIVATE
|
||||
}
|
||||
}
|
||||
|
||||
const USES = new Set(['sig', 'alg'])
|
||||
|
||||
function defineLazyComponents (obj) {
|
||||
Object.defineProperties(obj, props[obj.keyObject.asymmetricKeyType.toUpperCase()][obj.keyObject.type].reduce((acc, component) => {
|
||||
acc[component] = {
|
||||
get () {
|
||||
const jwk = pemToJwk(this.keyObject)
|
||||
Object.defineProperties(
|
||||
this,
|
||||
Object.entries(jwk)
|
||||
.filter(([key]) => props[this.kty][this.keyObject.type].includes(key))
|
||||
.reduce((acc, [key, value]) => {
|
||||
acc[key] = { value, enumerable: true, configurable: false }
|
||||
return acc
|
||||
}, {})
|
||||
)
|
||||
|
||||
return this[component]
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
}
|
||||
return acc
|
||||
}, {}))
|
||||
}
|
||||
|
||||
class Key {
|
||||
constructor (keyObject, { alg, use, kid } = {}) {
|
||||
if (use !== undefined) {
|
||||
if (typeof use !== 'string' || !USES.has(use)) {
|
||||
throw new TypeError('`use` must be either "sig" or "enc" string when provided')
|
||||
}
|
||||
}
|
||||
|
||||
if (use !== undefined) {
|
||||
if (typeof use !== 'string') {
|
||||
throw new TypeError('`alg` must be a string when provided')
|
||||
}
|
||||
}
|
||||
|
||||
if (kid !== undefined) {
|
||||
if (typeof kid !== 'string') {
|
||||
throw new TypeError('`kid` must be a string when provided')
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(this, {
|
||||
keyObject: { value: keyObject },
|
||||
kty: { value: keyObject.asymmetricKeyType.toUpperCase(), enumerable: true },
|
||||
private: { value: keyObject.type === 'private', enumerable: keyObject.type === 'private' },
|
||||
public: { value: keyObject.type === 'public', enumerable: keyObject.type === 'public' },
|
||||
alg: { value: alg, enumerable: alg !== undefined },
|
||||
use: { value: use, enumerable: use !== undefined },
|
||||
kid: {
|
||||
enumerable: true,
|
||||
...(kid ? { value: kid } : {
|
||||
get () {
|
||||
Object.defineProperty(this, 'kid', { value: thumbprint.kid(this), configurable: false })
|
||||
return this.kid
|
||||
},
|
||||
configurable: true
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
defineLazyComponents(this)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Key
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
const { createSign, createVerify } = require('crypto')
|
||||
const { derToJose, joseToDer } = require('../help/ecdsa_signatures')
|
||||
|
||||
const Key = require('./base')
|
||||
|
||||
const SIG_ALGS = new Set([
|
||||
'ES256',
|
||||
'ES384'
|
||||
// 'ES512'
|
||||
])
|
||||
|
||||
const WRAP_ALGS = new Set([
|
||||
'ECDH-ES',
|
||||
'ECDH-ES+A128KW',
|
||||
'ECDH-ES+A192KW',
|
||||
'ECDH-ES+A256KW'
|
||||
])
|
||||
|
||||
class ECKey extends Key {
|
||||
constructor (...args) {
|
||||
super(...args)
|
||||
Object.defineProperties(this, {
|
||||
length: {
|
||||
get () {
|
||||
Object.defineProperty(this, 'length', {
|
||||
value: parseInt(this.crv.substring(2), 10),
|
||||
configurable: false
|
||||
})
|
||||
|
||||
return this.length
|
||||
},
|
||||
configurable: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
algorithms (operation) {
|
||||
// TODO: operation empty, return all supported or throw
|
||||
|
||||
switch (operation) {
|
||||
case 'sign':
|
||||
if (this.public || this.use === 'enc') {
|
||||
return new Set([])
|
||||
}
|
||||
|
||||
if (this.alg) {
|
||||
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : [])
|
||||
}
|
||||
|
||||
return new Set([`ES${this.length}`])
|
||||
case 'verify':
|
||||
if (this.use === 'enc') {
|
||||
return new Set([])
|
||||
}
|
||||
|
||||
if (this.alg) {
|
||||
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : [])
|
||||
}
|
||||
|
||||
return new Set([`ES${this.length}`])
|
||||
case 'unwrapKey':
|
||||
if (this.public || this.use === 'sig') {
|
||||
return new Set([])
|
||||
}
|
||||
|
||||
if (this.alg) {
|
||||
return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : [])
|
||||
}
|
||||
|
||||
return new Set(WRAP_ALGS)
|
||||
case 'wrapKey':
|
||||
if (this.use === 'sig') {
|
||||
return new Set([])
|
||||
}
|
||||
|
||||
if (this.alg) {
|
||||
return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : [])
|
||||
}
|
||||
|
||||
return new Set(WRAP_ALGS)
|
||||
default:
|
||||
return new Set([])
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move to jwa
|
||||
signAlg (alg) {
|
||||
switch (alg) {
|
||||
case 'ES256':
|
||||
return 'RSA-SHA256'
|
||||
case 'ES384':
|
||||
return 'RSA-SHA384'
|
||||
// case 'ES512':
|
||||
// return 'RSA-SHA512'
|
||||
default:
|
||||
throw new Error('invalid alg')
|
||||
}
|
||||
}
|
||||
|
||||
sign (alg, payload) {
|
||||
if (!this.algorithms('sign').has(alg)) {
|
||||
throw new Error('the key does not support this signing algorithm')
|
||||
}
|
||||
const sign = createSign(this.signAlg(alg))
|
||||
sign.update(payload)
|
||||
|
||||
return derToJose(sign.sign(this.keyObject), alg)
|
||||
}
|
||||
|
||||
verify (alg, payload, signature) {
|
||||
if (!this.algorithms('verify').has(alg)) {
|
||||
throw new Error('the key does not support this verify algorithm')
|
||||
}
|
||||
const verify = createVerify(this.signAlg(alg))
|
||||
verify.update(payload)
|
||||
|
||||
try {
|
||||
return verify.verify(this.keyObject, joseToDer(signature, alg))
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ECKey
|
||||
@@ -0,0 +1,59 @@
|
||||
const { createPublicKey, createPrivateKey } = require('crypto')
|
||||
|
||||
const Key = require('./base')
|
||||
const RSAKey = require('./rsa')
|
||||
const ECKey = require('./ec')
|
||||
|
||||
const { PrivateKeyObject, PublicKeyObject } = require('../help/key_objects')
|
||||
const { jwkToPem } = require('../help/key_utils')
|
||||
|
||||
function importKey (key, opts) {
|
||||
let privateKey, publicKey
|
||||
if (key instanceof Key) {
|
||||
if (key.private) {
|
||||
privateKey = key.keyObject
|
||||
} else if (key.public) {
|
||||
publicKey = key.keyObject
|
||||
} else { // secret
|
||||
// TODO: symmetrical keys
|
||||
// secret = key
|
||||
}
|
||||
} else if (key instanceof PrivateKeyObject) {
|
||||
privateKey = key
|
||||
} else if (key instanceof PublicKeyObject) {
|
||||
publicKey = key
|
||||
} else if (key && key.kty) { // assume JWK formatted <Object>
|
||||
let parsedJWK
|
||||
try {
|
||||
parsedJWK = jwkToPem(key)
|
||||
} catch (err) {}
|
||||
if (parsedJWK && key.d) {
|
||||
privateKey = createPrivateKey(parsedJWK)
|
||||
} else if (parsedJWK) {
|
||||
publicKey = createPublicKey(parsedJWK)
|
||||
}
|
||||
} else { // <Object> | <string> | <Buffer> passed to crypto.createPrivateKey or crypto.createPublicKey
|
||||
try {
|
||||
privateKey = createPrivateKey(key)
|
||||
} catch (err) {}
|
||||
try {
|
||||
publicKey = createPublicKey(key)
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
if (!privateKey && !publicKey) {
|
||||
throw new Error('import failed')
|
||||
}
|
||||
|
||||
const keyObject = privateKey || publicKey
|
||||
|
||||
switch (keyObject.asymmetricKeyType) {
|
||||
case 'rsa':
|
||||
return new RSAKey(keyObject, opts)
|
||||
case 'ec':
|
||||
return new ECKey(keyObject, opts)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = importKey
|
||||
@@ -0,0 +1 @@
|
||||
module.exports.importKey = require('./import')
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
const { createSign, createVerify, constants } = require('crypto')
|
||||
|
||||
const Key = require('./base')
|
||||
|
||||
const SIG_ALGS = new Set([
|
||||
'RS256',
|
||||
'RS384',
|
||||
'RS512',
|
||||
'PS256',
|
||||
'PS384',
|
||||
'PS512'
|
||||
])
|
||||
|
||||
const WRAP_ALGS = new Set([
|
||||
'RSA-OAEP',
|
||||
'RSA1_5'
|
||||
])
|
||||
|
||||
class RSAKey extends Key {
|
||||
constructor (...args) {
|
||||
super(...args)
|
||||
Object.defineProperties(this, {
|
||||
length: {
|
||||
get () {
|
||||
Object.defineProperty(this, 'length', {
|
||||
value: Buffer.byteLength(this.n, 'base64') * 8,
|
||||
configurable: false
|
||||
})
|
||||
|
||||
return this.length
|
||||
},
|
||||
configurable: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
algorithms (operation) {
|
||||
// TODO: operation empty, return all supported or throw
|
||||
|
||||
switch (operation) {
|
||||
case 'sign':
|
||||
if (this.public || this.use === 'enc') {
|
||||
return new Set([])
|
||||
}
|
||||
|
||||
if (this.alg) {
|
||||
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : [])
|
||||
}
|
||||
|
||||
return new Set(SIG_ALGS)
|
||||
case 'verify':
|
||||
if (this.use === 'enc') {
|
||||
return new Set([])
|
||||
}
|
||||
|
||||
if (this.alg) {
|
||||
return new Set(SIG_ALGS.has(this.alg) ? [this.alg] : [])
|
||||
}
|
||||
|
||||
return new Set(SIG_ALGS)
|
||||
case 'unwrapKey':
|
||||
if (this.public || this.use === 'sig') {
|
||||
return new Set([])
|
||||
}
|
||||
|
||||
if (this.alg) {
|
||||
return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : [])
|
||||
}
|
||||
|
||||
return new Set(WRAP_ALGS)
|
||||
case 'wrapKey':
|
||||
if (this.use === 'sig') {
|
||||
return new Set([])
|
||||
}
|
||||
|
||||
if (this.alg) {
|
||||
return new Set(WRAP_ALGS.has(this.alg) ? [this.alg] : [])
|
||||
}
|
||||
|
||||
return new Set(WRAP_ALGS)
|
||||
default:
|
||||
return new Set([])
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move to jwa
|
||||
signAlg (alg) {
|
||||
switch (alg) {
|
||||
case 'RS256':
|
||||
case 'PS256':
|
||||
return 'RSA-SHA256'
|
||||
case 'RS384':
|
||||
case 'PS384':
|
||||
return 'RSA-SHA384'
|
||||
case 'RS512':
|
||||
case 'PS512':
|
||||
return 'RSA-SHA512'
|
||||
default:
|
||||
throw new Error('invalid alg')
|
||||
}
|
||||
}
|
||||
|
||||
signPadding (alg) {
|
||||
if (alg.startsWith('PS')) {
|
||||
return {
|
||||
padding: constants.RSA_PKCS1_PSS_PADDING,
|
||||
saltLength: constants.RSA_PSS_SALTLEN_DIGEST
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
sign (alg, payload) {
|
||||
if (!this.algorithms('sign').has(alg)) {
|
||||
throw new Error('the key does not support this signing algorithm')
|
||||
}
|
||||
const sign = createSign(this.signAlg(alg))
|
||||
sign.update(payload)
|
||||
|
||||
return sign.sign({ key: this.keyObject, ...this.signPadding(alg) })
|
||||
}
|
||||
|
||||
verify (alg, payload, signature) {
|
||||
if (!this.algorithms('verify').has(alg)) {
|
||||
throw new Error('the key does not support this verify algorithm')
|
||||
}
|
||||
const verify = createVerify(this.signAlg(alg))
|
||||
verify.update(payload)
|
||||
|
||||
return verify.verify({ key: this.keyObject, ...this.signPadding(alg) }, signature)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RSAKey
|
||||
@@ -0,0 +1,21 @@
|
||||
const { createHash } = require('crypto')
|
||||
|
||||
const base64url = require('../help/base64url')
|
||||
|
||||
module.exports.kid = (key) => {
|
||||
let json
|
||||
|
||||
switch (key.kty) {
|
||||
case 'RSA':
|
||||
json = JSON.stringify({ e: key.e, kty: 'RSA', n: key.n })
|
||||
break
|
||||
case 'EC':
|
||||
json = JSON.stringify({ crv: key.crv, kty: 'EC', x: key.x, y: key.y })
|
||||
break
|
||||
case 'oct':
|
||||
json = JSON.stringify({ k: key.k, kty: 'oct' })
|
||||
break
|
||||
}
|
||||
|
||||
return base64url.encode(createHash('sha256').update(json).digest())
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports.TODO = 'TODO'
|
||||
@@ -0,0 +1,12 @@
|
||||
const Sign = require('./sign')
|
||||
const verify = require('./verify')
|
||||
|
||||
module.exports.Sign = Sign
|
||||
module.exports.sign = (payload, key, protectedHeader, unprotectedHeader, serialization) => {
|
||||
const jws = new Sign(payload, protectedHeader, unprotectedHeader)
|
||||
jws.recipient(key)
|
||||
const result = jws.sign(serialization)
|
||||
return result
|
||||
}
|
||||
|
||||
module.exports.verify = verify
|
||||
@@ -0,0 +1,61 @@
|
||||
const base64url = require('../help/base64url')
|
||||
|
||||
const { JWSMissingAlg, JWSInvalidHeader, JWSNoRecipients } = require('../errors')
|
||||
|
||||
class Recipients extends Set {
|
||||
process (payload, sharedProtectedHeader, sharedUnprotectedHeader) {
|
||||
if (!this.size) {
|
||||
throw new JWSNoRecipients('JWS needs to have recipients added first')
|
||||
}
|
||||
|
||||
return Array.from(this)
|
||||
.map((recipient) => {
|
||||
return recipient.process(payload, sharedProtectedHeader, sharedUnprotectedHeader)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Recipient {
|
||||
constructor (key, protectedHeader, unprotectedHeader) {
|
||||
this.key = key
|
||||
|
||||
if (typeof protectedHeader !== 'undefined') {
|
||||
this.protectedHeader = protectedHeader
|
||||
}
|
||||
|
||||
if (typeof unprotectedHeader !== 'undefined') {
|
||||
this.unprotectedHeader = unprotectedHeader
|
||||
}
|
||||
}
|
||||
|
||||
process (payload, sharedProtectedHeader, sharedUnprotectedHeader) {
|
||||
const joseHeader = {
|
||||
protected: {
|
||||
...sharedProtectedHeader,
|
||||
...this.protectedHeader
|
||||
},
|
||||
unprotected: {
|
||||
...sharedUnprotectedHeader,
|
||||
...this.unprotectedHeader
|
||||
}
|
||||
}
|
||||
const unprotectedKeys = new Set(Object.keys(joseHeader.unprotected))
|
||||
|
||||
if (new Set(Object.keys(joseHeader.protected).filter(x => unprotectedKeys.has(x))).size) {
|
||||
throw new JWSInvalidHeader('JWS Protected and JWS Unprotected Header Parameter names must be disjoint')
|
||||
}
|
||||
|
||||
const { alg } = { ...joseHeader.protected, ...joseHeader.unprotected }
|
||||
if (!alg) {
|
||||
throw new JWSMissingAlg('every JOSE header must contain an "alg" parameter')
|
||||
}
|
||||
|
||||
if (Object.keys(joseHeader.unprotected).length) {
|
||||
this.header = joseHeader.unprotected
|
||||
}
|
||||
this.protected = base64url.JSON.encode(joseHeader.protected)
|
||||
this.signature = base64url.encode(this.key.sign(alg, `${this.protected}.${payload}`))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Recipients, Recipient }
|
||||
@@ -0,0 +1,61 @@
|
||||
function compactSerializer (payload, [{ signature, protected: prot }]) {
|
||||
return `${prot}.${payload}.${signature}`
|
||||
}
|
||||
compactSerializer.validate = (jws, recipients) => {
|
||||
if (recipients.length !== 1 || jws.sharedUnprotectedHeader || recipients.some(r => r.unprotectedHeader)) {
|
||||
throw new TypeError("JWS Compact Serialization doesn't support multiple recipients or JWS unprotected headers")
|
||||
}
|
||||
}
|
||||
|
||||
function generalSerializer (payload, recipients) {
|
||||
return {
|
||||
payload,
|
||||
signatures: recipients.map(({ header, signature, protected: prot }) => {
|
||||
return {
|
||||
signature,
|
||||
protected: prot,
|
||||
...header ? { header } : undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
generalSerializer.validate = () => {}
|
||||
|
||||
function flattenedSerializer (payload, [{ header, signature, protected: prot }]) {
|
||||
return {
|
||||
payload,
|
||||
signature,
|
||||
protected: prot,
|
||||
...header ? { header } : undefined
|
||||
}
|
||||
}
|
||||
flattenedSerializer.validate = (jws, { length }) => {
|
||||
if (length !== 1) {
|
||||
throw new TypeError("Flattened JWS JSON Serialization doesn't support multiple recipients")
|
||||
}
|
||||
}
|
||||
|
||||
function detect (input) {
|
||||
if (typeof input === 'string') {
|
||||
return 'compact'
|
||||
}
|
||||
|
||||
if ('signature' in input && 'payload' in input && 'protected' in input) {
|
||||
return 'flattened'
|
||||
}
|
||||
|
||||
if ('signatures' in input && 'payload' in input && Array.isArray(input.signatures)) {
|
||||
if (input.signatures.every(s => 'signature' in s && 'protected' in s)) {
|
||||
return 'general'
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('invalid serialization')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
compact: compactSerializer,
|
||||
flattened: flattenedSerializer,
|
||||
general: generalSerializer,
|
||||
detect
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
const base64url = require('../help/base64url')
|
||||
|
||||
const serializers = require('./serializers')
|
||||
const { Recipients, Recipient } = require('./recipients')
|
||||
const Key = require('../jwk/base')
|
||||
|
||||
const map = new WeakMap()
|
||||
function get (instance, property) {
|
||||
return map.get(instance)[property]
|
||||
}
|
||||
|
||||
class Sign {
|
||||
constructor (payload, sharedProtectedHeader, sharedUnprotectedHeader) {
|
||||
if (!Buffer.isBuffer(payload) && !(typeof payload === 'string')) {
|
||||
payload = JSON.stringify(payload)
|
||||
}
|
||||
|
||||
Object.freeze(sharedProtectedHeader)
|
||||
Object.freeze(sharedUnprotectedHeader)
|
||||
|
||||
map.set(this, {
|
||||
payload: base64url.encode(payload),
|
||||
recipients: new Recipients()
|
||||
})
|
||||
|
||||
if (typeof sharedProtectedHeader !== 'undefined') {
|
||||
map.get(this).sharedProtectedHeader = sharedProtectedHeader
|
||||
}
|
||||
|
||||
if (typeof sharedUnprotectedHeader !== 'undefined') {
|
||||
map.get(this).sharedUnprotectedHeader = sharedUnprotectedHeader
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @private
|
||||
*/
|
||||
get payload () {
|
||||
return get(this, 'payload')
|
||||
}
|
||||
|
||||
/*
|
||||
* @private
|
||||
*/
|
||||
get sharedProtectedHeader () {
|
||||
return get(this, 'sharedProtectedHeader')
|
||||
}
|
||||
|
||||
/*
|
||||
* @private
|
||||
*/
|
||||
get sharedUnprotectedHeader () {
|
||||
return get(this, 'sharedUnprotectedHeader')
|
||||
}
|
||||
|
||||
/*
|
||||
* @public
|
||||
*/
|
||||
recipient (key, protectedHeader, unprotectedHeader) {
|
||||
if (!(key instanceof Key)) {
|
||||
throw new TypeError('key must be an instance of a key instantiated by JWK.importKey')
|
||||
}
|
||||
const recipient = new Recipient(key, protectedHeader, unprotectedHeader)
|
||||
get(this, 'recipients').add(recipient)
|
||||
return this
|
||||
}
|
||||
|
||||
/*
|
||||
* @public
|
||||
*/
|
||||
sign (serialization = 'compact') {
|
||||
if (!serializers[serialization]) {
|
||||
throw new TypeError('invalid serialization')
|
||||
}
|
||||
serializers[serialization].validate(this, Array.from(get(this, 'recipients')))
|
||||
|
||||
get(this, 'recipients').process(this.payload, this.sharedProtectedHeader, this.sharedUnprotectedHeader)
|
||||
|
||||
return serializers[serialization](this.payload, Array.from(get(this, 'recipients')))
|
||||
}
|
||||
|
||||
/*
|
||||
* @public
|
||||
*/
|
||||
static sign (payload, key, protectedHeader, unprotectedHeader, serialization) {
|
||||
const jws = new this(payload, protectedHeader, unprotectedHeader)
|
||||
jws.recipient(key)
|
||||
const result = jws.sign(serialization)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Sign
|
||||
@@ -0,0 +1,52 @@
|
||||
const base64url = require('../help/base64url')
|
||||
const serializers = require('./serializers')
|
||||
const { JWSVerificationFailed } = require('../errors')
|
||||
|
||||
const SINGLE_RECIPIENT = new Set(['compact', 'flattened'])
|
||||
|
||||
/*
|
||||
* @public
|
||||
*/
|
||||
// TODO: option to return everything not just the payload
|
||||
// TODO: add kid magic
|
||||
module.exports = (jws, key) => {
|
||||
const serialization = serializers.detect(jws)
|
||||
let prot
|
||||
let payload
|
||||
let signature
|
||||
|
||||
if (SINGLE_RECIPIENT.has(serialization)) {
|
||||
if (serialization === 'compact') { // compact serialization format
|
||||
([prot, payload, signature] = jws.split('.'))
|
||||
} else { // flattened serialization format
|
||||
({ protected: prot, payload, signature } = jws)
|
||||
}
|
||||
|
||||
// TODO: alg may also be unprotected but must it must be provided disjoint
|
||||
const { alg } = base64url.JSON.decode(prot)
|
||||
|
||||
if (!key.verify(alg, [prot, payload].join('.'), base64url.decodeToBuffer(signature))) {
|
||||
throw new JWSVerificationFailed('verification failed')
|
||||
}
|
||||
|
||||
return base64url.JSON.decode.try(payload)
|
||||
}
|
||||
|
||||
// general serialization format
|
||||
({ payload } = jws)
|
||||
for ({ protected: prot, signature } of jws.signatures) {
|
||||
// TODO: alg may also be unprotected but must it must be provided disjoint
|
||||
const { alg } = base64url.JSON.decode(prot)
|
||||
|
||||
if (
|
||||
!key.algorithms('verify').has(alg) ||
|
||||
!key.verify(alg, [prot, payload].join('.'), base64url.decodeToBuffer(signature))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
return base64url.JSON.decode.try(payload)
|
||||
}
|
||||
|
||||
throw new JWSVerificationFailed('verification failed')
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports.TODO = 'TODO'
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "ultimate-jose",
|
||||
"version": "0.0.1",
|
||||
"description": "JSON Web Everything (JWA, JWS, JWE, JWK, JWKS, JWT) for Node.js with minimal dependencies",
|
||||
"keywords": [
|
||||
"jose",
|
||||
"jsonwebtoken",
|
||||
"jwa",
|
||||
"jwe",
|
||||
"jwk",
|
||||
"jws",
|
||||
"jwt",
|
||||
"rfc7515",
|
||||
"rfc7516",
|
||||
"rfc7517",
|
||||
"rfc7518",
|
||||
"rfc7519",
|
||||
"rfc7797"
|
||||
],
|
||||
"homepage": "https://github.com/panva/ultimate-jose",
|
||||
"repository": "panva/ultimate-jose",
|
||||
"license": "MIT",
|
||||
"author": "Filip Skokan <[email protected]>",
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"lint": "standard",
|
||||
"lint-fix": "standard --fix",
|
||||
"test": "ava"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"eckles": "^1.3.2",
|
||||
"rasha": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^7.3.2",
|
||||
"@commitlint/config-conventional": "^7.3.1",
|
||||
"ava": "^1.1.0",
|
||||
"husky": "^1.3.1",
|
||||
"standard": "^12.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=11.7.0"
|
||||
},
|
||||
"ava": {
|
||||
"babel": false,
|
||||
"compileEnhancements": false
|
||||
},
|
||||
"commitlint": {
|
||||
"extends": [
|
||||
"@commitlint/config-conventional"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg0M8fij/yTb4tf/Zk
|
||||
0qBQFeUrLBPnDFOhBO2g6DjzEzShRANCAAQrveSafGzJbSqtsQ4HF2F0lxjtPbKG
|
||||
vE0k4vzi9OWhWO7P7o7dJCuN0PBK69tPS1ZR4pJRz+U6/7nQ8IMVr8jV
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,4 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEK73kmnxsyW0qrbEOBxdhdJcY7T2y
|
||||
hrxNJOL84vTloVjuz+6O3SQrjdDwSuvbT0tWUeKSUc/lOv+50PCDFa/I1Q==
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
wmBYi5IzgDHVJ+m4U5XGDlDttyVPog==
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,14 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwpXGtfFTghM7LoX0tZf8
|
||||
KSXEaVzwC+GSKhD41ZG1Yh9K4svvAAK8LnIpMcHHbQdtkRNVW1xr0A68K+1O0ASc
|
||||
k6gWnDgiVvYevZpx1/9f+iddbJZGohQ38AnzVag9G7M1NZyFQiRyOFFiXWun7KMJ
|
||||
XALK/lLxJK/5TCTLDpcBLV5pRw5hNuVejWXwWsY4+uNZYEfO8SGt9Gp0HhajshR+
|
||||
n+NQou5wdLNe0sNthyrLTQUyZuEJyURLZZFUSmSnLzzzfCynTcOHgpzmwOjyJ42U
|
||||
yiUPAujl29UCnb/jEPsHRdAwz/oudmhQ7oVUOr6C6VlOHi/gmn0nEoisRQoXXWq3
|
||||
ZGE2+WqLgEicwZuG/WQm9iGAeZ6UIaRRcRlFmRE6gsVdubxySiX+GOrNo45m+oEs
|
||||
pNmkOVpWFgJYDD7IUpw4pKda5n9vS3fR+Hjj+9rjZjqW0OIuR5yr0HGxIT997nL/
|
||||
KMIuhH7HPRAerd9zcEXQiiWaRtq3pg3iSi7te6au0tQhJqqZGNXtulq8rBL4HftX
|
||||
/UqC6i9vA6WYGJ+FyzzRzceiTEtHEHhyZj7wrk1IRFPU2RuCa8TuolH26LjdiS4C
|
||||
WyMh4ho/XstQxH7FAnKE33xWBTQKHN+g99mxvvSy9jfCgdN/UzEplIL0eUFAizYF
|
||||
i6piRZx1I1JAD9LZOg9E4wcCAwEAAQ==
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -0,0 +1,50 @@
|
||||
/* eslint-disable */
|
||||
const { strict: assert } = require('assert')
|
||||
const { readFileSync } = require('fs')
|
||||
|
||||
const { JWS, JWK: { importKey } } = require('..')
|
||||
|
||||
const ecPrivate = importKey(readFileSync('./removeme/ec.key'))
|
||||
const rsaPrivate = importKey(readFileSync('./removeme/rsa.key'))
|
||||
const ecPub = importKey(readFileSync('./removeme/ec.pem'))
|
||||
const rsaPub = importKey(readFileSync('./removeme/rsa.pem'))
|
||||
const payload = { sub: '1234567890', name: 'John Doe', iat: 1516239022 }
|
||||
|
||||
// compact ec
|
||||
;(() => {
|
||||
const signed = JWS.sign(payload, ecPrivate, { alg: 'ES256' }, undefined)
|
||||
console.log('compact ES256 signed', signed, '\n')
|
||||
assert(JWS.verify(signed, ecPrivate), 'compact JWS verification with ecPrivate failed')
|
||||
assert(JWS.verify(signed, ecPub), 'compact JWS verification with ecPub failed')
|
||||
})();
|
||||
|
||||
// compact rsa
|
||||
(() => {
|
||||
const signed = JWS.sign(payload, rsaPrivate, { alg: 'RS256' }, undefined)
|
||||
console.log('compact RS256 signed', signed, '\n')
|
||||
assert(JWS.verify(signed, rsaPrivate), 'compact JWS verification with rsaPrivate failed')
|
||||
assert(JWS.verify(signed, rsaPub), 'compact JWS verification with rsaPub failed')
|
||||
})();
|
||||
|
||||
// flattened
|
||||
(() => {
|
||||
const jws = new JWS.Sign({ sub: 'Filip', name: 'Skokan' }, { typ: 'JWT' })
|
||||
jws.recipient(ecPrivate, { alg: 'ES256' })
|
||||
const signed = jws.sign('flattened')
|
||||
console.log('flattened ES256 signed', signed, '\n')
|
||||
assert(JWS.verify(signed, ecPrivate), 'flattened JWS verification with ecPrivate failed')
|
||||
assert(JWS.verify(signed, ecPub), 'flattened JWS verification with ecPub failed')
|
||||
})();
|
||||
|
||||
// general
|
||||
(() => {
|
||||
const jws = new JWS.Sign({ sub: 'Filip', name: 'Skokan' }, { typ: 'JWT' })
|
||||
jws.recipient(ecPrivate, { alg: 'ES256' })
|
||||
jws.recipient(rsaPrivate, { alg: 'RS512' })
|
||||
const signed = jws.sign('general')
|
||||
console.log('general ES256 and RS512 signed', signed, '\n')
|
||||
assert(JWS.verify(signed, ecPrivate), 'general JWS verification with ecPrivate failed')
|
||||
assert(JWS.verify(signed, ecPub), 'general JWS verification with ecPub failed')
|
||||
assert(JWS.verify(signed, rsaPrivate), 'general JWS verification with rsaPrivate failed')
|
||||
assert(JWS.verify(signed, rsaPub), 'general JWS verification with rsaPub failed')
|
||||
})();
|
||||
Reference in New Issue
Block a user