| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312 |
- import { encodeUTF8 } from './util/strings.js';
- import EventTargetMixin from './util/eventtarget.js';
- import legacyCrypto from './crypto/crypto.js';
- class RA2Cipher {
- constructor() {
- this._cipher = null;
- this._counter = new Uint8Array(16);
- }
- async setKey(key) {
- this._cipher = await legacyCrypto.importKey(
- "raw", key, { name: "AES-EAX" }, false, ["encrypt, decrypt"]);
- }
- async makeMessage(message) {
- const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
- const encrypted = await legacyCrypto.encrypt({
- name: "AES-EAX",
- iv: this._counter,
- additionalData: ad,
- }, this._cipher, message);
- for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
- const res = new Uint8Array(message.length + 2 + 16);
- res.set(ad);
- res.set(encrypted, 2);
- return res;
- }
- async receiveMessage(length, encrypted) {
- const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
- const res = await legacyCrypto.decrypt({
- name: "AES-EAX",
- iv: this._counter,
- additionalData: ad,
- }, this._cipher, encrypted);
- for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
- return res;
- }
- }
- export default class RSAAESAuthenticationState extends EventTargetMixin {
- constructor(sock, getCredentials) {
- super();
- this._hasStarted = false;
- this._checkSock = null;
- this._checkCredentials = null;
- this._approveServerResolve = null;
- this._sockReject = null;
- this._credentialsReject = null;
- this._approveServerReject = null;
- this._sock = sock;
- this._getCredentials = getCredentials;
- }
- _waitSockAsync(len) {
- return new Promise((resolve, reject) => {
- const hasData = () => !this._sock.rQwait('RA2', len);
- if (hasData()) {
- resolve();
- } else {
- this._checkSock = () => {
- if (hasData()) {
- resolve();
- this._checkSock = null;
- this._sockReject = null;
- }
- };
- this._sockReject = reject;
- }
- });
- }
- _waitApproveKeyAsync() {
- return new Promise((resolve, reject) => {
- this._approveServerResolve = resolve;
- this._approveServerReject = reject;
- });
- }
- _waitCredentialsAsync(subtype) {
- const hasCredentials = () => {
- if (subtype === 1 && this._getCredentials().username !== undefined &&
- this._getCredentials().password !== undefined) {
- return true;
- } else if (subtype === 2 && this._getCredentials().password !== undefined) {
- return true;
- }
- return false;
- };
- return new Promise((resolve, reject) => {
- if (hasCredentials()) {
- resolve();
- } else {
- this._checkCredentials = () => {
- if (hasCredentials()) {
- resolve();
- this._checkCredentials = null;
- this._credentialsReject = null;
- }
- };
- this._credentialsReject = reject;
- }
- });
- }
- checkInternalEvents() {
- if (this._checkSock !== null) {
- this._checkSock();
- }
- if (this._checkCredentials !== null) {
- this._checkCredentials();
- }
- }
- approveServer() {
- if (this._approveServerResolve !== null) {
- this._approveServerResolve();
- this._approveServerResolve = null;
- }
- }
- disconnect() {
- if (this._sockReject !== null) {
- this._sockReject(new Error("disconnect normally"));
- this._sockReject = null;
- }
- if (this._credentialsReject !== null) {
- this._credentialsReject(new Error("disconnect normally"));
- this._credentialsReject = null;
- }
- if (this._approveServerReject !== null) {
- this._approveServerReject(new Error("disconnect normally"));
- this._approveServerReject = null;
- }
- }
- async negotiateRA2neAuthAsync() {
- this._hasStarted = true;
- // 1: Receive server public key
- await this._waitSockAsync(4);
- const serverKeyLengthBuffer = this._sock.rQpeekBytes(4);
- const serverKeyLength = this._sock.rQshift32();
- if (serverKeyLength < 1024) {
- throw new Error("RA2: server public key is too short: " + serverKeyLength);
- } else if (serverKeyLength > 8192) {
- throw new Error("RA2: server public key is too long: " + serverKeyLength);
- }
- const serverKeyBytes = Math.ceil(serverKeyLength / 8);
- await this._waitSockAsync(serverKeyBytes * 2);
- const serverN = this._sock.rQshiftBytes(serverKeyBytes);
- const serverE = this._sock.rQshiftBytes(serverKeyBytes);
- const serverRSACipher = await legacyCrypto.importKey(
- "raw", { n: serverN, e: serverE }, { name: "RSA-PKCS1-v1_5" }, false, ["encrypt"]);
- const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
- serverPublickey.set(serverKeyLengthBuffer);
- serverPublickey.set(serverN, 4);
- serverPublickey.set(serverE, 4 + serverKeyBytes);
- // verify server public key
- let approveKey = this._waitApproveKeyAsync();
- this.dispatchEvent(new CustomEvent("serververification", {
- detail: { type: "RSA", publickey: serverPublickey }
- }));
- await approveKey;
- // 2: Send client public key
- const clientKeyLength = 2048;
- const clientKeyBytes = Math.ceil(clientKeyLength / 8);
- const clientRSACipher = (await legacyCrypto.generateKey({
- name: "RSA-PKCS1-v1_5",
- modulusLength: clientKeyLength,
- publicExponent: new Uint8Array([1, 0, 1]),
- }, true, ["encrypt"])).privateKey;
- const clientExportedRSAKey = await legacyCrypto.exportKey("raw", clientRSACipher);
- const clientN = clientExportedRSAKey.n;
- const clientE = clientExportedRSAKey.e;
- const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);
- clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;
- clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;
- clientPublicKey[2] = (clientKeyLength & 0xff00) >>> 8;
- clientPublicKey[3] = clientKeyLength & 0xff;
- clientPublicKey.set(clientN, 4);
- clientPublicKey.set(clientE, 4 + clientKeyBytes);
- this._sock.sQpushBytes(clientPublicKey);
- this._sock.flush();
- // 3: Send client random
- const clientRandom = new Uint8Array(16);
- window.crypto.getRandomValues(clientRandom);
- const clientEncryptedRandom = await legacyCrypto.encrypt(
- { name: "RSA-PKCS1-v1_5" }, serverRSACipher, clientRandom);
- const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
- clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
- clientRandomMessage[1] = serverKeyBytes & 0xff;
- clientRandomMessage.set(clientEncryptedRandom, 2);
- this._sock.sQpushBytes(clientRandomMessage);
- this._sock.flush();
- // 4: Receive server random
- await this._waitSockAsync(2);
- if (this._sock.rQshift16() !== clientKeyBytes) {
- throw new Error("RA2: wrong encrypted message length");
- }
- const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);
- const serverRandom = await legacyCrypto.decrypt(
- { name: "RSA-PKCS1-v1_5" }, clientRSACipher, serverEncryptedRandom);
- if (serverRandom === null || serverRandom.length !== 16) {
- throw new Error("RA2: corrupted server encrypted random");
- }
- // 5: Compute session keys and set ciphers
- let clientSessionKey = new Uint8Array(32);
- let serverSessionKey = new Uint8Array(32);
- clientSessionKey.set(serverRandom);
- clientSessionKey.set(clientRandom, 16);
- serverSessionKey.set(clientRandom);
- serverSessionKey.set(serverRandom, 16);
- clientSessionKey = await window.crypto.subtle.digest("SHA-1", clientSessionKey);
- clientSessionKey = new Uint8Array(clientSessionKey).slice(0, 16);
- serverSessionKey = await window.crypto.subtle.digest("SHA-1", serverSessionKey);
- serverSessionKey = new Uint8Array(serverSessionKey).slice(0, 16);
- const clientCipher = new RA2Cipher();
- await clientCipher.setKey(clientSessionKey);
- const serverCipher = new RA2Cipher();
- await serverCipher.setKey(serverSessionKey);
- // 6: Compute and exchange hashes
- let serverHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);
- let clientHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);
- serverHash.set(serverPublickey);
- serverHash.set(clientPublicKey, 4 + serverKeyBytes * 2);
- clientHash.set(clientPublicKey);
- clientHash.set(serverPublickey, 4 + clientKeyBytes * 2);
- serverHash = await window.crypto.subtle.digest("SHA-1", serverHash);
- clientHash = await window.crypto.subtle.digest("SHA-1", clientHash);
- serverHash = new Uint8Array(serverHash);
- clientHash = new Uint8Array(clientHash);
- this._sock.sQpushBytes(await clientCipher.makeMessage(clientHash));
- this._sock.flush();
- await this._waitSockAsync(2 + 20 + 16);
- if (this._sock.rQshift16() !== 20) {
- throw new Error("RA2: wrong server hash");
- }
- const serverHashReceived = await serverCipher.receiveMessage(
- 20, this._sock.rQshiftBytes(20 + 16));
- if (serverHashReceived === null) {
- throw new Error("RA2: failed to authenticate the message");
- }
- for (let i = 0; i < 20; i++) {
- if (serverHashReceived[i] !== serverHash[i]) {
- throw new Error("RA2: wrong server hash");
- }
- }
- // 7: Receive subtype
- await this._waitSockAsync(2 + 1 + 16);
- if (this._sock.rQshift16() !== 1) {
- throw new Error("RA2: wrong subtype");
- }
- let subtype = (await serverCipher.receiveMessage(
- 1, this._sock.rQshiftBytes(1 + 16)));
- if (subtype === null) {
- throw new Error("RA2: failed to authenticate the message");
- }
- subtype = subtype[0];
- let waitCredentials = this._waitCredentialsAsync(subtype);
- if (subtype === 1) {
- if (this._getCredentials().username === undefined ||
- this._getCredentials().password === undefined) {
- this.dispatchEvent(new CustomEvent(
- "credentialsrequired",
- { detail: { types: ["username", "password"] } }));
- }
- } else if (subtype === 2) {
- if (this._getCredentials().password === undefined) {
- this.dispatchEvent(new CustomEvent(
- "credentialsrequired",
- { detail: { types: ["password"] } }));
- }
- } else {
- throw new Error("RA2: wrong subtype");
- }
- await waitCredentials;
- let username;
- if (subtype === 1) {
- username = encodeUTF8(this._getCredentials().username).slice(0, 255);
- } else {
- username = "";
- }
- const password = encodeUTF8(this._getCredentials().password).slice(0, 255);
- const credentials = new Uint8Array(username.length + password.length + 2);
- credentials[0] = username.length;
- credentials[username.length + 1] = password.length;
- for (let i = 0; i < username.length; i++) {
- credentials[i + 1] = username.charCodeAt(i);
- }
- for (let i = 0; i < password.length; i++) {
- credentials[username.length + 2 + i] = password.charCodeAt(i);
- }
- this._sock.sQpushBytes(await clientCipher.makeMessage(credentials));
- this._sock.flush();
- }
- get hasStarted() {
- return this._hasStarted;
- }
- set hasStarted(s) {
- this._hasStarted = s;
- }
- }
|