Skip to content

Commit

Permalink
refactor: use private instance fields where possible
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Apr 23, 2019
1 parent 1159b0d commit a8ef20e
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 69 deletions.
7 changes: 4 additions & 3 deletions lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,19 @@ class JOSEError extends Error {
}

const isMulti = e => e instanceof JOSEMultiError
const ERRORS = Symbol('ERRORS')
class JOSEMultiError extends JOSEError {
#errors

constructor (errors) {
super()
let i
while ((i = errors.findIndex(isMulti)) && i !== -1) {
errors.splice(i, 1, ...errors[i])
}
this[ERRORS] = errors
this.#errors = errors
}
* [Symbol.iterator] () {
for (const error of this[ERRORS]) {
for (const error of this.#errors) {
yield error
}
}
Expand Down
81 changes: 41 additions & 40 deletions lib/jwe/encrypt.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ const serializers = require('./serializers')
const generateCEK = require('./generate_cek')
const validateHeaders = require('./validate_headers')

const AAD = Symbol('AAD')
const CEK = Symbol('CEK')
const CLEARTEXT = Symbol('CLEARTEXT')
const PROCESS_RECIPIENT = Symbol('PROCESS_RECIPIENT')
const PROTECTED = Symbol('PROTECTED')
const RECIPIENTS = Symbol('RECIPIENTS')
const UNPROTECTED = Symbol('UNPROTECTED')

class Encrypt {
#aad
#cek
#unprotected
#protected
#cleartext
#recipients

constructor (cleartext, protectedHeader, unprotectedHeader, aad) {
if (!Buffer.isBuffer(cleartext) && typeof cleartext !== 'string') {
throw new TypeError('cleartext argument must be a Buffer or a string')
Expand All @@ -43,13 +44,11 @@ class Encrypt {
throw new TypeError('unprotectedHeader argument must be a plain object when provided')
}

Object.assign(this, {
[CLEARTEXT]: cleartext,
[RECIPIENTS]: [],
[PROTECTED]: protectedHeader ? deepClone(protectedHeader) : undefined,
[UNPROTECTED]: unprotectedHeader ? deepClone(unprotectedHeader) : undefined,
[AAD]: aad
})
this.#recipients = []
this.#cleartext = cleartext
this.#aad = aad
this.#unprotected = unprotectedHeader ? deepClone(unprotectedHeader) : undefined
this.#protected = protectedHeader ? deepClone(protectedHeader) : undefined
}

/*
Expand All @@ -64,7 +63,7 @@ class Encrypt {
throw new TypeError('header argument must be a plain object when provided')
}

this[RECIPIENTS].push({
this.#recipients.push({
key,
header: header ? deepClone(header) : undefined
})
Expand All @@ -76,7 +75,9 @@ class Encrypt {
* @private
*/
[PROCESS_RECIPIENT] (recipient) {
const { [PROTECTED]: protectedHeader, [UNPROTECTED]: unprotectedHeader, [RECIPIENTS]: { length: recipientCount } } = this
const unprotectedHeader = this.#unprotected
const protectedHeader = this.#protected
const { length: recipientCount } = this.#recipients

const jweHeader = {
...protectedHeader,
Expand Down Expand Up @@ -107,7 +108,7 @@ class Encrypt {
if (protectedHeader) {
protectedHeader.alg = alg
} else {
this[PROTECTED] = { alg }
this.#protected = { alg }
}
} else {
if (recipient.header) {
Expand All @@ -122,11 +123,11 @@ class Encrypt {
let generatedHeader

if (key.kty === 'oct' && alg === 'dir') {
this[CEK] = importKey(key[KEYOBJECT], { use: 'enc', alg: enc })
this.#cek = importKey(key[KEYOBJECT], { use: 'enc', alg: enc })
} else {
({ wrapped, header: generatedHeader } = wrapKey(alg, key, this[CEK][KEYOBJECT].export(), { enc, alg }))
({ wrapped, header: generatedHeader } = wrapKey(alg, key, this.#cek[KEYOBJECT].export(), { enc, alg }))
if (alg === 'ECDH-ES') {
this[CEK] = importKey(createSecretKey(wrapped), { use: 'enc', alg: enc })
this.#cek = importKey(createSecretKey(wrapped), { use: 'enc', alg: enc })
}
}

Expand All @@ -150,58 +151,58 @@ class Encrypt {
throw new TypeError('serialization must be one of "compact", "flattened", "general"')
}

if (!this[RECIPIENTS].length) {
if (!this.#recipients.length) {
throw new JWEInvalid('missing recipients')
}

serializer.validate(this[PROTECTED], this[UNPROTECTED], this[AAD], this[RECIPIENTS])
serializer.validate(this.#protected, this.#unprotected, this.#aad, this.#recipients)

let enc = validateHeaders(this[PROTECTED], this[UNPROTECTED], this[RECIPIENTS], false, this[PROTECTED] ? this[PROTECTED].crit : undefined)
let enc = validateHeaders(this.#protected, this.#unprotected, this.#recipients, false, this.#protected ? this.#protected.crit : undefined)
if (!enc) {
enc = 'A128CBC-HS256'
if (this[PROTECTED]) {
this[PROTECTED].enc = enc
if (this.#protected) {
this.#protected.enc = enc
} else {
this[PROTECTED] = { enc }
this.#protected = { enc }
}
}
const final = {}
this[CEK] = generateCEK(enc)
this.#cek = generateCEK(enc)

this[RECIPIENTS].forEach(this[PROCESS_RECIPIENT].bind(this))
this.#recipients.forEach(this[PROCESS_RECIPIENT].bind(this))

const iv = generateIV(enc)
final.iv = base64url.encodeBuffer(iv)

if (this[RECIPIENTS].length === 1 && this[RECIPIENTS][0].generatedHeader) {
const [{ generatedHeader }] = this[RECIPIENTS]
delete this[RECIPIENTS][0].generatedHeader
this[PROTECTED] = Object.assign({}, this[PROTECTED], generatedHeader)
if (this.#recipients.length === 1 && this.#recipients[0].generatedHeader) {
const [{ generatedHeader }] = this.#recipients
delete this.#recipients[0].generatedHeader
this.#protected = Object.assign({}, this.#protected, generatedHeader)
}

if (this[PROTECTED]) {
final.protected = base64url.JSON.encode(this[PROTECTED])
if (this.#protected) {
final.protected = base64url.JSON.encode(this.#protected)
}
final.unprotected = this[UNPROTECTED]
final.unprotected = this.#unprotected

let aad
if (this[AAD]) {
final.aad = base64url.encode(this[AAD])
if (this.#aad) {
final.aad = base64url.encode(this.#aad)
aad = Buffer.concat([Buffer.from(final.protected || ''), Buffer.from('.'), Buffer.from(final.aad)])
} else {
aad = Buffer.from(final.protected || '')
}

let cleartext = this[CLEARTEXT]
if (this[PROTECTED] && 'zip' in this[PROTECTED]) {
let cleartext = this.#cleartext
if (this.#protected && 'zip' in this.#protected) {
cleartext = deflateRawSync(cleartext)
}

const { ciphertext, tag } = encrypt(enc, this[CEK], cleartext, { iv, aad })
const { ciphertext, tag } = encrypt(enc, this.#cek, cleartext, { iv, aad })
final.tag = base64url.encodeBuffer(tag)
final.ciphertext = base64url.encodeBuffer(ciphertext)

return serializer(final, this[RECIPIENTS])
return serializer(final, this.#recipients)
}
}

Expand Down
20 changes: 10 additions & 10 deletions lib/jwks/keystore.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ const { generate, generateSync } = require('../jwk/generate')
const Key = require('../jwk/key/base')
const importKey = require('../jwk/import')

const KEYS = Symbol('keys')

const keyscore = (key, { alg, kid, use }) => {
let score = 0

Expand All @@ -24,6 +22,8 @@ const keyscore = (key, { alg, kid, use }) => {
}

class KeyStore {
#keys

constructor (...keys) {
while (keys.some(Array.isArray)) {
keys = keys.flat()
Expand All @@ -32,7 +32,7 @@ class KeyStore {
throw new TypeError('all keys must be an instances of a key instantiated by JWK.importKey')
}

Object.defineProperty(this, KEYS, { value: new Set(keys) })
this.#keys = new Set(keys);
}

static fromJWKS (jwks) {
Expand All @@ -46,7 +46,7 @@ class KeyStore {
}

all ({ alg, kid, use, kty, operation } = {}) {
return [...this[KEYS]]
return [...this.#keys]
.filter((key) => {
let candidate = true

Expand Down Expand Up @@ -80,31 +80,31 @@ class KeyStore {
throw new TypeError('key must be an instance of a key instantiated by JWK.importKey')
}

this[KEYS].add(key)
this.#keys.add(key)
}

remove (key) {
if (!(key instanceof Key)) {
throw new TypeError('key must be an instance of a key instantiated by JWK.importKey')
}

this[KEYS].delete(key)
this.#keys.delete(key)
}

toJWKS (priv = false) {
return { keys: [...this[KEYS].values()].map(key => key.toJWK(priv)) }
return { keys: [...this.#keys.values()].map(key => key.toJWK(priv)) }
}

async generate (...args) {
this[KEYS].add(await generate(...args))
this.#keys.add(await generate(...args))
}

generateSync (...args) {
this[KEYS].add(generateSync(...args))
this.#keys.add(generateSync(...args))
}

get size () {
return this[KEYS].size
return this.#keys.size
}
}

Expand Down
31 changes: 15 additions & 16 deletions lib/jws/sign.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ const { check, sign } = require('../jwa')

const serializers = require('./serializers')

const RECIPIENTS = Symbol('RECIPIENTS')
const PAYLOAD = Symbol('PAYLOAD')
const PROCESS_RECIPIENT = Symbol('PROCESS_RECIPIENT')
const B64 = Symbol('b64')

class Sign {
#b64
#payload
#recipients

constructor (payload) {
if (typeof payload === 'string') {
payload = base64url.encode(payload)
Expand All @@ -25,10 +26,8 @@ class Sign {
throw new TypeError('payload argument must be a Buffer, string or an object')
}

Object.assign(this, {
[PAYLOAD]: payload,
[RECIPIENTS]: []
})
this.#payload = payload
this.#recipients = []
}

/*
Expand All @@ -51,7 +50,7 @@ class Sign {
throw new JWSInvalid('JWS Protected and JWS Unprotected Header Parameter names must be disjoint')
}

this[RECIPIENTS].push({
this.#recipients.push({
key,
protectedHeader: protectedHeader ? deepClone(protectedHeader) : undefined,
unprotectedHeader: unprotectedHeader ? deepClone(unprotectedHeader) : undefined
Expand Down Expand Up @@ -89,19 +88,19 @@ class Sign {
}

if (joseHeader.protected.crit && joseHeader.protected.crit.includes('b64')) {
if (B64 in this && this[B64] !== joseHeader.protected.b64) {
if (this.#b64 !== undefined && this.#b64 !== joseHeader.protected.b64) {
throw new JWSInvalid('the "b64" Header Parameter value MUST be the same for all recipients')
} else {
this[B64] = joseHeader.protected.b64
this.#b64 = joseHeader.protected.b64
}
if (!joseHeader.protected.b64) {
this[PAYLOAD] = base64url.decode(this[PAYLOAD])
this.#payload = base64url.decode(this.#payload)
}
}

recipient.header = unprotectedHeader
recipient.protected = Object.keys(joseHeader.protected).length ? base64url.JSON.encode(joseHeader.protected) : ''
recipient.signature = base64url.encodeBuffer(sign(alg, key, Buffer.from(`${recipient.protected}.${this[PAYLOAD]}`)))
recipient.signature = base64url.encodeBuffer(sign(alg, key, Buffer.from(`${recipient.protected}.${this.#payload}`)))
}

/*
Expand All @@ -113,15 +112,15 @@ class Sign {
throw new TypeError('serialization must be one of "compact", "flattened", "general"')
}

if (!this[RECIPIENTS].length) {
if (!this.#recipients.length) {
throw new JWSInvalid('missing recipients')
}

serializer.validate(this, this[RECIPIENTS])
serializer.validate(this, this.#recipients)

this[RECIPIENTS].forEach(this[PROCESS_RECIPIENT].bind(this))
this.#recipients.forEach(this[PROCESS_RECIPIENT].bind(this))

return serializer(this[PAYLOAD], this[RECIPIENTS])
return serializer(this.#payload, this.#recipients)
}
}

Expand Down

0 comments on commit a8ef20e

Please sign in to comment.