1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | 'use strict';
|
15 |
|
16 | const assert = require('bsert');
|
17 | const base32 = require('bs32');
|
18 | const onion = exports;
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | onion.isString = function isString(str) {
|
27 | return onion.isLegacyString(str) || onion.isNGString(str);
|
28 | };
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | onion.is = function is(raw) {
|
37 | return onion.isLegacy(raw) || onion.isNG(raw);
|
38 | };
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | onion.encode = function encode(raw, sha3) {
|
48 | if (onion.isLegacy(raw))
|
49 | return onion.encodeLegacy(raw);
|
50 |
|
51 | if (onion.isNG(raw))
|
52 | return onion.encodeNG(raw, sha3);
|
53 |
|
54 | throw new Error('Not an onion buffer.');
|
55 | };
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | onion.decode = function decode(str, sha3) {
|
65 | if (onion.isLegacyString(str))
|
66 | return onion.decodeLegacy(str);
|
67 |
|
68 | if (onion.isNGString(str))
|
69 | return onion.decodeNG(str, sha3);
|
70 |
|
71 | throw new Error('Not an onion string.');
|
72 | };
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 | onion.normalize = function normalize(str, sha3) {
|
82 | if (onion.isLegacyString(str))
|
83 | return onion.normalizeLegacy(str);
|
84 |
|
85 | if (onion.isNGString(str))
|
86 | return onion.normalizeNG(str, sha3);
|
87 |
|
88 | throw new Error('Not an onion string.');
|
89 | };
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | onion.isLegacyString = function isLegacyString(str) {
|
98 | assert(typeof str === 'string');
|
99 |
|
100 | if (str.length !== 16 + 6)
|
101 | return false;
|
102 |
|
103 | return str.slice(-6).toLowerCase() === '.onion';
|
104 | };
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 | onion.isLegacy = function isLegacy(raw) {
|
113 | assert(Buffer.isBuffer(raw));
|
114 | return raw.length === 10;
|
115 | };
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | onion.encodeLegacy = function encodeLegacy(raw) {
|
124 | assert(onion.isLegacy(raw));
|
125 | const host = base32.encode(raw);
|
126 | return `${host}.onion`;
|
127 | };
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 | onion.decodeLegacy = function decodeLegacy(str) {
|
136 | assert(onion.isLegacyString(str));
|
137 | const data = base32.decode(str.slice(0, -6));
|
138 | assert(data.length === 10, 'Invalid onion address.');
|
139 | return data;
|
140 | };
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | onion.normalizeLegacy = function normalizeLegacy(str) {
|
149 | return onion.encodeLegacy(onion.decodeLegacy(str));
|
150 | };
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 | onion.isNGString = function isNGString(str) {
|
159 | assert(typeof str === 'string');
|
160 |
|
161 | if (str.length !== 56 + 6)
|
162 | return false;
|
163 |
|
164 | return str.slice(-6).toLowerCase() === '.onion';
|
165 | };
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 | onion.isNG = function isNG(raw) {
|
175 | assert(Buffer.isBuffer(raw));
|
176 | return raw.length === 33;
|
177 | };
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 | onion.encodeNG = function encodeNG(key, sha3) {
|
189 | assert(Buffer.isBuffer(key));
|
190 | assert(key.length === 33);
|
191 |
|
192 |
|
193 | const data = Buffer.alloc(32 + 2 + 1);
|
194 |
|
195 |
|
196 | key.copy(data, 0, 1, 33);
|
197 |
|
198 |
|
199 | const chk = checksum(key, sha3);
|
200 | data[32] = chk >>> 8;
|
201 | data[33] = chk & 0xff;
|
202 |
|
203 |
|
204 | data[34] = key[0];
|
205 |
|
206 | const host = base32.encode(data);
|
207 |
|
208 | return `${host}.onion`;
|
209 | };
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 | onion.decodeNG = function decodeNG(str, sha3) {
|
221 |
|
222 | assert(onion.isNGString(str), 'Invalid onion address.');
|
223 |
|
224 | const data = base32.decode(str.slice(0, -6));
|
225 | assert(data.length === 35, 'Invalid onion address.');
|
226 |
|
227 |
|
228 | const key = Buffer.alloc(1 + 32);
|
229 |
|
230 |
|
231 | key[0] = data[34];
|
232 |
|
233 |
|
234 | data.copy(key, 1, 0, 32);
|
235 |
|
236 |
|
237 | assert(verify(key, data, sha3), 'Invalid checksum for onion address.');
|
238 |
|
239 | return key;
|
240 | };
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 | onion.normalizeNG = function normalizeNG(str, sha3) {
|
250 | return onion.encodeNG(onion.decodeNG(str, sha3), sha3);
|
251 | };
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 | function checksum(key, sha3) {
|
258 | assert(Buffer.isBuffer(key));
|
259 | assert(key.length === 33);
|
260 |
|
261 | if (sha3 == null)
|
262 | return 0;
|
263 |
|
264 | assert(typeof sha3 === 'function');
|
265 |
|
266 |
|
267 | const buf = Buffer.alloc(15 + 32 + 1);
|
268 | buf.write('.onion checksum', 0, 15, 'ascii');
|
269 | key.copy(buf, 15, 1, 33);
|
270 | buf[47] = key[0];
|
271 |
|
272 | return sha3(buf).readUInt16BE(0);
|
273 | }
|
274 |
|
275 | function verify(key, data, sha3) {
|
276 | assert(Buffer.isBuffer(data));
|
277 | assert(data.length === 35);
|
278 |
|
279 | if (sha3 == null)
|
280 | return true;
|
281 |
|
282 | const chk = data.readUInt16BE(32);
|
283 | return chk === checksum(key, sha3);
|
284 | }
|