This repository has been archived by the owner on Jul 18, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 19
/
duo.js
203 lines (173 loc) · 4.82 KB
/
duo.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
var crypto = require('crypto')
var DUO_PREFIX = 'TX'
var APP_PREFIX = 'APP'
var AUTH_PREFIX = 'AUTH'
var DUO_EXPIRE = 300
var APP_EXPIRE = 3600
var IKEY_LEN = 20
var SKEY_LEN = 40
var AKEY_LEN = 40
/* Exception Messages */
var ERR_USER = 'ERR|The username passed to sign_request() is invalid.'
var ERR_IKEY = 'ERR|The Duo integration key passed to sign_request() is invalid'
var ERR_SKEY = 'ERR|The Duo secret key passed to sign_request() is invalid'
var ERR_AKEY = 'ERR|The application secret key passed to sign_request() must be at least ' + String(AKEY_LEN) + ' characters.'
class UsernameError extends Error {
constructor (message) {
super(message)
this.name = this.constructor.name
Error.captureStackTrace(this, this.constructor)
}
}
class IkeyError extends Error {
constructor (message) {
super(message)
this.name = this.constructor.name
Error.captureStackTrace(this, this.constructor)
}
}
class AkeyError extends Error {
constructor (message) {
super(message)
this.name = this.constructor.name
Error.captureStackTrace(this, this.constructor)
}
}
/**
* @function sign a value
*
* @param {String} key Integration's Secret Key
* @param {String} vals Value(s) to sign
* @param {String} prefix DUO/APP/AUTH Prefix
* @param {Integer} expire time till expiry
*
* @return {String} Containing the signed value in sha1-hmac with prefix
*
* @api private
*/
function _sign_vals (key, vals, prefix, expire) {
var exp = Math.round((new Date()).getTime() / 1000) + expire
var val = vals + '|' + exp
var b64 = Buffer.from(val).toString('base64')
var cookie = prefix + '|' + b64
var sig = crypto.createHmac('sha1', key)
.update(cookie)
.digest('hex')
return cookie + '|' + sig
}
/**
* @function parses a value
*
* @param {String} key Integration's Secret Key
* @param {String} val Value to unpack
* @param {String} prefix DUO/APP/AUTH Prefix
* @param {String} ikey Integration Key
*
* @return {String/Null} Returns a username on successful parse. Null if not
*
* @api private
*/
function _parse_vals (key, val, prefix, ikey) {
var ts = Math.round((new Date()).getTime() / 1000)
var parts = val.split('|')
if (parts.length !== 3) {
return null
}
var u_prefix = parts[0]
var u_b64 = parts[1]
var u_sig = parts[2]
var sig = crypto.createHmac('sha1', key)
.update(u_prefix + '|' + u_b64)
.digest('hex')
if (crypto.createHmac('sha1', key).update(sig).digest('hex') !== crypto.createHmac('sha1', key).update(u_sig).digest('hex')) {
return null
}
if (u_prefix !== prefix) {
return null
}
var cookie_parts = Buffer.from(u_b64, 'base64').toString('utf8').split('|')
if (cookie_parts.length !== 3) {
return null
}
var user = cookie_parts[0]
var u_ikey = cookie_parts[1]
var exp = cookie_parts[2]
if (u_ikey !== ikey) {
return null
}
if (ts >= parseInt(exp)) {
return null
}
return user
}
/**
* @function sign's a login request to be passed onto Duo Security
*
* @param {String} ikey Integration Key
* @param {String} skey Secret Key
* @param {String} akey Application Security Key
* @param {String} username Username
*
* @return {String} Duo Signature
*
* @api public
*/
var sign_request = function (ikey, skey, akey, username) {
if (!username || username.length < 1) {
return ERR_USER
}
if (username.indexOf('|') !== -1) {
return ERR_USER
}
if (!ikey || ikey.length !== IKEY_LEN) {
return ERR_IKEY
}
if (!skey || skey.length !== SKEY_LEN) {
return ERR_SKEY
}
if (!akey || akey.length < AKEY_LEN) {
return ERR_AKEY
}
var vals = username + '|' + ikey
var duo_sig = _sign_vals(skey, vals, DUO_PREFIX, DUO_EXPIRE)
var app_sig = _sign_vals(akey, vals, APP_PREFIX, APP_EXPIRE)
var sig_request = duo_sig + ':' + app_sig
return sig_request
}
/**
* @function verifies a response from Duo Security
*
* @param {String} ikey Integration Key
* @param {String} skey Secret Key
* @param {String} akey Application Security Key
* @param {String} sig_response Signature Response from Duo
*
* @param (String/Null} Returns a string containing the username if the response checks out. Returns null if it does not.
*
* @api public
*/
var verify_response = function (ikey, skey, akey, sig_response) {
var parts = sig_response.split(':')
if (parts.length !== 2) {
return null
}
var auth_sig = parts[0]
var app_sig = parts[1]
var auth_user = _parse_vals(skey, auth_sig, AUTH_PREFIX, ikey)
var app_user = _parse_vals(akey, app_sig, APP_PREFIX, ikey)
if (auth_user !== app_user) {
return null
}
return auth_user
}
module.exports = {
'sign_request': sign_request,
'verify_response': verify_response,
'ERR_USER': ERR_USER,
'ERR_IKEY': ERR_IKEY,
'ERR_AKEY': ERR_AKEY,
'ERR_SKEY': ERR_SKEY,
'UsernameError': UsernameError,
'IkeyError': IkeyError,
'AkeyError': AkeyError
}