// Use strict "use strict"; // Classes // Tor class class Tor { // Public // Initialize static initialize() { // Set browser support Tor to true if is an Onion Service or unknown otherwise Tor.browserSupportsTor = (Tor.isOnionService() === true) ? true : Tor.SUPPORT_UNKNOWN; // Return promise return new Promise(function(resolve, reject) { // Return checking if browser supports Tor return Tor.checkIfBrowserSupportsTor().then(function() { // Check if browser's Tor support is unknown if(Tor.browserSupportsTor === Tor.SUPPORT_UNKNOWN) { // Window online event $(window).on("online", function() { // Check if browser supports Tor Tor.checkIfBrowserSupportsTor(); }); } // Resolve resolve(); }); }); } // Is supported static isSupported() { // Return if browser supports Tor return Tor.browserSupportsTor === true; } // Is Tor URL static isTorUrl(url) { // Try try { // Parse URL var parsedUrl = new URL(url); } // Catch errors catch(error) { // Return false return false; } // Return if address is a Tor URL return Tor.URL_PATTERN.test(parsedUrl["protocol"] + "//" + parsedUrl["hostname"]) === true; } // Get Tor address from URL static getTorAddressFromUrl(url) { // Trim url url = url.trim(); // Try try { // Parse URL with a protocol and top-level domain added if needed var parsedUrl = new URL(((Common.urlContainsProtocol(url) === false) ? Common.HTTP_PROTOCOL + "//" : "") + url + ((Common.urlContainsProtocol(url) === false && Common.urlContainsTopLevelDomain(url) === false) ? Tor.URL_TOP_LEVEL_DOMAIN : "")); } // Catch errors catch(error) { // Throw error throw "Invalid URL."; } // Check if parsed URL is a Tor URL if(Tor.URL_PATTERN.test(parsedUrl["protocol"] + "//" + parsedUrl["hostname"]) === false) { // Throw error throw "Invalid URL."; } // Set Tor address to the URL without its subdomain and top-level domain var torAddress = Common.removeTopLevelDomain(Common.removeSubdomain(parsedUrl["hostname"])); // Try try { // Get public key from the Tor address Tor.torAddressToPublicKey(torAddress); } // Catch errors catch(error) { // Throw error throw "Invalid URL."; } // Return Tor address return torAddress; } // Is Onion Service static isOnionService() { // Check if is extension or loading from file if(Common.isExtension() === true || location["protocol"] === Common.FILE_PROTOCOL) { // Return false return false; } // Return if site is an Onion Service return Tor.isTorUrl(location["protocol"] + "//" + location["hostname"]) === true; } // Public key to Tor address static publicKeyToTorAddress(publicKey) { // Check it public key isn't the correct length if(publicKey["length"] !== Crypto.ED25519_PUBLIC_KEY_LENGTH) { // Throw error throw "Invalid public key."; } // Get checksum of seed, address public key, and version var checksum = new Uint8Array(sha3_256.arrayBuffer(Common.mergeArrays([ // Seed (new TextEncoder()).encode(Tor.ADDRESS_CHECKSUM_SEED), // Public key publicKey, // Version new Uint8Array([Tor.ADDRESS_VERSION]) ]))); // Get Tor address from address public key, checksum, and version var torAddress = base32.encode(Common.mergeArrays([ // Public key publicKey, // Checksum checksum.subarray(0, Tor.ADDRESS_CHECKSUM_LENGTH), // Version new Uint8Array([Tor.ADDRESS_VERSION]) ])).toLowerCase(); // Return Tor address return torAddress; } // Tor address to public key static torAddressToPublicKey(torAddress) { // Check it Tor address isn't the correct length if(torAddress["length"] !== Tor.ADDRESS_LENGTH) { // Throw error throw "Invalid Tor address."; } // Check it Tor address isn't lower case if(Common.isLowercaseString(torAddress) === false) { // Throw error throw "Invalid Tor address."; } // Try try { // Decode Tor address var decodedAddress = new Uint8Array(base32.decode.asBytes(torAddress.toUpperCase())); } // Catch errors catch(error) { // Throw error throw "Invalid Tor address."; } // Check if decoded address's length is invalid if(decodedAddress["length"] !== Crypto.ED25519_PUBLIC_KEY_LENGTH + Tor.ADDRESS_CHECKSUM_LENGTH + [Tor.ADDRESS_VERSION]["length"]) { // Throw error throw "Invalid Tor address."; } // Get checksum of seed, decoded address, and version var checksum = new Uint8Array(sha3_256.arrayBuffer(Common.mergeArrays([ // Seed (new TextEncoder()).encode(Tor.ADDRESS_CHECKSUM_SEED), // Decoded address decodedAddress.subarray(0, Crypto.ED25519_PUBLIC_KEY_LENGTH), // Version new Uint8Array([Tor.ADDRESS_VERSION]) ]))); // Check if decoded address's checksum doesn't match the expected value if(Common.arraysAreEqual(checksum.subarray(0, Tor.ADDRESS_CHECKSUM_LENGTH), decodedAddress.subarray(Crypto.ED25519_PUBLIC_KEY_LENGTH, Crypto.ED25519_PUBLIC_KEY_LENGTH + Tor.ADDRESS_CHECKSUM_LENGTH)) === false) { // Throw error throw "Invalid Tor address."; } // Check if decoded address's version doesn't match the expected value if(decodedAddress[Crypto.ED25519_PUBLIC_KEY_LENGTH + Tor.ADDRESS_CHECKSUM_LENGTH] !== Tor.ADDRESS_VERSION) { // Throw error throw "Invalid Tor address."; } // Return decoded address without the checksum and version return decodedAddress.subarray(0, Crypto.ED25519_PUBLIC_KEY_LENGTH); } // Address length static get ADDRESS_LENGTH() { // Return address length return 56; } // URL top-level domain static get URL_TOP_LEVEL_DOMAIN() { // Return URL top-level domain return ".onion"; } // Private // Check if browser supports Tor static checkIfBrowserSupportsTor() { // Return promise return new Promise(function(resolve, reject) { // Check if online and browser's Tor support is unknown if(navigator["onLine"] === true && Tor.browserSupportsTor === Tor.SUPPORT_UNKNOWN) { // Return getting testing connection to Tor site return $.get(TOR_SERVER_ADDRESS + Tor.CONNECTION_TEST_URL).then(function() { // Set browser supports Tor Tor.browserSupportsTor = true; // Resolve resolve(); // Catch errors }).catch(function(error) { // Clear browser supports Tor Tor.browserSupportsTor = false; // Resolve resolve(); }); } // Otherwise else { // Resolve resolve(); } }); } // Support unknown static get SUPPORT_UNKNOWN() { // Return support unknown return null; } // Connection test URL static get CONNECTION_TEST_URL() { // Return connection test URL return getResource("./connection_test.html").substring("."["length"]); } // URL pattern static get URL_PATTERN() { // Return URL pattern return /^[^:]+:\/\/.+\.onion$/ui; } // Address checksum seed static get ADDRESS_CHECKSUM_SEED() { // Return address checksum seed return ".onion checksum"; } // Address version static get ADDRESS_VERSION() { // Return address version return 3; } // Address checksum length static get ADDRESS_CHECKSUM_LENGTH() { // Return address checksum length return 2; } } // Main function // Set global object's Tor globalThis["Tor"] = Tor;