main
Raw Download raw file
  1import { encodeUTF8 } from './util/strings.js';
  2import EventTargetMixin from './util/eventtarget.js';
  3import legacyCrypto from './crypto/crypto.js';
  4
  5class RA2Cipher {
  6    constructor() {
  7        this._cipher = null;
  8        this._counter = new Uint8Array(16);
  9    }
 10
 11    async setKey(key) {
 12        this._cipher = await legacyCrypto.importKey(
 13            "raw", key, { name: "AES-EAX" }, false, ["encrypt, decrypt"]);
 14    }
 15
 16    async makeMessage(message) {
 17        const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
 18        const encrypted = await legacyCrypto.encrypt({
 19            name: "AES-EAX",
 20            iv: this._counter,
 21            additionalData: ad,
 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);
 25        res.set(ad);
 26        res.set(encrypted, 2);
 27        return res;
 28    }
 29
 30    async receiveMessage(length, encrypted) {
 31        const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
 32        const res = await legacyCrypto.decrypt({
 33            name: "AES-EAX",
 34            iv: this._counter,
 35            additionalData: ad,
 36        }, this._cipher, encrypted);
 37        for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
 38        return res;
 39    }
 40}
 41
 42export default class RSAAESAuthenticationState extends EventTargetMixin {
 43    constructor(sock, getCredentials) {
 44        super();
 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;
 52        this._sock = sock;
 53        this._getCredentials = getCredentials;
 54    }
 55
 56    _waitSockAsync(len) {
 57        return new Promise((resolve, reject) => {
 58            const hasData = () => !this._sock.rQwait('RA2', len);
 59            if (hasData()) {
 60                resolve();
 61            } else {
 62                this._checkSock = () => {
 63                    if (hasData()) {
 64                        resolve();
 65                        this._checkSock = null;
 66                        this._sockReject = null;
 67                    }
 68                };
 69                this._sockReject = reject;
 70            }
 71        });
 72    }
 73
 74    _waitApproveKeyAsync() {
 75        return new Promise((resolve, reject) => {
 76            this._approveServerResolve = resolve;
 77            this._approveServerReject = reject;
 78        });
 79    }
 80
 81    _waitCredentialsAsync(subtype) {
 82        const hasCredentials = () => {
 83            if (subtype === 1 && this._getCredentials().username !== undefined &&
 84                this._getCredentials().password !== undefined) {
 85                return true;
 86            } else if (subtype === 2 && this._getCredentials().password !== undefined) {
 87                return true;
 88            }
 89            return false;
 90        };
 91        return new Promise((resolve, reject) => {
 92            if (hasCredentials()) {
 93                resolve();
 94            } else {
 95                this._checkCredentials = () => {
 96                    if (hasCredentials()) {
 97                        resolve();
 98                        this._checkCredentials = null;
 99                        this._credentialsReject = null;
100                    }
101                };
102                this._credentialsReject = reject;
103            }
104        });
105    }
106
107    checkInternalEvents() {
108        if (this._checkSock !== null) {
109            this._checkSock();
110        }
111        if (this._checkCredentials !== null) {
112            this._checkCredentials();
113        }
114    }
115
116    approveServer() {
117        if (this._approveServerResolve !== null) {
118            this._approveServerResolve();
119            this._approveServerResolve = null;
120        }
121    }
122
123    disconnect() {
124        if (this._sockReject !== null) {
125            this._sockReject(new Error("disconnect normally"));
126            this._sockReject = null;
127        }
128        if (this._credentialsReject !== null) {
129            this._credentialsReject(new Error("disconnect normally"));
130            this._credentialsReject = null;
131        }
132        if (this._approveServerReject !== null) {
133            this._approveServerReject(new Error("disconnect normally"));
134            this._approveServerReject = null;
135        }
136    }
137
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);
148        }
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);
159
160        // verify server public key
161        let approveKey = this._waitApproveKeyAsync();
162        this.dispatchEvent(new CustomEvent("serververification", {
163            detail: { type: "RSA", publickey: serverPublickey }
164        }));
165        await approveKey;
166
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);
186        this._sock.flush();
187
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);
198        this._sock.flush();
199
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");
204        }
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");
210        }
211
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);
227
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));
240        this._sock.flush();
241        await this._waitSockAsync(2 + 20 + 16);
242        if (this._sock.rQshift16() !== 20) {
243            throw new Error("RA2: wrong server hash");
244        }
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");
249        }
250        for (let i = 0; i < 20; i++) {
251            if (serverHashReceived[i] !== serverHash[i]) {
252                throw new Error("RA2: wrong server hash");
253            }
254        }
255
256        // 7: Receive subtype
257        await this._waitSockAsync(2 + 1 + 16);
258        if (this._sock.rQshift16() !== 1) {
259            throw new Error("RA2: wrong subtype");
260        }
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");
265        }
266        subtype = subtype[0];
267        let waitCredentials = this._waitCredentialsAsync(subtype);
268        if (subtype === 1) {
269            if (this._getCredentials().username === undefined ||
270                this._getCredentials().password === undefined) {
271                this.dispatchEvent(new CustomEvent(
272                    "credentialsrequired",
273                    { detail: { types: ["username", "password"] } }));
274            }
275        } else if (subtype === 2) {
276            if (this._getCredentials().password === undefined) {
277                this.dispatchEvent(new CustomEvent(
278                    "credentialsrequired",
279                    { detail: { types: ["password"] } }));
280            }
281        } else {
282            throw new Error("RA2: wrong subtype");
283        }
284        await waitCredentials;
285        let username;
286        if (subtype === 1) {
287            username = encodeUTF8(this._getCredentials().username).slice(0, 255);
288        } else {
289            username = "";
290        }
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);
297        }
298        for (let i = 0; i < password.length; i++) {
299            credentials[username.length + 2 + i] = password.charCodeAt(i);
300        }
301        this._sock.sQpushBytes(await clientCipher.makeMessage(credentials));
302        this._sock.flush();
303    }
304
305    get hasStarted() {
306        return this._hasStarted;
307    }
308
309    set hasStarted(s) {
310        this._hasStarted = s;
311    }
312}