Skip to content

Instantly share code, notes, and snippets.

@kemitchell
Created November 11, 2020 03:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kemitchell/d8efef457f1a99f38b5909fe7180d0e2 to your computer and use it in GitHub Desktop.
Save kemitchell/d8efef457f1a99f38b5909fe7180d0e2 to your computer and use it in GitHub Desktop.
const assert = require('assert')
module.exports = function ({
clientStretch,
serverStretch,
serverStretchSaltLength,
deriveKey,
authenticate,
random,
generateUserID,
verificationHash,
authenticationToken: authenticationTokenParameters,
clientKey: clientKeyParameters,
serverKey: serverKeyParameters,
verificationHash: verificationHashParameters,
responseAuthenticationKey: responseAuthenticationKeyParameters,
responseEncryptionKey: responseEncryptionKeyParameters,
requestAuthenticationKey: requestAuthenticationKeyParameters,
keyRequestToken: keyRequestTokenParameters,
tokenID: tokenIDParameters
}) {
// Cryptographic Primitives
assert(typeof clientStretch === 'function')
assert(typeof serverStretch === 'function')
assert(Number.isInteger(serverStretchSaltLength))
assert(serverStretchSaltLength > 0)
assert(typeof deriveKey === 'function')
assert(typeof authenticate === 'function')
assert(typeof random === 'function')
assert(typeof generateUserID === 'function')
// Key Derivation Parameters
assert(typeof verificationHashParameters === 'object')
assert(typeof authenticationTokenParameters === 'object')
assert(typeof clientKeyParameters === 'object')
assert(typeof serverKeyParameters === 'object')
assert(typeof responseAuthenticationKeyParameters === 'object')
assert(typeof responseEncryptionKeyParameters === 'object')
assert(typeof requestAuthenticationKeyParameters === 'object')
assert(typeof keyRequestTokenParameters === 'object')
assert(typeof tokenIDParameters === 'object')
// API
return {
client: {
login: clientLogin,
request: clientRequest
},
server: {
register: serverRegister,
login: serverLogin,
request: serverRequest
}
}
function clientLogin ({ password, email }) {
assert(typeof password === 'string')
assert(password.length > 0)
const passwordBuffer = Buffer.from(password, 'utf8')
assert(typeof email === 'string')
assert(email.length > 0)
assert(email.indexOf('@') > 1)
const emailBuffer = Buffer.from(email, 'utf8')
const clientStretchedPassword = clientStretch({
password: passwordBuffer,
salt: emailBuffer
})
const authenticationToken = deriveKeyHelper(
clientStretchedPassword, authenticationTokenParameters
)
return {
authenticationToken,
clientStretchedPassword
}
}
function serverRegister ({
clientStretchedPassword,
authenticationToken
}) {
assert(Buffer.isBuffer(clientStretchedPassword))
assert(clientStretchedPassword.byteLength > 0)
assert(Buffer.isBuffer(authenticationToken))
assert(authenticationToken.byteLength > 0)
const authenticationSalt = random(serverStretchSaltLength)
const serverStretchedPassword = serverStretch({
password: authenticationToken,
salt: authenticationSalt
})
const verificationHash = deriveKeyHelper(
serverStretchedPassword, verificationHashParameters
)
const serverWrappedKey = random(32)
const userID = generateUserID()
return {
authenticationSalt,
userID,
serverWrappedKey,
verificationHash,
serverStretchedPassword
}
}
function serverLogin ({
authenticationToken,
authenticationSalt,
verificationHash
}) {
assert(Buffer.isBuffer(authenticationToken))
assert(authenticationToken.byteLength > 0)
assert(Buffer.isBuffer(authenticationSalt))
assert(authenticationSalt.byteLength > 0)
assert(Buffer.isBuffer(verificationHash))
assert(verificationHash.byteLength > 0)
const serverStretchedPassword = serverStretch({
password: authenticationToken,
salt: authenticationSalt
})
const computedVerificationHash = deriveKeyHelper(
serverStretchedPassword, verificationHashParameters
)
return verificationHash.equals(computedVerificationHash)
}
function serverRequest ({
serverStretchedPassword,
serverWrappedKey,
keyAccessToken
}) {
assert(Buffer.isBuffer(serverStretchedPassword))
assert(serverStretchedPassword.byteLength > 0)
assert(Buffer.isBuffer(serverWrappedKey))
assert(serverWrappedKey.byteLength > 0)
assert(Buffer.isBuffer(keyAccessToken))
assert(keyAccessToken.byteLength > 0)
const parameters = { key: serverStretchedPassword }
Object.assign(parameters, serverKeyParameters)
const serverKey = deriveKeyHelper(
serverStretchedPassword, serverKeyParameters
)
const clientWrappedKey = xor(serverKey, serverWrappedKey)
const tokenID = deriveKeyHelper(
keyAccessToken, tokenIDParameters
)
const requestAuthenticationKey = deriveKeyHelper(
keyAccessToken, requestAuthenticationKeyParameters
)
const keyRequestToken = deriveKeyHelper(
keyAccessToken, keyRequestTokenParameters
)
const responseEncryptionKey = deriveKeyHelper(
keyRequestToken, responseEncryptionKeyParameters
)
const responseAuthenticationKey = deriveKeyHelper(
keyRequestToken, responseAuthenticationKeyParameters
)
const ciphertext = xor(clientWrappedKey, responseEncryptionKey)
const mac = authenticate({
key: responseAuthenticationKey,
input: ciphertext
})
return {
ciphertext,
mac,
requestAuthenticationKey,
tokenID
}
}
function clientRequest ({
ciphertext,
mac,
clientStretchedPassword,
keyAccessToken
}) {
assert(Buffer.isBuffer(ciphertext))
assert(Buffer.isBuffer(mac))
assert(Buffer.isBuffer(clientStretchedPassword))
assert(Buffer.isBuffer(keyAccessToken))
const keyRequestToken = deriveKeyHelper(
keyAccessToken, keyRequestTokenParameters
)
const responseAuthenticationKey = deriveKeyHelper(
keyRequestToken, responseAuthenticationKeyParameters
)
const responseEncryptionKey = deriveKeyHelper(
keyRequestToken, responseEncryptionKeyParameters
)
const computedMAC = authenticate({
key: responseAuthenticationKey,
input: ciphertext
})
if (!mac.equals(computedMAC)) return false
const clientWrappedKey = xor(ciphertext, responseEncryptionKey)
const clientKey = deriveKeyHelper(
clientStretchedPassword, clientKeyParameters
)
const encryptionKey = xor(clientWrappedKey, clientKey)
return { encryptionKey }
}
function deriveKeyHelper (key, parameters) {
assert(Buffer.isBuffer(key))
assert(typeof parameters === 'object')
return deriveKey(Object.assign({ key }, parameters))
}
}
function xor (a, b) {
assert(a.length === b.length)
const returned = Buffer.alloc(a.length)
for (let offset = 0; offset < a.length; offset++) {
returned[offset] = a[offset] ^ b[offset]
}
return returned
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment