JavaScript 65.5%
Python 17.8%
CSS 13%
HTML 3.7%
1import { encodeUTF8 } from './util/strings.js';2import EventTargetMixin from './util/eventtarget.js';3import legacyCrypto from './crypto/crypto.js';45class RA2Cipher {6 constructor() {7 this._cipher = null;8 this._counter = new Uint8Array(16);9 }1011 async setKey(key) {12 this._cipher = await legacyCrypto.importKey(13 "raw", key, { name: "AES-EAX" }, false, ["encrypt, decrypt"]);14 }1516 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 }2930 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}4142export 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 }5556 _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 }7374 _waitApproveKeyAsync() {75 return new Promise((resolve, reject) => {76 this._approveServerResolve = resolve;77 this._approveServerReject = reject;78 });79 }8081 _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 }106107 checkInternalEvents() {108 if (this._checkSock !== null) {109 this._checkSock();110 }111 if (this._checkCredentials !== null) {112 this._checkCredentials();113 }114 }115116 approveServer() {117 if (this._approveServerResolve !== null) {118 this._approveServerResolve();119 this._approveServerResolve = null;120 }121 }122123 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 }137138 async negotiateRA2neAuthAsync() {139 this._hasStarted = true;140 // 1: Receive server public key141 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);159160 // verify server public key161 let approveKey = this._waitApproveKeyAsync();162 this.dispatchEvent(new CustomEvent("serververification", {163 detail: { type: "RSA", publickey: serverPublickey }164 }));165 await approveKey;166167 // 2: Send client public key168 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();187188 // 3: Send client random189 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();199200 // 4: Receive server random201 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 }211212 // 5: Compute session keys and set ciphers213 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);227228 // 6: Compute and exchange hashes229 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 }255256 // 7: Receive subtype257 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 }304305 get hasStarted() {306 return this._hasStarted;307 }308309 set hasStarted(s) {310 this._hasStarted = s;311 }312}313