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.
189 lines
5.2 KiB
189 lines
5.2 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, comment) { |
|
return new Promise(function(resolve, reject) { |
|
var content = {url: url}; |
|
if(typeof comment === 'string') |
|
content.comment = comment; |
|
var bytes = enc.encode(JSON.stringify(content)); |
|
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(""); |
|
}
|
|
|