-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
178 lines (150 loc) · 5.06 KB
/
index.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
var crypto = require("crypto"),
id_pool = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
settings = {
service: "xmpp"
};
/*
* @summary Encodes something using base64.
*
* @param thing - The thing to encode using base64.
*
* @returns {string} - Teh thing encoded using base64.
*/
function base64_encode(thing)
{
return new Buffer(thing).toString("base64");
}
/*
* @summary Decodes something that is encoded using base64.
*
* @param thing - The thing to decode from base64.
*
* @returns The decoded thing.
*/
function base64_decode(thing)
{
return new Buffer(thing, "base64").toString("utf-8");
}
/*
* @summary Calculates the MD5 hash of something.
*
* @param thing - The thing to hash.
* @param encoding {string} - The encoding to return the hash as. Defautls to "hex".
*
* @returns The MD5 hash of `thing`.
*/
function md5(thing, encoding)
{
if(typeof encoding != "string") encoding = "hex";
return crypto.createHash("md5").update(thing).digest(encoding);
}
/*
* @summary Generates a random string of letters and numbers.
*
* @param length {number} - The length of the string to generate. Defaults to 32 characters.
*
* @returns {string} - A random string of letters and numbers of length `length`.
*/
function get_id(length)
{
if(typeof length != "number") length = 10;
var result = "";
for(var i = 0; i < length; i++)
{
result += id_pool[Math.floor(Math.random() * id_pool.length)];
}
return result;
}
/*
* @summary Parses a DIGEST-MD5 challenge into it's component parts.
*
* @param challenge {string} - The challenge to parse.
*
* @returns {object} An object contains the component parts of the given challenge.
*/
function parse_challenge(challenge)
{
var parts = {};
challenge.split(",").forEach(function(str) {
var str_split = str.split("=");
if(str_split[1].substring(0, 1) == `"`) str_split[1] = str_split[1].substring(1);
if(str_split[1].slice(-1) == `"`) str_split[1] = str_split[1].slice(0, -1);
parts[str_split[0]] = str_split[1];
});
return parts;
}
/*
* @summary Calculates the response to a given challenge.
*
* @reference http://web.archive.org/web/20050224191820/http://cataclysm.cx/wip/digest-md5-crash.html
*
* @param c_parts {object} - A parsed challenge from parse_challenge().
* @param user {string} - The name of the user to authenticate.
* @param pass {string} - The user's password.
* @param resource {string} - The resource to request. Omitting results in the response being calculated without a resource. Note that some servers (e.g. Prosody) don't support an authzid when using DIGEST-MD5.
*/
function calculate_response(c_parts, user, pass, resource)
{
if(typeof user != "string")
throw new Error(`Invalid user: ${user}. Expecting string.`);
if(typeof pass != "string")
throw new Error(`Invalid pass: ${pass}. Expecting string.`);
if(typeof resource != "string") resource = false;
var response = "";
response += `username="${user}",realm="${c_parts.realm},nonce="${c_parts.nonce}",`;
var cnonce = get_id(32),
// Note Prosody doesn't actually contain support for an authzid when using DIGEST-MD5
authzid = `${user}@${c_parts.realm}/${resource}`,
digest_uri = `${settings.service}/${c_parts.realm}`;
response += `cnonce="${cnonce}",nc=00000001,qop=auth,`;
response += `digest-uri="${digest_uri}",charset=utf-8,`;
var x = `${user}:${c_parts.realm}:${pass}`,
y = md5(x, "binary"),
a1 = `${y}:${c_parts.nonce}:${cnonce}`;
if(resource) a1 += `:${authzid}`;
var a2 = `AUTHENTICATE:${digest_uri}`,
ha1 = md5(a1),
ha2 = md5(a2),
kd = `${ha1}:${c_parts.nonce}:00000001:${cnonce}:${c_parts.qop}:${ha2}`,
z = md5(kd);
response += `response=${z}`;
if(resource) response += `,authzid="${authzid}"`;
return response;
}
/*
* @summary Calculates the response to a given challenge.
*
* @reference http://web.archive.org/web/20050224191820/http://cataclysm.cx/wip/digest-md5-crash.html
*
* @param c_parts {string} - The challenge to calculate a response for.
* @param user {string} - The name of the user to authenticate.
* @param pass {string} - The user's password.
* @param resource {string} - The resource to request. Omitting results in the response being calculated without a resource. Note that some servers (e.g. Prosody) don't support an authzid when using DIGEST-MD5.
*/
function get_response(challenge, user, pass, resource)
{
return calculate_response(parse_challenge(challenge), user, pass, resource);
}
/*
* @summary The same as get_response, but decodes the challenge from base64 first.
*/
function get_response_b64(challenge, user, pass, resource)
{
return get_response(base64_decode(challenge), user, pass, resource);
}
/*
* @summary Sets the service you are authenticating to.
*
* @param The service to authenticate with.
*/
function set_service(service) { settings.service = service; }
module.exports = {
parse_challenge: parse_challenge,
get_response_raw: calculate_response,
get_response: get_response,
get_response_b64: get_response_b64,
base64_encode: base64_encode,
base64_decode: base64_decode,
md5: md5,
set_service: set_service
};