A Minimalistic and Privary-by-default URL sortener
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

186 lines
5.1 KiB

var enc = new TextEncoder("utf-8");
var dec = new TextDecoder("utf-8");
var userkey;
function computeKey(username, password) {
return new Promise(function(resolve, reject) {
window.crypto.subtle.importKey(
'raw',
password,
{name: 'PBKDF2'},
false,
['deriveBits', 'deriveKey']
).then(function(key) {
return window.crypto.subtle.deriveKey(
{name: 'PBKDF2', salt: username, iterations: 4096, hash: 'SHA-256'},
key,
{name: 'AES-GCM', length: 256},
true,
[ "encrypt", "decrypt" ]
);
}).then(function(aeskey) {
resolve(aeskey);
}).catch(function(error) {
reject(error);
});
});
}
function storeKey(aeskey) {
return new Promise(function(resolve, reject) {
window.crypto.subtle.exportKey('jwk', aeskey).then(function(jwk) {
window.localStorage['userkey'] = JSON.stringify(jwk);
resolve();
}).catch(function(error) {
reject(error);
});
});
}
function loadKey() {
return new Promise(function(resolve, reject) {
var jwk = JSON.parse(window.localStorage['userkey']);
window.crypto.subtle.importKey(
'jwk',
jwk,
{name: 'AES-GCM', length: 256},
true,
['encrypt', 'decrypt']
).then(function(aeskey) {
resolve(aeskey);
}).catch(function(error) {
reject(error);
});
});
}
function deleteKey() {
return new Promise(function(resolve, reject) {
window.localStorage.removeItem('userkey');
});
}
function encrypt(data, key) {
return new Promise(function(resolve, reject) {
const iv = crypto.getRandomValues(new Uint8Array(12));
window.crypto.subtle.encrypt({name: 'AES-GCM', iv: iv}, key, data).then(function(encr) {
resolve([
base64js.fromByteArray(new Uint8Array(encr)),
base64js.fromByteArray(iv)
]);
}).catch(function(error) {
reject(error);
});
});
}
function decrypt(edata, key) {
return new Promise(function(resolve, reject) {
const encr = base64js.toByteArray(edata[0]);
const iv = base64js.toByteArray(edata[1]);
window.crypto.subtle.decrypt({name: 'AES-GCM', iv: iv}, key, encr).then(function(decr) {
resolve(decr);
}).catch(function(error) {
reject(error);
});
});
}
function journalmarks_loadkey() {
return new Promise(function(resolve, reject) {
loadKey().then(function(aeskey) {
userkey = aeskey;
resolve();
}).catch(function(error) {
reject(error);
});
});
}
function journalmarks_initkey(username, password) {
return new Promise(function(resolve, reject) {
computeKey(enc.encode(username), enc.encode(password))
.then(storeKey).then(function() {
resolve();
}).catch(function(error) {
reject(error);
});
});
}
function journalmarks_encrypturl(url) {
return new Promise(function(resolve, reject) {
var bytes = enc.encode(JSON.stringify({url: url}));
encrypt(bytes, userkey).then(function(encurl) {
resolve(encurl);
}).catch(function(error) {
reject(error);
});
});
}
function journalmarks_decrypturl(encurl) {
return new Promise(function(resolve, reject) {
decrypt(encurl, userkey).then(function(bytes) {
resolve(JSON.parse(dec.decode(bytes)));
}).catch(function(error) {
reject(error);
});
});
}
function post_object(url, data) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.open('POST', url);
req.setRequestHeader('Content-type', 'application/json');
req.onload = function() {
if (req.status == 200)
resolve(JSON.parse(req.response));
else
reject(Error(req.response));
};
req.onerror = function() {
reject(Error("Network Error"));
};
req.send(JSON.stringify(data));
});
}
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
function sha256(str) {
var buffer = new TextEncoder("utf-8").encode(str);
return crypto.subtle.digest("SHA-256", buffer).then(function (hash) {
return hex(hash);
});
}
function hex(buffer) {
var hexCodes = [];
var view = new DataView(buffer);
for (var i = 0; i < view.byteLength; i += 4) {
// Using getUint32 reduces the number of iterations needed (we process 4 bytes each time)
var value = view.getUint32(i)
// toString(16) will give the hex representation of the number without padding
var stringValue = value.toString(16)
// We use concatenation and slice for padding
var padding = '00000000'
var paddedValue = (padding + stringValue).slice(-padding.length)
hexCodes.push(paddedValue);
}
// Join all the hex strings into one
return hexCodes.join("");
}