Files
grin-web-wallet/scripts/tor.js
2024-12-20 18:08:44 -08:00

343 lines
7.7 KiB
JavaScript
Executable File

// 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;