From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mailnews/test/fakeserver/Auth.jsm | 209 +++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 comm/mailnews/test/fakeserver/Auth.jsm (limited to 'comm/mailnews/test/fakeserver/Auth.jsm') diff --git a/comm/mailnews/test/fakeserver/Auth.jsm b/comm/mailnews/test/fakeserver/Auth.jsm new file mode 100644 index 0000000000..4bd240b509 --- /dev/null +++ b/comm/mailnews/test/fakeserver/Auth.jsm @@ -0,0 +1,209 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file implements the authentication mechanisms + * - AUTH LOGIN + * - AUTH PLAIN + * - AUTH CRAM-MD5 + * for all the server implementations, i.e. in a generic way. + * In fact, you could use this to implement a real server in JS :-) . + * + * @author Ben Bucksch + */ + +var EXPORTED_SYMBOLS = ["AuthPLAIN", "AuthLOGIN", "AuthCRAM"]; + +/** + * Implements AUTH PLAIN + * + * @see RFC 4616 + */ +var AuthPLAIN = { + /** + * Takes full PLAIN auth line, and decodes it. + * + * @param line {string} + * @returns {Object { username : value, password : value } } + * @throws {string} error to return to client + */ + decodeLine(line) { + dump("AUTH PLAIN line -" + line + "-\n"); + line = atob(line); // base64 decode + let aap = line.split("\u0000"); // 0-charater is delimiter + if (aap.length != 3) { + throw new Error("Expected three parts"); + } + /* aap is: authorize-id, authenticate-id, password. + Generally, authorize-id = authenticate-id = username. + authorize-id may thus be empty and then defaults to authenticate-id. */ + var result = {}; + var authzid = aap[0]; + result.username = aap[1]; + result.password = aap[2]; + dump( + "authorize-id: -" + + authzid + + "-, username: -" + + result.username + + "-, password: -" + + result.password + + "-\n" + ); + if (authzid && authzid != result.username) { + throw new Error( + "Expecting a authorize-id that's either the same as authenticate-id or empty" + ); + } + return result; + }, + + /** + * Create an AUTH PLAIN line, to allow a client to authenticate to a server. + * Useful for tests. + */ + encodeLine(username, password) { + username = username.substring(0, 255); + password = password.substring(0, 255); + return btoa("\u0000" + username + "\u0000" + password); // base64 encode + }, +}; + +var AuthLOGIN = { + /** + * Takes full LOGIN auth line, and decodes it. + * It may contain either username or password, + * depending on state/step (first username, then pw). + * + * @param line {string} + * @returns {string} username or password + * @throws {string} error to return to client + */ + decodeLine(line) { + dump("AUTH LOGIN -" + atob(line) + "-\n"); + return atob(line); // base64 decode + }, +}; + +/** + * Implements AUTH CRAM-MD5 + * + * @see RFC 2195, RFC 2104 + */ +var AuthCRAM = { + /** + * First part of CRAM exchange is that the server sends + * a challenge to the client. The client response depends on + * the challenge. (This prevents replay attacks, I think.) + * This function generates the challenge. + * + * You need to store it, you'll need it to check the client response. + * + * @param domain {string} - Your hostname or domain, + * e.g. "example.com", "mx.example.com" or just "localhost". + * @returns {string} The challenge. + * It's already base64-encoded. Send it as-is to the client. + */ + createChallenge(domain) { + var timestamp = new Date().getTime(); // unixtime + var challenge = "<" + timestamp + "@" + domain + ">"; + dump("CRAM challenge unencoded: " + challenge + "\n"); + return btoa(challenge); + }, + /** + * Takes full CRAM-MD5 auth line, and decodes it. + * + * Compare the returned |digest| to the result of + * encodeCRAMMD5(). If they match, the |username| + * returned here is authenticated. + * + * @param line {string} + * @returns {Object { username : value, digest : value } } + * @throws {string} error to return to client + */ + decodeLine(line) { + dump("AUTH CRAM-MD5 line -" + line + "-\n"); + line = atob(line); + dump("base64 decoded -" + line + "-\n"); + var sp = line.split(" "); + if (sp.length != 2) { + throw new Error("Expected one space"); + } + var result = {}; + result.username = sp[0]; + result.digest = sp[1]; + return result; + }, + /** + * @param text {string} - server challenge (base64-encoded) + * @param key {string} - user's password + * @returns {string} digest as hex string + */ + encodeCRAMMD5(text, key) { + text = atob(text); // createChallenge() returns it already encoded + dump("encodeCRAMMD5(text: -" + text + "-, key: -" + key + "-)\n"); + const kInputLen = 64; + // const kHashLen = 16; + const kInnerPad = 0x36; // per spec + const kOuterPad = 0x5c; + + key = this.textToNumberArray(key); + text = this.textToNumberArray(text); + // Make sure key is exactly kDigestLen bytes long. Algo per spec. + if (key.length > kInputLen) { + // (results in kHashLen) + key = this.md5(key); + } + while (key.length < kInputLen) { + // Fill up with zeros. + key.push(0); + } + + // MD5((key XOR outerpad) + MD5((key XOR innerpad) + text)) , per spec + var digest = this.md5( + this.xor(key, kOuterPad).concat( + this.md5(this.xor(key, kInnerPad).concat(text)) + ) + ); + return this.arrayToHexString(digest); + }, + // Utils + xor(binary, value) { + var result = []; + for (var i = 0; i < binary.length; i++) { + result.push(binary[i] ^ value); + } + return result; + }, + md5(binary) { + var md5 = Cc["@mozilla.org/security/hash;1"].createInstance( + Ci.nsICryptoHash + ); + md5.init(Ci.nsICryptoHash.MD5); + md5.update(binary, binary.length); + return this.textToNumberArray(md5.finish(false)); + }, + textToNumberArray(text) { + var array = []; + for (var i = 0; i < text.length; i++) { + // Convert string (only lower byte) to array. + array.push(text.charCodeAt(i) & 0xff); + } + return array; + }, + arrayToHexString(binary) { + var result = ""; + for (var i = 0; i < binary.length; i++) { + if (binary[i] > 255) { + throw new Error("unexpected that value > 255"); + } + let hex = binary[i].toString(16); + if (hex.length < 2) { + hex = "0" + hex; + } + result += hex; + } + return result; + }, +}; -- cgit v1.2.3