SPB Git forge

spb/admin-ka

Public
41commits 1branches 0releases
172.9 MBsize
maindefault branch
20 days agolast push
JavaScript 65.5% Python 17.8% CSS 13% HTML 3.7%
12.2 KB · 313 lines javascript
Raw Blame History
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