Skip to content

Commit

Permalink
Merge pull request #111 from Terreii/msrCrypto
Browse files Browse the repository at this point in the history
Move to msrCrypto
  • Loading branch information
Terreii authored Nov 13, 2020
2 parents 9352ff8 + f376796 commit 6cf1c7f
Show file tree
Hide file tree
Showing 13 changed files with 9,679 additions and 364 deletions.
13 changes: 13 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# EditorConfig from https://EditorConfig.org

# top-most EditorConfig file
root = true

# All files
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
56 changes: 41 additions & 15 deletions lib/create-key.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,55 @@
'use strict'

var Buffer = require('buffer/').Buffer
var pbkdf2 = require('pbkdf2')
var Promise = require('lie')
var randomBytes = require('randombytes')
var getCrypto = require('./utils/get-web-crypto')

module.exports = function createKey (password, saltArg) {
var digest = 'sha256'
var subtle = getCrypto().subtle

var passwordBuffer = toBuffer(password, 'utf-8', 'password')
var digest = 'SHA-256'
var iterations = 100000
var keyLength = 256 / 8

var salt = saltArg != null && typeof saltArg === 'string' && saltArg.length === 32
? saltArg
: randomBytes(16).toString('hex')
var saltyBuffy = Buffer.from(salt, 'hex')

return new Promise(function (resolve, reject) {
var saltyBuffy = Buffer.from(salt, 'hex')

pbkdf2.pbkdf2(password, saltyBuffy, iterations, 256 / 8, digest, function (err, key) {
if (err) {
reject(err)
} else {
resolve({
key: key,
salt: salt
})
return subtle.importKey('raw', passwordBuffer, { name: 'PBKDF2' }, false, ['deriveBits'])

.then(function (key) {
return subtle.deriveBits(
{
name: 'PBKDF2',
salt: saltyBuffy,
iterations: iterations,
hash: {
name: digest
}
},
key,
keyLength << 3
)
})

.then(function (res) {
return {
key: Buffer.from(res),
salt: salt
}
})
})
}

function toBuffer (thing, encoding, name) {
if (Buffer.isBuffer(thing)) {
return thing
} else if (typeof thing === 'string') {
return Buffer.from(thing, encoding)
} else if (ArrayBuffer.isView(thing)) {
return Buffer.from(thing.buffer)
} else {
throw new TypeError(name + ' must be a string, a Buffer, a typed array or a DataView')
}
}
7 changes: 2 additions & 5 deletions lib/helpers/change-password-and-update-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,8 @@ function changePassword (store, state, oldKey, newPassword) {
.map(function (doc) {
return decryptOne(data.oldKey, doc)

.catch(function (error) {
if (error.message === 'Unsupported state or unable to authenticate data') {
return doc._id
}
throw error
.catch(function () {
return doc._id
})
})

Expand Down
99 changes: 18 additions & 81 deletions lib/helpers/decrypt-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

module.exports = decrypt

var aes = require('browserify-aes')
var Buffer = require('buffer/').Buffer

var subtle = global.crypto && global.crypto.subtle
var getCrypto = require('../utils/get-web-crypto')

/**
* Decrypt data.
Expand All @@ -20,93 +18,32 @@ function decrypt (key, iv, data, tag, aad) {
if (key.length !== 32) {
return Promise.reject(new TypeError('invalid key size'))
}
var subtle = getCrypto().subtle

return checkBrowser()

// Original implementation from:
// https://github.com/calvinmetcalf/native-crypto/blob/master/browser/decrypt.js
.then(function (supportsBrowserCrypto) {
// for when the browser supports crypto.subtle
if (supportsBrowserCrypto) {
return subtle.importKey('raw', key, { name: 'AES-GCM' }, true, ['decrypt'])

.then(function (cryptoKey) {
var options = {
name: 'AES-GCM',
iv: iv
}

if (aad != null) {
options.additionalData = aad
}

var encryptedData = Buffer.concat([data, tag])
return subtle.decrypt(options, cryptoKey, encryptedData)
})
return subtle.importKey('raw', key, { name: 'AES-GCM' }, true, ['decrypt'])

.then(function (result) {
return Buffer.from(result).toString()
})
} else {
// fall back to browserify-aes
var cipher = aes.createDecipheriv('aes-256-gcm', key, iv)
if (aad != null) {
cipher.setAAD(aad)
}
cipher.setAuthTag(tag)

var output = cipher.update(data)
cipher.final()
return output.toString()
.then(function (cryptoKey) {
var options = {
name: 'AES-GCM',
iv: iv
}
})
}

var canUseBrowserCrypto = null

/**
* This checks if the browser supports crypto.subtle and the used encryption algorithm.
*/
function checkBrowser () {
if (global.process && !global.process.browser) {
return Promise.resolve(false)
}
if (!subtle || !subtle.importKey || !subtle.encrypt) {
return Promise.resolve(false)
}
if (canUseBrowserCrypto != null) {
return Promise.resolve(canUseBrowserCrypto)
}

var zeroBuffy = Buffer.alloc(16, 0)

return subtle.importKey('raw', zeroBuffy, { name: 'AES-GCM' }, true, ['decrypt'])
if (aad != null) {
options.additionalData = aad
}

.then(function (key) {
return subtle.decrypt(
{
name: 'AES-GCM',
iv: zeroBuffy.slice(0, 12),
additionalData: zeroBuffy.slice(0, 8)
},
key,
Buffer.from(
'A4jazmC2o5LzKMK5cbL+ePeVqqtJS1kj9/2J/5SLweAgAhEhTnOU2iCJtqzQk6vgyU2iGRG' +
'OKX17fry8ycOI8p7MWbwdPpusE+GvoYsO2TE=',
'base64'
)
)
var encryptedData = Buffer.concat([data, tag])
return subtle.decrypt(options, cryptoKey, encryptedData)
})

.then(function (result) {
canUseBrowserCrypto = Buffer.from(result).toString('base64') ===
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='

return canUseBrowserCrypto
return Buffer.from(result).toString()
})

.catch(function () {
canUseBrowserCrypto = false
return canUseBrowserCrypto
.catch(function (err) {
if (err.type === 'error' && err.data instanceof Error) {
throw err.data
}
throw err
})
}
104 changes: 22 additions & 82 deletions lib/helpers/encrypt-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

module.exports = encrypt

var aes = require('browserify-aes')
var Buffer = require('buffer/').Buffer
var Promise = require('lie')

var subtle = global.crypto && global.crypto.subtle
var getCrypto = require('../utils/get-web-crypto')

/**
* Encrypt data.
Expand All @@ -20,94 +18,36 @@ function encrypt (key, iv, data, aad) {
if (key.length !== 32) {
return Promise.reject(new TypeError('invalid key size'))
}
var subtle = getCrypto().subtle

return checkBrowser()

// Original implementation from:
// https://github.com/calvinmetcalf/native-crypto/blob/master/browser/encrypt.js
.then(function (supportsBrowserCrypto) {
// for when the browser supports crypto.subtle
if (supportsBrowserCrypto) {
return subtle.importKey('raw', key, { name: 'AES-GCM' }, true, ['encrypt'])

.then(function (key) {
var options = {
name: 'AES-GCM',
iv: iv
}

if (aad != null) {
options.additionalData = aad
}

return subtle.encrypt(options, key, data)
})

.then(function (response) {
var buffy = Buffer.from(response)
return subtle.importKey('raw', key, { name: 'AES-GCM' }, true, ['encrypt'])

return {
tag: buffy.slice(-16).toString('hex'),
data: buffy.slice(0, -16).toString('hex')
}
})
} else {
// fall back to browserify-aes
var cipher = aes.createCipheriv('aes-256-gcm', key, iv)
if (aad != null) {
cipher.setAAD(aad)
}

var output = cipher.update(data)
cipher.final()
var tag = cipher.getAuthTag()

return {
tag: tag.toString('hex'),
data: output.toString('hex')
}
.then(function (key) {
var options = {
name: 'AES-GCM',
iv: iv
}
})
}

var canUseBrowserCrypto = null

/**
* This checks if the browser supports crypto.subtle and the used encryption algorithm.
*/
function checkBrowser () {
if (global.process && !global.process.browser) {
return Promise.resolve(false)
}
if (!subtle || !subtle.importKey || !subtle.encrypt) {
return Promise.resolve(false)
}
if (canUseBrowserCrypto != null) {
return Promise.resolve(canUseBrowserCrypto)
}

var zeroBuffy = Buffer.alloc(32, 0)
var ivFaith = Buffer.alloc(12, 0)

return subtle.importKey('raw', zeroBuffy.buffer, { name: 'AES-GCM' }, true, ['encrypt'])
if (aad != null) {
options.additionalData = aad
}

.then(function (key) {
return subtle.encrypt(
{ name: 'AES-GCM', iv: ivFaith },
key,
zeroBuffy.buffer
)
return subtle.encrypt(options, key, data)
})

.then(function (res) {
canUseBrowserCrypto = Buffer.from(res)
.toString('base64') === 'zqdAPU1ga24HTsXTuvOdGHJgA8o3pip00aL1jnUGNY7R0whMmaqKn9q7PoPrKMFd'
.then(function (response) {
var buffy = Buffer.from(response)

return canUseBrowserCrypto
return {
tag: buffy.slice(-16).toString('hex'),
data: buffy.slice(0, -16).toString('hex')
}
})

.catch(function () {
canUseBrowserCrypto = false
return canUseBrowserCrypto
.catch(function (err) {
if (err.type === 'error' && err.data instanceof Error) {
throw err.data
}
throw err
})
}
45 changes: 45 additions & 0 deletions lib/utils/get-web-crypto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// select the web crypto API

module.exports = getCrypto

var msrCryptoPolyfill = require('./msrcrypto')

var globalContext = (function () {
try {
return globalThis // eslint-disable-line no-undef
} catch (err) {}

try {
return window
} catch (err) {}

try {
return global
} catch (err) {}

try {
return process
} catch (err) {
return undefined
}
})()

/**
* Get the web crypto api
* @returns {Crypto} Web Crypto API
*/
function getCrypto () {
if (globalContext == null) {
return msrCryptoPolyfill
}

// globalContext.msCrypto (IE11) isn't listed, because it does not support deriveBits.
// It could be added back, once the CryptoKey object is used.
if (globalContext.crypto && globalContext.crypto.subtle) {
return globalContext.crypto
}
if (globalContext.msrCrypto && globalContext.msrCrypto.subtle) {
return globalContext.msrCrypto
}
return msrCryptoPolyfill
}
Loading

0 comments on commit 6cf1c7f

Please sign in to comment.