-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathsasl.js
147 lines (117 loc) · 3.82 KB
/
sasl.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
'use strict'
const crypto = require('crypto')
function startSession (mechanisms) {
if (mechanisms.indexOf('SCRAM-SHA-256') === -1) {
throw new Error('SASL: Only mechanism SCRAM-SHA-256 is currently supported')
}
const clientNonce = crypto.randomBytes(18).toString('base64')
return {
mechanism: 'SCRAM-SHA-256',
clientNonce,
response: 'n,,n=*,r=' + clientNonce,
message: 'SASLInitialResponse'
}
}
function continueSession (session, password, serverData) {
if (session.message !== 'SASLInitialResponse') {
throw new Error('SASL: Last message was not SASLInitialResponse')
}
const sv = extractVariablesFromFirstServerMessage(serverData)
if (!sv.nonce.startsWith(session.clientNonce)) {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not start with client nonce')
}
var saltBytes = Buffer.from(sv.salt, 'base64')
var saltedPassword = Hi(password, saltBytes, sv.iteration)
var clientKey = createHMAC(saltedPassword, 'Client Key')
var storedKey = crypto.createHash('sha256').update(clientKey).digest()
var clientFirstMessageBare = 'n=*,r=' + session.clientNonce
var serverFirstMessage = 'r=' + sv.nonce + ',s=' + sv.salt + ',i=' + sv.iteration
var clientFinalMessageWithoutProof = 'c=biws,r=' + sv.nonce
var authMessage = clientFirstMessageBare + ',' + serverFirstMessage + ',' + clientFinalMessageWithoutProof
var clientSignature = createHMAC(storedKey, authMessage)
var clientProofBytes = xorBuffers(clientKey, clientSignature)
var clientProof = clientProofBytes.toString('base64')
var serverKey = createHMAC(saltedPassword, 'Server Key')
var serverSignatureBytes = createHMAC(serverKey, authMessage)
session.message = 'SASLResponse'
session.serverSignature = serverSignatureBytes.toString('base64')
session.response = clientFinalMessageWithoutProof + ',p=' + clientProof
}
function finalizeSession (session, serverData) {
if (session.message !== 'SASLResponse') {
throw new Error('SASL: Last message was not SASLResponse')
}
var serverSignature
String(serverData).split(',').forEach(function (part) {
switch (part[0]) {
case 'v':
serverSignature = part.substr(2)
break
}
})
if (serverSignature !== session.serverSignature) {
throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match')
}
}
function extractVariablesFromFirstServerMessage (data) {
var nonce, salt, iteration
String(data).split(',').forEach(function (part) {
switch (part[0]) {
case 'r':
nonce = part.substr(2)
break
case 's':
salt = part.substr(2)
break
case 'i':
iteration = parseInt(part.substr(2), 10)
break
}
})
if (!nonce) {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing')
}
if (!salt) {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt missing')
}
if (!iteration) {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing')
}
return {
nonce,
salt,
iteration
}
}
function xorBuffers (a, b) {
if (!Buffer.isBuffer(a)) a = Buffer.from(a)
if (!Buffer.isBuffer(b)) b = Buffer.from(b)
var res = []
if (a.length > b.length) {
for (var i = 0; i < b.length; i++) {
res.push(a[i] ^ b[i])
}
} else {
for (var j = 0; j < a.length; j++) {
res.push(a[j] ^ b[j])
}
}
return Buffer.from(res)
}
function createHMAC (key, msg) {
return crypto.createHmac('sha256', key).update(msg).digest()
}
function Hi (password, saltBytes, iterations) {
var ui1 = createHMAC(password, Buffer.concat([saltBytes, Buffer.from([0, 0, 0, 1])]))
var ui = ui1
for (var i = 0; i < iterations - 1; i++) {
ui1 = createHMAC(password, ui1)
ui = xorBuffers(ui, ui1)
}
return ui
}
module.exports = {
startSession,
continueSession,
finalizeSession
}