-
Notifications
You must be signed in to change notification settings - Fork 1
/
nacl-cert.js
317 lines (271 loc) · 12.4 KB
/
nacl-cert.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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
// Nacl certification implementation
// Copyright (c) 2014 Tom Zhou<[email protected]>
(function(Export, Nacl, UUID){
var CERT_VERSION = '1.0';
// Generate cert
// @param reqdesc: nacl cert description request to sign
// @param cakey: nacl ca signature secret key
// @param cacert: ca cert, self-signed
// @return cert on success, false on fail
Export.generate = function(reqdesc, cakey, cacert) {
// check version
if (!(reqdesc && reqdesc.version === CERT_VERSION)) {
console.log('Invalid cert request version');
return false;
}
// check time-to-expire
if (reqdesc.tte && reqdesc.tte < new Date().getTime()) {
console.log('Invalid cert time-to-expire, smaller than current time');
return false;
}
// check type
if (reqdesc &&
reqdesc.type &&
(reqdesc.type.toLowerCase() === 'self' ||
reqdesc.type.toLowerCase() === 'ca')) {
// override CA field
if (reqdesc.type === 'ca') {
reqdesc.ca = cacert.desc.ca;
// check time-to-expire
if (reqdesc.tte && reqdesc.tte > cacert.desc.tte) {
console.log('Invalid cert time-to-expire, bigger than CA');
return false;
}
}
// append fields
reqdesc.signtime = new Date().getTime();
reqdesc.gid = UUID.v4();
var cert = {desc: reqdesc};
// stringify cert.desc
var descstr = JSON.stringify(cert.desc);
///console.log('\ngenerate for '+descstr);
var descbuf = isNodeJS() ? new Uint8Array(new Buffer(descstr, 'utf-8')) :
Nacl.util.decodeUTF8(descstr);
if (!((cakey &&
Array.isArray(cakey) &&
cakey.length === Nacl.sign.secretKeyLength) ||
(cakey &&
cakey instanceof Uint8Array &&
cakey.length === Nacl.sign.secretKeyLength))) {
console.log('Invalid cert sign secretKey');
return false;
}
var signSecretKey = (cakey instanceof Uint8Array) ? cakey : ArrayToUint8(cakey);
// sign signature
var signature = Nacl.sign.detached(descbuf, signSecretKey);
if (!signature) {
console.log('Sign signature failed');
return false;
}
// append signature
cert.sign = {};
cert.sign.signature = Uint8ToArray(signature);
return cert;
} else {
console.log('Invalid cert type');
return false;
}
}
// Validate cert
// @param reqdesc: nacl cert description to sign
// @param cacert: ca cert, ignore it in case self-sign
// @return true on success, false on fail
Export.validate = function(cert, cacert) {
// check time-to-expire
if (!(cert && cert.desc && cert.desc.tte > new Date().getTime())) {
console.log('nacl cert expired');
return false;
}
// check version
if (!(cert && cert.desc && cert.desc.version.toLowerCase() === CERT_VERSION)) {
console.log('Invalid cert version');
return false;
}
// check type
if (cert &&
cert.desc &&
cert.desc.type &&
cert.desc.type.toLowerCase() === 'self') {
// extract nacl sign publicKey
if (!(cert &&
cert.desc &&
cert.desc.publickey &&
Array.isArray(cert.desc.publickey) &&
cert.desc.publickey.length === Nacl.sign.publicKeyLength)) {
console.log('Invalid cert sign publicKey');
return false;
}
var signPublicKey = ArrayToUint8(cert.desc.publickey);
// stringify cert.desc
var descstr = JSON.stringify(cert.desc);
///console.log('\nvalidate for self-signed:'+descstr);
var descbuf = isNodeJS() ? new Uint8Array(new Buffer(descstr, 'utf-8')) :
Nacl.util.decodeUTF8(descstr);
// extract signature
if (!(cert &&
cert.sign &&
cert.sign.signature &&
Array.isArray(cert.sign.signature) &&
cert.sign.signature.length === Nacl.sign.signatureLength)) {
console.log('Invalid signature');
return false;
}
var signature = ArrayToUint8(cert.sign.signature);
// verify signature
if (!Nacl.sign.detached.verify(descbuf, signature, signPublicKey)) {
console.log('Verify signature failed');
return false;
}
} else if (cert &&
cert.desc &&
cert.desc.type &&
cert.desc.type.toLowerCase() === 'ca') {
// check CA cert, MUST be self-signed
if (!(cacert &&
cacert.desc &&
cacert.desc.type &&
cacert.desc.type.toLowerCase() === 'self')) {
console.log('CA cert MUST be self-signed');
return false;
}
if (!Export.validate(cacert)) {
console.log('Invalid CA cert');
return false;
}
// check CA name
if (!(cert.desc.ca &&
cacert.desc.ca &&
(cert.desc.ca.toLowerCase() === cacert.desc.ca.toLowerCase()))) {
console.log('CA not matched');
return false;
}
// check CA time-to-expire
if (cert.desc.tte && cert.desc.tte > cacert.desc.tte) {
console.log('Invalid cert time-to-expire, bigger than CA');
return false;
}
// extract nacl sign publicKey
var casignPublicKey = ArrayToUint8(cacert.desc.publickey);
// stringify cert.desc
var cadescstr = JSON.stringify(cert.desc);
///console.log('\nvalidate for ca-sign:'+cadescstr);
var cadescbuf = isNodeJS() ? new Uint8Array(new Buffer(cadescstr, 'utf-8')) :
Nacl.util.decodeUTF8(cadescstr);
// extract signature
if (!(cert &&
cert.sign &&
cert.sign.signature &&
Array.isArray(cert.sign.signature) &&
cert.sign.signature.length === Nacl.sign.signatureLength)) {
console.log('Invalid signature');
return false;
}
var casignature = ArrayToUint8(cert.sign.signature);
// verify signature
if (!Nacl.sign.detached.verify(cadescbuf, casignature, casignPublicKey)) {
console.log('Verify signature failed');
return false;
}
} else {
console.log('Invalid cert type');
return false;
}
return true;
}
// Check domain name
Export.checkDomain = function(cert, expectDomain) {
///console.log('expectDomain:'+expectDomain);
var ret = false;
if (cert.desc && cert.desc.names)
for (var i = 0; i < cert.desc.names.length; i ++)
// allow sub-domain match, like xxx.appnet.link will pass in case cert name is appnet.link
if (expectDomain) {
var nmreg = new RegExp('(('+cert.desc.names[i]+')|(.'+cert.desc.names[i]+')$)',"gi");
if (expectDomain.match(nmreg)) {
///console.log('checkDomain passed');
ret = true;
break;
}
}
return ret;
}
// Check IP
Export.checkIP = function(cert, expectIP) {
///console.log('expectIP:'+expectIP);
var ret = false;
if (cert.desc && cert.desc.ips)
for (var i = 0; i < cert.desc.ips.length; i ++)
if (expectIP && expectIP === cert.desc.ips[i]) {
ret = true;
break;
}
return ret;
}
// Check MAC
Export.checkMAC = function(cert, expectMAC) {
///console.log('expectMAC:'+expectMAC);
var ret = false;
if (cert.desc && cert.desc.macs)
for (var i = 0; i < cert.desc.macs.length; i ++)
if (expectMAC && expectMAC === cert.desc.macs[i]) {
ret = true;
break;
}
return ret;
}
// Generate self-sgin CA
// @param cainfo: fill domain name, time-to-expire
Export.generateCA = function(cainfo) {
// prepare self-sign reqdesc
var reqdesc = {};
reqdesc.version = '1.0'; // fixed
reqdesc.type = 'self'; // fixed
reqdesc.ca = cainfo.name; // user input
reqdesc.tte = cainfo.tte; // user input
// generate Sign keypair
var skp = Nacl.sign.keyPair();
reqdesc.publickey = Uint8ToArray(skp.publicKey);
// generate cert
var cert = Export.generate(reqdesc, skp.secretKey);
// return cert with Sign secretKey as JSON array
return {cert: cert, secretkey: Uint8ToArray(skp.secretKey)};
}
// default NACL rootCA cert, never modify it
Export.rootCACert = JSON.parse('{"desc":{"version":"1.0","type":"self","ca":"appnet.link","tte":4570381246341,"publickey":[237,135,86,100,145,128,37,184,250,64,66,132,116,123,207,51,182,199,59,95,17,186,93,249,220,212,109,77,200,222,157,67],"signtime":1416781246454,"gid":"d2f971fc-98ad-4dea-ada2-74ebc129ed99"},"sign":{"signature":[214,154,215,247,146,167,144,7,25,170,129,182,224,231,13,239,250,159,139,23,184,249,151,12,153,188,61,76,32,215,218,31,185,251,224,222,15,3,17,53,121,125,166,143,167,52,148,146,85,94,234,202,196,157,211,142,134,74,109,78,7,123,177,2]}}');
// default NACL testCA, including cert and secretkey
Export.testCA = JSON.parse('{"cert":{"desc":{"version":"1.0","type":"self","ca":"appnet.link","tte":1732375104475,"publickey":[16,239,203,168,67,4,190,200,68,163,63,140,27,142,10,25,65,227,92,199,166,33,30,92,73,221,145,174,220,55,82,34],"signtime":1417015104534,"gid":"8d0fdd95-566c-4917-b158-36bace3254c7"},"sign":{"signature":[84,224,227,61,149,247,74,147,167,225,148,123,103,7,168,101,136,193,121,64,93,37,82,154,3,116,119,206,5,56,96,74,87,195,58,110,233,117,52,57,237,80,91,39,25,223,50,114,201,72,159,158,75,0,230,13,33,34,134,167,171,129,52,0]}},"secretkey":[146,248,181,166,252,192,146,133,46,43,69,244,31,182,120,173,115,43,14,89,157,78,77,216,13,240,28,84,186,40,174,232,16,239,203,168,67,4,190,200,68,163,63,140,27,142,10,25,65,227,92,199,166,33,30,92,73,221,145,174,220,55,82,34]}');
// NACL Box keypair
Export.BoxkeyPair = Nacl.box.keyPair;
// NACL Signature keypair
Export.SignkeyPair = Nacl.sign.keyPair;
// Utils
function ArrayToUint8(data) {
if (Array.isArray(data)) {
var ret = new Uint8Array(data.length);
ret.set(data);
return ret;
} else if (data instanceof Uint8Array) {
return data
} else {
console.log('invalid ArrayToUint8:'+JSON.stringify(data));
return null;
}
}
function Uint8ToArray(data) {
if (Array.isArray(data)) {
return data;
} else if (data instanceof Uint8Array) {
return Array.prototype.slice.call(data);
} else {
console.log('invalid Uint8ToArray:'+JSON.stringify(data));
return null;
}
}
function isNodeJS() {
return (typeof module != 'undefined' && typeof window === 'undefined');
}
Export.ArrayToUint8 = ArrayToUint8;
Export.Uint8ToArray = Uint8ToArray;
})(typeof module !== 'undefined' ? module.exports :(window.naclcert = window.naclcert || {}),
typeof require !== 'undefined' ? require('tweetnacl/nacl-fast.js') : window.nacl,
typeof require !== 'undefined' ? require('node-uuid') : window.uuid);