1import { encodeUTF8 } from './util/strings.js';
2import EventTargetMixin from './util/eventtarget.js';
3import legacyCrypto from './crypto/crypto.js';
8 this._counter = new Uint8Array(16);
12 this._cipher = await legacyCrypto.importKey(
13 "raw", key, { name: "AES-EAX" }, false, ["encrypt, decrypt"]);
16 async makeMessage(message) {
17 const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
18 const encrypted = await legacyCrypto.encrypt({
22 }, this._cipher, message);
23 for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
24 const res = new Uint8Array(message.length + 2 + 16);
26 res.set(encrypted, 2);
30 async receiveMessage(length, encrypted) {
31 const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
32 const res = await legacyCrypto.decrypt({
36 }, this._cipher, encrypted);
37 for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
42export default class RSAAESAuthenticationState extends EventTargetMixin {
43 constructor(sock, getCredentials) {
45 this._hasStarted = false;
46 this._checkSock = null;
47 this._checkCredentials = null;
48 this._approveServerResolve = null;
49 this._sockReject = null;
50 this._credentialsReject = null;
51 this._approveServerReject = null;
53 this._getCredentials = getCredentials;
57 return new Promise((resolve, reject) => {
58 const hasData = () => !this._sock.rQwait('RA2', len);
62 this._checkSock = () => {
65 this._checkSock = null;
66 this._sockReject = null;
69 this._sockReject = reject;
74 _waitApproveKeyAsync() {
75 return new Promise((resolve, reject) => {
76 this._approveServerResolve = resolve;
77 this._approveServerReject = reject;
81 _waitCredentialsAsync(subtype) {
82 const hasCredentials = () => {
83 if (subtype === 1 && this._getCredentials().username !== undefined &&
84 this._getCredentials().password !== undefined) {
86 } else if (subtype === 2 && this._getCredentials().password !== undefined) {
91 return new Promise((resolve, reject) => {
92 if (hasCredentials()) {
95 this._checkCredentials = () => {
96 if (hasCredentials()) {
98 this._checkCredentials = null;
99 this._credentialsReject = null;
102 this._credentialsReject = reject;
107 checkInternalEvents() {
108 if (this._checkSock !== null) {
111 if (this._checkCredentials !== null) {
112 this._checkCredentials();
117 if (this._approveServerResolve !== null) {
118 this._approveServerResolve();
119 this._approveServerResolve = null;
124 if (this._sockReject !== null) {
125 this._sockReject(new Error("disconnect normally"));
126 this._sockReject = null;
128 if (this._credentialsReject !== null) {
129 this._credentialsReject(new Error("disconnect normally"));
130 this._credentialsReject = null;
132 if (this._approveServerReject !== null) {
133 this._approveServerReject(new Error("disconnect normally"));
134 this._approveServerReject = null;
138 async negotiateRA2neAuthAsync() {
139 this._hasStarted = true;
140 // 1: Receive server public key
141 await this._waitSockAsync(4);
142 const serverKeyLengthBuffer = this._sock.rQpeekBytes(4);
143 const serverKeyLength = this._sock.rQshift32();
144 if (serverKeyLength < 1024) {
145 throw new Error("RA2: server public key is too short: " + serverKeyLength);
146 } else if (serverKeyLength > 8192) {
147 throw new Error("RA2: server public key is too long: " + serverKeyLength);
149 const serverKeyBytes = Math.ceil(serverKeyLength / 8);
150 await this._waitSockAsync(serverKeyBytes * 2);
151 const serverN = this._sock.rQshiftBytes(serverKeyBytes);
152 const serverE = this._sock.rQshiftBytes(serverKeyBytes);
153 const serverRSACipher = await legacyCrypto.importKey(
154 "raw", { n: serverN, e: serverE }, { name: "RSA-PKCS1-v1_5" }, false, ["encrypt"]);
155 const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
156 serverPublickey.set(serverKeyLengthBuffer);
157 serverPublickey.set(serverN, 4);
158 serverPublickey.set(serverE, 4 + serverKeyBytes);
160 // verify server public key
161 let approveKey = this._waitApproveKeyAsync();
162 this.dispatchEvent(new CustomEvent("serververification", {
163 detail: { type: "RSA", publickey: serverPublickey }
167 // 2: Send client public key
168 const clientKeyLength = 2048;
169 const clientKeyBytes = Math.ceil(clientKeyLength / 8);
170 const clientRSACipher = (await legacyCrypto.generateKey({
171 name: "RSA-PKCS1-v1_5",
172 modulusLength: clientKeyLength,
173 publicExponent: new Uint8Array([1, 0, 1]),
174 }, true, ["encrypt"])).privateKey;
175 const clientExportedRSAKey = await legacyCrypto.exportKey("raw", clientRSACipher);
176 const clientN = clientExportedRSAKey.n;
177 const clientE = clientExportedRSAKey.e;
178 const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);
179 clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;
180 clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;
181 clientPublicKey[2] = (clientKeyLength & 0xff00) >>> 8;
182 clientPublicKey[3] = clientKeyLength & 0xff;
183 clientPublicKey.set(clientN, 4);
184 clientPublicKey.set(clientE, 4 + clientKeyBytes);
185 this._sock.sQpushBytes(clientPublicKey);
188 // 3: Send client random
189 const clientRandom = new Uint8Array(16);
190 window.crypto.getRandomValues(clientRandom);
191 const clientEncryptedRandom = await legacyCrypto.encrypt(
192 { name: "RSA-PKCS1-v1_5" }, serverRSACipher, clientRandom);
193 const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
194 clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
195 clientRandomMessage[1] = serverKeyBytes & 0xff;
196 clientRandomMessage.set(clientEncryptedRandom, 2);
197 this._sock.sQpushBytes(clientRandomMessage);
200 // 4: Receive server random
201 await this._waitSockAsync(2);
202 if (this._sock.rQshift16() !== clientKeyBytes) {
203 throw new Error("RA2: wrong encrypted message length");
205 const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);
206 const serverRandom = await legacyCrypto.decrypt(
207 { name: "RSA-PKCS1-v1_5" }, clientRSACipher, serverEncryptedRandom);
208 if (serverRandom === null || serverRandom.length !== 16) {
209 throw new Error("RA2: corrupted server encrypted random");
212 // 5: Compute session keys and set ciphers
213 let clientSessionKey = new Uint8Array(32);
214 let serverSessionKey = new Uint8Array(32);
215 clientSessionKey.set(serverRandom);
216 clientSessionKey.set(clientRandom, 16);
217 serverSessionKey.set(clientRandom);
218 serverSessionKey.set(serverRandom, 16);
219 clientSessionKey = await window.crypto.subtle.digest("SHA-1", clientSessionKey);
220 clientSessionKey = new Uint8Array(clientSessionKey).slice(0, 16);
221 serverSessionKey = await window.crypto.subtle.digest("SHA-1", serverSessionKey);
222 serverSessionKey = new Uint8Array(serverSessionKey).slice(0, 16);
223 const clientCipher = new RA2Cipher();
224 await clientCipher.setKey(clientSessionKey);
225 const serverCipher = new RA2Cipher();
226 await serverCipher.setKey(serverSessionKey);
228 // 6: Compute and exchange hashes
229 let serverHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);
230 let clientHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);
231 serverHash.set(serverPublickey);
232 serverHash.set(clientPublicKey, 4 + serverKeyBytes * 2);
233 clientHash.set(clientPublicKey);
234 clientHash.set(serverPublickey, 4 + clientKeyBytes * 2);
235 serverHash = await window.crypto.subtle.digest("SHA-1", serverHash);
236 clientHash = await window.crypto.subtle.digest("SHA-1", clientHash);
237 serverHash = new Uint8Array(serverHash);
238 clientHash = new Uint8Array(clientHash);
239 this._sock.sQpushBytes(await clientCipher.makeMessage(clientHash));
241 await this._waitSockAsync(2 + 20 + 16);
242 if (this._sock.rQshift16() !== 20) {
243 throw new Error("RA2: wrong server hash");
245 const serverHashReceived = await serverCipher.receiveMessage(
246 20, this._sock.rQshiftBytes(20 + 16));
247 if (serverHashReceived === null) {
248 throw new Error("RA2: failed to authenticate the message");
250 for (let i = 0; i < 20; i++) {
251 if (serverHashReceived[i] !== serverHash[i]) {
252 throw new Error("RA2: wrong server hash");
256 // 7: Receive subtype
257 await this._waitSockAsync(2 + 1 + 16);
258 if (this._sock.rQshift16() !== 1) {
259 throw new Error("RA2: wrong subtype");
261 let subtype = (await serverCipher.receiveMessage(
262 1, this._sock.rQshiftBytes(1 + 16)));
263 if (subtype === null) {
264 throw new Error("RA2: failed to authenticate the message");
266 subtype = subtype[0];
267 let waitCredentials = this._waitCredentialsAsync(subtype);
269 if (this._getCredentials().username === undefined ||
270 this._getCredentials().password === undefined) {
271 this.dispatchEvent(new CustomEvent(
272 "credentialsrequired",
273 { detail: { types: ["username", "password"] } }));
275 } else if (subtype === 2) {
276 if (this._getCredentials().password === undefined) {
277 this.dispatchEvent(new CustomEvent(
278 "credentialsrequired",
279 { detail: { types: ["password"] } }));
282 throw new Error("RA2: wrong subtype");
284 await waitCredentials;
287 username = encodeUTF8(this._getCredentials().username).slice(0, 255);
291 const password = encodeUTF8(this._getCredentials().password).slice(0, 255);
292 const credentials = new Uint8Array(username.length + password.length + 2);
293 credentials[0] = username.length;
294 credentials[username.length + 1] = password.length;
295 for (let i = 0; i < username.length; i++) {
296 credentials[i + 1] = username.charCodeAt(i);
298 for (let i = 0; i < password.length; i++) {
299 credentials[username.length + 2 + i] = password.charCodeAt(i);
301 this._sock.sQpushBytes(await clientCipher.makeMessage(credentials));
306 return this._hasStarted;
310 this._hasStarted = s;