|
<!DOCTYPE html> |
|
<html class="staticrypt-html"> |
|
<head> |
|
<meta charset="utf-8" /> |
|
<title>Protected Page</title> |
|
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
|
|
|
|
|
<meta http-equiv="cache-control" content="max-age=0" /> |
|
<meta http-equiv="cache-control" content="no-cache" /> |
|
<meta http-equiv="expires" content="0" /> |
|
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" /> |
|
<meta http-equiv="pragma" content="no-cache" /> |
|
|
|
<style> |
|
.staticrypt-hr { |
|
margin-top: 20px; |
|
margin-bottom: 20px; |
|
border: 0; |
|
border-top: 1px solid #eee; |
|
} |
|
|
|
.staticrypt-page { |
|
width: 360px; |
|
padding: 8% 0 0; |
|
margin: auto; |
|
box-sizing: border-box; |
|
} |
|
|
|
.staticrypt-form { |
|
position: relative; |
|
z-index: 1; |
|
background: #ffffff; |
|
max-width: 360px; |
|
margin: 0 auto 100px; |
|
padding: 45px; |
|
text-align: center; |
|
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); |
|
} |
|
|
|
.staticrypt-form input[type="password"], |
|
input[type="text"] { |
|
background: inherit; |
|
border: 0; |
|
box-sizing: border-box; |
|
font-size: 14px; |
|
outline: 0; |
|
padding: 15px 30px 15px 15px; |
|
width: 100%; |
|
} |
|
|
|
.staticrypt-password-container { |
|
position: relative; |
|
outline: 0; |
|
background: #f2f2f2; |
|
width: 100%; |
|
border: 0; |
|
margin: 0 0 15px; |
|
box-sizing: border-box; |
|
} |
|
|
|
.staticrypt-toggle-password-visibility { |
|
cursor: pointer; |
|
height: 20px; |
|
opacity: 60%; |
|
padding: 13px; |
|
position: absolute; |
|
right: 0; |
|
top: 50%; |
|
transform: translateY(-50%); |
|
width: 20px; |
|
} |
|
|
|
.staticrypt-form .staticrypt-decrypt-button { |
|
text-transform: uppercase; |
|
outline: 0; |
|
background: #4CAF50; |
|
width: 100%; |
|
border: 0; |
|
padding: 15px; |
|
color: #ffffff; |
|
font-size: 14px; |
|
cursor: pointer; |
|
} |
|
|
|
.staticrypt-form .staticrypt-decrypt-button:hover, |
|
.staticrypt-form .staticrypt-decrypt-button:active, |
|
.staticrypt-form .staticrypt-decrypt-button:focus { |
|
background: #4CAF50; |
|
filter: brightness(92%); |
|
} |
|
|
|
.staticrypt-html { |
|
height: 100%; |
|
} |
|
|
|
.staticrypt-body { |
|
height: 100%; |
|
margin: 0; |
|
} |
|
|
|
.staticrypt-content { |
|
height: 100%; |
|
margin-bottom: 1em; |
|
background: #76B852; |
|
font-family: "Arial", sans-serif; |
|
-webkit-font-smoothing: antialiased; |
|
-moz-osx-font-smoothing: grayscale; |
|
} |
|
|
|
.staticrypt-instructions { |
|
margin-top: -1em; |
|
margin-bottom: 1em; |
|
} |
|
|
|
.staticrypt-title { |
|
font-size: 1.5em; |
|
} |
|
|
|
label.staticrypt-remember { |
|
display: flex; |
|
align-items: center; |
|
margin-bottom: 1em; |
|
} |
|
|
|
.staticrypt-remember input[type="checkbox"] { |
|
transform: scale(1.5); |
|
margin-right: 1em; |
|
} |
|
|
|
.hidden { |
|
display: none !important; |
|
} |
|
|
|
.staticrypt-spinner-container { |
|
height: 100%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
} |
|
|
|
.staticrypt-spinner { |
|
display: inline-block; |
|
width: 2rem; |
|
height: 2rem; |
|
vertical-align: text-bottom; |
|
border: 0.25em solid gray; |
|
border-right-color: transparent; |
|
border-radius: 50%; |
|
-webkit-animation: spinner-border 0.75s linear infinite; |
|
animation: spinner-border 0.75s linear infinite; |
|
animation-duration: 0.75s; |
|
animation-timing-function: linear; |
|
animation-delay: 0s; |
|
animation-iteration-count: infinite; |
|
animation-direction: normal; |
|
animation-fill-mode: none; |
|
animation-play-state: running; |
|
animation-name: spinner-border; |
|
} |
|
|
|
@keyframes spinner-border { |
|
100% { |
|
transform: rotate(360deg); |
|
} |
|
} |
|
|
|
@media screen and (-webkit-min-device-pixel-ratio: 0) { |
|
.staticrypt-form input[type="password"], |
|
input[type="text"] { |
|
font-size: 16px; |
|
} |
|
} |
|
</style> |
|
</head> |
|
|
|
<body class="staticrypt-body"> |
|
<div id="staticrypt_loading" class="staticrypt-spinner-container"> |
|
<div class="staticrypt-spinner"></div> |
|
</div> |
|
|
|
<div id="staticrypt_content" class="staticrypt-content hidden"> |
|
<div class="staticrypt-page"> |
|
<div class="staticrypt-form"> |
|
<div class="staticrypt-instructions"> |
|
<p class="staticrypt-title">Protected Page</p> |
|
<p></p> |
|
</div> |
|
|
|
<hr class="staticrypt-hr" /> |
|
|
|
<form id="staticrypt-form" action="#" method="post"> |
|
<div class="staticrypt-password-container"> |
|
<input |
|
id="staticrypt-password" |
|
type="password" |
|
name="password" |
|
placeholder="Password" |
|
autofocus |
|
/> |
|
|
|
<img |
|
class="staticrypt-toggle-password-visibility" |
|
alt="template_toggle_show" |
|
title="template_toggle_show" |
|
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NDAgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNS4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTM4LjggNS4xQzI4LjQtMy4xIDEzLjMtMS4yIDUuMSA5LjJTLTEuMiAzNC43IDkuMiA0Mi45bDU5MiA0NjRjMTAuNCA4LjIgMjUuNSA2LjMgMzMuNy00LjFzNi4zLTI1LjUtNC4xLTMzLjdMNTI1LjYgMzg2LjdjMzkuNi00MC42IDY2LjQtODYuMSA3OS45LTExOC40YzMuMy03LjkgMy4zLTE2LjcgMC0yNC42Yy0xNC45LTM1LjctNDYuMi04Ny43LTkzLTEzMS4xQzQ2NS41IDY4LjggNDAwLjggMzIgMzIwIDMyYy02OC4yIDAtMTI1IDI2LjMtMTY5LjMgNjAuOEwzOC44IDUuMXpNMjIzLjEgMTQ5LjVDMjQ4LjYgMTI2LjIgMjgyLjcgMTEyIDMyMCAxMTJjNzkuNSAwIDE0NCA2NC41IDE0NCAxNDRjMCAyNC45LTYuMyA0OC4zLTE3LjQgNjguN0w0MDggMjk0LjVjOC40LTE5LjMgMTAuNi00MS40IDQuOC02My4zYy0xMS4xLTQxLjUtNDcuOC02OS40LTg4LjYtNzEuMWMtNS44LS4yLTkuMiA2LjEtNy40IDExLjdjMi4xIDYuNCAzLjMgMTMuMiAzLjMgMjAuM2MwIDEwLjItMi40IDE5LjgtNi42IDI4LjNsLTkwLjMtNzAuOHpNMzczIDM4OS45Yy0xNi40IDYuNS0zNC4zIDEwLjEtNTMgMTAuMWMtNzkuNSAwLTE0NC02NC41LTE0NC0xNDRjMC02LjkgLjUtMTMuNiAxLjQtMjAuMkw4My4xIDE2MS41QzYwLjMgMTkxLjIgNDQgMjIwLjggMzQuNSAyNDMuN2MtMy4zIDcuOS0zLjMgMTYuNyAwIDI0LjZjMTQuOSAzNS43IDQ2LjIgODcuNyA5MyAxMzEuMUMxNzQuNSA0NDMuMiAyMzkuMiA0ODAgMzIwIDQ4MGM0Ny44IDAgODkuOS0xMi45IDEyNi4yLTMyLjVMMzczIDM4OS45eiIvPjwvc3ZnPg==" |
|
/> |
|
</div> |
|
|
|
<label id="staticrypt-remember-label" class="staticrypt-remember hidden"> |
|
<input id="staticrypt-remember" type="checkbox" name="remember" /> |
|
Remember me |
|
</label> |
|
|
|
<input type="submit" class="staticrypt-decrypt-button" value="DECRYPT" /> |
|
</form> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
const staticryptInitiator = |
|
((function(){ |
|
const exports = {}; |
|
const cryptoEngine = ((function(){ |
|
const exports = {}; |
|
const { subtle } = crypto; |
|
|
|
const IV_BITS = 16 * 8; |
|
const HEX_BITS = 4; |
|
const ENCRYPTION_ALGO = "AES-CBC"; |
|
|
|
|
|
|
|
|
|
|
|
const HexEncoder = { |
|
|
|
|
|
|
|
|
|
|
|
parse: function (hexString) { |
|
if (hexString.length % 2 !== 0) throw "Invalid hexString"; |
|
const arrayBuffer = new Uint8Array(hexString.length / 2); |
|
|
|
for (let i = 0; i < hexString.length; i += 2) { |
|
const byteValue = parseInt(hexString.substring(i, i + 2), 16); |
|
if (isNaN(byteValue)) { |
|
throw "Invalid hexString"; |
|
} |
|
arrayBuffer[i / 2] = byteValue; |
|
} |
|
return arrayBuffer; |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
stringify: function (bytes) { |
|
const hexBytes = []; |
|
|
|
for (let i = 0; i < bytes.length; ++i) { |
|
let byteString = bytes[i].toString(16); |
|
if (byteString.length < 2) { |
|
byteString = "0" + byteString; |
|
} |
|
hexBytes.push(byteString); |
|
} |
|
return hexBytes.join(""); |
|
}, |
|
}; |
|
|
|
|
|
|
|
|
|
const UTF8Encoder = { |
|
parse: function (str) { |
|
return new TextEncoder().encode(str); |
|
}, |
|
|
|
stringify: function (bytes) { |
|
return new TextDecoder().decode(bytes); |
|
}, |
|
}; |
|
|
|
|
|
|
|
|
|
async function encrypt(msg, hashedPassword) { |
|
|
|
|
|
const iv = crypto.getRandomValues(new Uint8Array(IV_BITS / 8)); |
|
|
|
const key = await subtle.importKey("raw", HexEncoder.parse(hashedPassword), ENCRYPTION_ALGO, false, ["encrypt"]); |
|
|
|
const encrypted = await subtle.encrypt( |
|
{ |
|
name: ENCRYPTION_ALGO, |
|
iv: iv, |
|
}, |
|
key, |
|
UTF8Encoder.parse(msg) |
|
); |
|
|
|
|
|
return HexEncoder.stringify(iv) + HexEncoder.stringify(new Uint8Array(encrypted)); |
|
} |
|
exports.encrypt = encrypt; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function decrypt(encryptedMsg, hashedPassword) { |
|
const ivLength = IV_BITS / HEX_BITS; |
|
const iv = HexEncoder.parse(encryptedMsg.substring(0, ivLength)); |
|
const encrypted = encryptedMsg.substring(ivLength); |
|
|
|
const key = await subtle.importKey("raw", HexEncoder.parse(hashedPassword), ENCRYPTION_ALGO, false, ["decrypt"]); |
|
|
|
const outBuffer = await subtle.decrypt( |
|
{ |
|
name: ENCRYPTION_ALGO, |
|
iv: iv, |
|
}, |
|
key, |
|
HexEncoder.parse(encrypted) |
|
); |
|
|
|
return UTF8Encoder.stringify(new Uint8Array(outBuffer)); |
|
} |
|
exports.decrypt = decrypt; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function hashPassword(password, salt) { |
|
|
|
|
|
let hashedPassword = await hashLegacyRound(password, salt); |
|
|
|
hashedPassword = await hashSecondRound(hashedPassword, salt); |
|
|
|
return hashThirdRound(hashedPassword, salt); |
|
} |
|
exports.hashPassword = hashPassword; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function hashLegacyRound(password, salt) { |
|
return pbkdf2(password, salt, 1000, "SHA-1"); |
|
} |
|
exports.hashLegacyRound = hashLegacyRound; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function hashSecondRound(hashedPassword, salt) { |
|
return pbkdf2(hashedPassword, salt, 14000, "SHA-256"); |
|
} |
|
exports.hashSecondRound = hashSecondRound; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function hashThirdRound(hashedPassword, salt) { |
|
return pbkdf2(hashedPassword, salt, 585000, "SHA-256"); |
|
} |
|
exports.hashThirdRound = hashThirdRound; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function pbkdf2(password, salt, iterations, hashAlgorithm) { |
|
const key = await subtle.importKey("raw", UTF8Encoder.parse(password), "PBKDF2", false, ["deriveBits"]); |
|
|
|
const keyBytes = await subtle.deriveBits( |
|
{ |
|
name: "PBKDF2", |
|
hash: hashAlgorithm, |
|
iterations, |
|
salt: UTF8Encoder.parse(salt), |
|
}, |
|
key, |
|
256 |
|
); |
|
|
|
return HexEncoder.stringify(new Uint8Array(keyBytes)); |
|
} |
|
|
|
function generateRandomSalt() { |
|
const bytes = crypto.getRandomValues(new Uint8Array(128 / 8)); |
|
|
|
return HexEncoder.stringify(new Uint8Array(bytes)); |
|
} |
|
exports.generateRandomSalt = generateRandomSalt; |
|
|
|
async function signMessage(hashedPassword, message) { |
|
const key = await subtle.importKey( |
|
"raw", |
|
HexEncoder.parse(hashedPassword), |
|
{ |
|
name: "HMAC", |
|
hash: "SHA-256", |
|
}, |
|
false, |
|
["sign"] |
|
); |
|
const signature = await subtle.sign("HMAC", key, UTF8Encoder.parse(message)); |
|
|
|
return HexEncoder.stringify(new Uint8Array(signature)); |
|
} |
|
exports.signMessage = signMessage; |
|
|
|
function getRandomAlphanum() { |
|
const possibleCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; |
|
|
|
let byteArray; |
|
let parsedInt; |
|
|
|
|
|
|
|
do { |
|
byteArray = crypto.getRandomValues(new Uint8Array(1)); |
|
|
|
parsedInt = byteArray[0] & 0xff; |
|
} while (parsedInt >= 256 - (256 % possibleCharacters.length)); |
|
|
|
|
|
const randomIndex = parsedInt % possibleCharacters.length; |
|
|
|
return possibleCharacters[randomIndex]; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function generateRandomString(length) { |
|
let randomString = ""; |
|
|
|
for (let i = 0; i < length; i++) { |
|
randomString += getRandomAlphanum(); |
|
} |
|
|
|
return randomString; |
|
} |
|
exports.generateRandomString = generateRandomString; |
|
|
|
return exports; |
|
})()); |
|
const codec = ((function(){ |
|
const exports = {}; |
|
|
|
|
|
|
|
|
|
|
|
function init(cryptoEngine) { |
|
const exports = {}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function encode(msg, password, salt) { |
|
const hashedPassword = await cryptoEngine.hashPassword(password, salt); |
|
|
|
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword); |
|
|
|
|
|
|
|
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted); |
|
|
|
return hmac + encrypted; |
|
} |
|
exports.encode = encode; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function encodeWithHashedPassword(msg, hashedPassword) { |
|
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword); |
|
|
|
|
|
|
|
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted); |
|
|
|
return hmac + encrypted; |
|
} |
|
exports.encodeWithHashedPassword = encodeWithHashedPassword; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function decode(signedMsg, hashedPassword, salt, backwardCompatibleAttempt = 0, originalPassword = "") { |
|
const encryptedHMAC = signedMsg.substring(0, 64); |
|
const encryptedMsg = signedMsg.substring(64); |
|
const decryptedHMAC = await cryptoEngine.signMessage(hashedPassword, encryptedMsg); |
|
|
|
if (decryptedHMAC !== encryptedHMAC) { |
|
|
|
|
|
originalPassword = originalPassword || hashedPassword; |
|
if (backwardCompatibleAttempt === 0) { |
|
const updatedHashedPassword = await cryptoEngine.hashThirdRound(originalPassword, salt); |
|
|
|
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword); |
|
} |
|
if (backwardCompatibleAttempt === 1) { |
|
let updatedHashedPassword = await cryptoEngine.hashSecondRound(originalPassword, salt); |
|
updatedHashedPassword = await cryptoEngine.hashThirdRound(updatedHashedPassword, salt); |
|
|
|
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword); |
|
} |
|
|
|
return { success: false, message: "Signature mismatch" }; |
|
} |
|
|
|
return { |
|
success: true, |
|
decoded: await cryptoEngine.decrypt(encryptedMsg, hashedPassword), |
|
}; |
|
} |
|
exports.decode = decode; |
|
|
|
return exports; |
|
} |
|
exports.init = init; |
|
|
|
return exports; |
|
})()); |
|
const decode = codec.init(cryptoEngine).decode; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function init(staticryptConfig, templateConfig) { |
|
const exports = {}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function decryptAndReplaceHtml(hashedPassword) { |
|
const { staticryptEncryptedMsgUniqueVariableName, staticryptSaltUniqueVariableName } = staticryptConfig; |
|
const { replaceHtmlCallback } = templateConfig; |
|
|
|
const result = await decode( |
|
staticryptEncryptedMsgUniqueVariableName, |
|
hashedPassword, |
|
staticryptSaltUniqueVariableName |
|
); |
|
if (!result.success) { |
|
return false; |
|
} |
|
const plainHTML = result.decoded; |
|
|
|
|
|
if (typeof replaceHtmlCallback === "function") { |
|
replaceHtmlCallback(plainHTML); |
|
} else { |
|
document.write(plainHTML); |
|
document.close(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function handleDecryptionOfPage(password, isRememberChecked) { |
|
const { staticryptSaltUniqueVariableName } = staticryptConfig; |
|
|
|
|
|
const hashedPassword = await cryptoEngine.hashPassword(password, staticryptSaltUniqueVariableName); |
|
return handleDecryptionOfPageFromHash(hashedPassword, isRememberChecked); |
|
} |
|
exports.handleDecryptionOfPage = handleDecryptionOfPage; |
|
|
|
async function handleDecryptionOfPageFromHash(hashedPassword, isRememberChecked) { |
|
const { isRememberEnabled, rememberDurationInDays } = staticryptConfig; |
|
const { rememberExpirationKey, rememberPassphraseKey } = templateConfig; |
|
|
|
const isDecryptionSuccessful = await decryptAndReplaceHtml(hashedPassword); |
|
|
|
if (!isDecryptionSuccessful) { |
|
return { |
|
isSuccessful: false, |
|
hashedPassword, |
|
}; |
|
} |
|
|
|
|
|
if (isRememberEnabled && isRememberChecked) { |
|
window.localStorage.setItem(rememberPassphraseKey, hashedPassword); |
|
|
|
|
|
if (rememberDurationInDays > 0) { |
|
window.localStorage.setItem( |
|
rememberExpirationKey, |
|
(new Date().getTime() + rememberDurationInDays * 24 * 60 * 60 * 1000).toString() |
|
); |
|
} |
|
} |
|
|
|
return { |
|
isSuccessful: true, |
|
hashedPassword, |
|
}; |
|
} |
|
exports.handleDecryptionOfPageFromHash = handleDecryptionOfPageFromHash; |
|
|
|
|
|
|
|
|
|
function clearLocalStorage() { |
|
const { clearLocalStorageCallback, rememberExpirationKey, rememberPassphraseKey } = templateConfig; |
|
|
|
if (typeof clearLocalStorageCallback === "function") { |
|
clearLocalStorageCallback(); |
|
} else { |
|
localStorage.removeItem(rememberPassphraseKey); |
|
localStorage.removeItem(rememberExpirationKey); |
|
} |
|
} |
|
|
|
async function handleDecryptOnLoad() { |
|
let isSuccessful = await decryptOnLoadFromUrl(); |
|
|
|
if (!isSuccessful) { |
|
isSuccessful = await decryptOnLoadFromRememberMe(); |
|
} |
|
|
|
return { isSuccessful }; |
|
} |
|
exports.handleDecryptOnLoad = handleDecryptOnLoad; |
|
|
|
|
|
|
|
|
|
|
|
|
|
function logoutIfNeeded() { |
|
const logoutKey = "staticrypt_logout"; |
|
|
|
|
|
const queryParams = new URLSearchParams(window.location.search); |
|
if (queryParams.has(logoutKey)) { |
|
clearLocalStorage(); |
|
return true; |
|
} |
|
|
|
|
|
const hash = window.location.hash.substring(1); |
|
if (hash.includes(logoutKey)) { |
|
clearLocalStorage(); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function decryptOnLoadFromRememberMe() { |
|
const { rememberDurationInDays } = staticryptConfig; |
|
const { rememberExpirationKey, rememberPassphraseKey } = templateConfig; |
|
|
|
|
|
if (logoutIfNeeded()) { |
|
return false; |
|
} |
|
|
|
|
|
if (rememberDurationInDays && rememberDurationInDays > 0) { |
|
const expiration = localStorage.getItem(rememberExpirationKey), |
|
isExpired = expiration && new Date().getTime() > parseInt(expiration); |
|
|
|
if (isExpired) { |
|
clearLocalStorage(); |
|
return false; |
|
} |
|
} |
|
|
|
const hashedPassword = localStorage.getItem(rememberPassphraseKey); |
|
|
|
if (hashedPassword) { |
|
|
|
const isDecryptionSuccessful = await decryptAndReplaceHtml(hashedPassword); |
|
|
|
|
|
|
|
if (!isDecryptionSuccessful) { |
|
clearLocalStorage(); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
async function decryptOnLoadFromUrl() { |
|
const passwordKey = "staticrypt_pwd"; |
|
const rememberMeKey = "remember_me"; |
|
|
|
|
|
|
|
const queryParams = new URLSearchParams(window.location.search); |
|
const hashedPasswordQuery = queryParams.get(passwordKey); |
|
const rememberMeQuery = queryParams.get(rememberMeKey); |
|
|
|
const urlFragment = window.location.hash.substring(1); |
|
|
|
const hashedPasswordRegexMatch = urlFragment.match(new RegExp(passwordKey + "=([^&]*)")); |
|
const hashedPasswordFragment = hashedPasswordRegexMatch ? hashedPasswordRegexMatch[1] : null; |
|
const rememberMeFragment = urlFragment.includes(rememberMeKey); |
|
|
|
const hashedPassword = hashedPasswordFragment || hashedPasswordQuery; |
|
const rememberMe = rememberMeFragment || rememberMeQuery; |
|
|
|
if (hashedPassword) { |
|
return handleDecryptionOfPageFromHash(hashedPassword, rememberMe); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
return exports; |
|
} |
|
exports.init = init; |
|
|
|
return exports; |
|
})()); |
|
; |
|
const templateError = "template_error", |
|
templateToggleAltShow = "template_toggle_show", |
|
templateToggleAltHide = "template_toggle_hide", |
|
isRememberEnabled = true, |
|
staticryptConfig = {"staticryptEncryptedMsgUniqueVariableName":"8a432664d4ec0fac42fa40b7cdf11e713b51a3658c8dea97bbb4080da29177048578180b68913698ab45a3489f6e04ce41f3cc18b2316feec1fc4ccd53c6f1b9ad1602cf309d5894a0c7a70180baeb85642017d30b96a2c5ade162cab62ba2b80cfb4cde70ea9e90127d2cb88fdb75e58c8d0e4f0429e65d4439d3e3af43d8b9c0052d02736aaf63c518ce1c9919a54fff0473a2ad1cb3549f997c9b0c4917019742c96e4f278ec6d2aab3a1d52708b8fb6deff8ab90983a92ddd9644195114cb1d83aec322816209584d4e064fd64fe53262c580ee0f0daee2a118c7e09aca42fdeff9af02171e65c483bd2984e8e9b701164ca4346083652c3a657155147101ceb2870d63e829cac205ec7b1cb032f682fb9b788536a8c5fdfdf014bc48dff3c3df6e0357911c41411ba0cd83562b2e91f5a94b41811dd41188d155ca82d05862da79dafecc10933c20e99e7a6f0e9e00ebadd49846f0153abdab5dc8289cd06ab1d842493cd4af23fa7567db72dac40cef178abbbf227f212195464c975ae205f8749ca0cc76ec82acf21e6294519db5a786908a835458722803145e81c0644e1a7d90f5a6dfb909e286d43ada1b106dfd764c7d9e8b674260b14af5e66308a9b75180cd93d0a618ef1f0c2a87668a86b5aaf837640563dac9072b88d6b6fd52ad3b76e5ee2b06ee015583baf53a4ea37fa5c9080f8f583967bd5e9595cc500046c988cc9f4908b1a04f727ff79ab18209936e2dc83257f6f401b583d5c8d7d0ac1b8eb650ed6ee4320324660b3eeadfb34e0d459cf068d4e28bba6751810266375dfee193fb954326fc87e4b3c258b5b9a80ed12181ad4d6e056d9156be1b5f125c564161a1c79a6e62d6662f861c114dbae873a660cf08b69087aa0dae8cd5a43801075c355270e2e28569e2cdd65ddffba0abfdbcc4f52598ad2380a0e6211ee2f2dec1e1bdea432d6fca746250bd2ba06f38e4f22f3d3cb1ff5c5dcb3cfb4a40c8ddc953355e2149daedc5e21a04d39af3ab3e295353d0c903efee2f972c486c53f66978c87fd43881f4de5f72533149635032011d9a8b66ecff67b0c8d85bc9c49829eef079d753465dac1e1427989e69761fc05fde35c9cf0f8dad0ad83b672a17c3f16e200ad7ed6e1e2c488f6db22d2809f23887035dcaee9f82cc2fcea16bf418236d6c8845190cd342b23df82d9ed266ab4869d804507991efa372c5bf7e238d3d77efbfefaf8a50a18d89d38d85dd6bd6c6ec44eaa6e766a55671425af0cda121dec5a791f5e650b2518f506c4423790fbc9537619733a784540bd8f179303891135b9f2879c323284954d671209c97eff2e1864b9a1c0d962122f3cdef81026304706ab122df517fcf799e7759f0cb4e4f4076993ad09760aae7c919e7fa11ee0b3461cb40edfa94b9bad472f32263a4a893a182ce5f8af436f0964f3d8d8f57ac8f8ae674a50963182351c2ea2f8be81f7a224db6539a1ea79981329f979ae4efcd2ea2f80f3415302c0efcadaf2266fca6b136eb9f2345ed1c2922df3e0fadca73549e72c5fe86718edf067ac02fab7eb4b4783ba7f6d463a1798abe804015c873c3aac2018f510deab1beb401aa5d195e5024cc247a76bcae20ea4840aa31bf327ec255f5f4dc3c165d31d1ee1b15179e1fc54435b5617dbdf4b08e43f2ce836e9bd34a797d00fa9ca8cb6b7c90f55e4ec2df9d5e7ac40160008cdf982a507d60aba0e404479451e79af23a58855c6187c62ca2f13e4837b5998275ff0eac8500f6792f8e763d0257a7ed04f55b24050522b0e6bb19bd259611fe8220026e9e5e4d1d227392a8d2b60f6a2f3c54b1b43b23e16c55ee8781871f6ebd951ea1d0cd02aedcd52bb3d1b55841676b92fe9ae5e9e49b6d1c4c5285fc2747c1e481cd16961142bd871781246450c8fa060e049e4d5fa445fb4628e2a36b3789228a4b156eb6ca6081cde5e332aa39af3a4bf9a3ab884d7f3037fbd7ccd2a985fdd7ece74bb5c2a580c56f4f92b3f59197fc52bf4bb392351b93cbcf56b75276ae10d99dd0b67f97b310ac65a98f489c41584cbcc210c0fa9487fe22cde86989d079551330d6b8f6f4ffd94b6df68a9949a6e4d484dfb919bba9c62be169664fbe6cd17373004952918a1b08291f16bed20065cd7b1aed52714114fe882f078db8120bc799b718a218fabce65f27af31f2e571d9d9e43caf28c2fca0e3495c1c903db306816cae69bd1fb1f75bab53ba91132e298a6c82af0e68b6de5521a98870800476514ae18529b76e55280b9bf6710f0420c5eda2847deea5ebfa7f43fb8b94414caf8a694e4ccb5f1dd2a12aa0c850566905c00d582b935b06dbcdb61178fc4a98077d9a9c644951bc39516b6912e98305208fb9944ee57f1108f4bae9644ff7026eaad277300a4","isRememberEnabled":true,"rememberDurationInDays":0,"staticryptSaltUniqueVariableName":"b6c99dd11ac4f218d62a58ee8a173832"}; |
|
|
|
|
|
const templateConfig = { |
|
rememberExpirationKey: "staticrypt_expiration", |
|
rememberPassphraseKey: "staticrypt_passphrase", |
|
replaceHtmlCallback: null, |
|
clearLocalStorageCallback: null, |
|
}; |
|
|
|
|
|
const staticrypt = staticryptInitiator.init(staticryptConfig, templateConfig); |
|
|
|
|
|
window.onload = async function () { |
|
const { isSuccessful } = await staticrypt.handleDecryptOnLoad(); |
|
|
|
|
|
|
|
if (!isSuccessful) { |
|
|
|
document.getElementById("staticrypt_loading").classList.add("hidden"); |
|
document.getElementById("staticrypt_content").classList.remove("hidden"); |
|
document.getElementById("staticrypt-password").focus(); |
|
|
|
|
|
if (isRememberEnabled) { |
|
document.getElementById("staticrypt-remember-label").classList.remove("hidden"); |
|
} |
|
} |
|
}; |
|
|
|
|
|
const toggleIcon = document.querySelector(".staticrypt-toggle-password-visibility"); |
|
|
|
const imgSrcEyeClosed = |
|
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NDAgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNS4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTM4LjggNS4xQzI4LjQtMy4xIDEzLjMtMS4yIDUuMSA5LjJTLTEuMiAzNC43IDkuMiA0Mi45bDU5MiA0NjRjMTAuNCA4LjIgMjUuNSA2LjMgMzMuNy00LjFzNi4zLTI1LjUtNC4xLTMzLjdMNTI1LjYgMzg2LjdjMzkuNi00MC42IDY2LjQtODYuMSA3OS45LTExOC40YzMuMy03LjkgMy4zLTE2LjcgMC0yNC42Yy0xNC45LTM1LjctNDYuMi04Ny43LTkzLTEzMS4xQzQ2NS41IDY4LjggNDAwLjggMzIgMzIwIDMyYy02OC4yIDAtMTI1IDI2LjMtMTY5LjMgNjAuOEwzOC44IDUuMXpNMjIzLjEgMTQ5LjVDMjQ4LjYgMTI2LjIgMjgyLjcgMTEyIDMyMCAxMTJjNzkuNSAwIDE0NCA2NC41IDE0NCAxNDRjMCAyNC45LTYuMyA0OC4zLTE3LjQgNjguN0w0MDggMjk0LjVjOC40LTE5LjMgMTAuNi00MS40IDQuOC02My4zYy0xMS4xLTQxLjUtNDcuOC02OS40LTg4LjYtNzEuMWMtNS44LS4yLTkuMiA2LjEtNy40IDExLjdjMi4xIDYuNCAzLjMgMTMuMiAzLjMgMjAuM2MwIDEwLjItMi40IDE5LjgtNi42IDI4LjNsLTkwLjMtNzAuOHpNMzczIDM4OS45Yy0xNi40IDYuNS0zNC4zIDEwLjEtNTMgMTAuMWMtNzkuNSAwLTE0NC02NC41LTE0NC0xNDRjMC02LjkgLjUtMTMuNiAxLjQtMjAuMkw4My4xIDE2MS41QzYwLjMgMTkxLjIgNDQgMjIwLjggMzQuNSAyNDMuN2MtMy4zIDcuOS0zLjMgMTYuNyAwIDI0LjZjMTQuOSAzNS43IDQ2LjIgODcuNyA5MyAxMzEuMUMxNzQuNSA0NDMuMiAyMzkuMiA0ODAgMzIwIDQ4MGM0Ny44IDAgODkuOS0xMi45IDEyNi4yLTMyLjVMMzczIDM4OS45eiIvPjwvc3ZnPg=="; |
|
const imgSrcEyeOpened = |
|
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNS4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTI4OCAzMmMtODAuOCAwLTE0NS41IDM2LjgtMTkyLjYgODAuNkM0OC42IDE1NiAxNy4zIDIwOCAyLjUgMjQzLjdjLTMuMyA3LjktMy4zIDE2LjcgMCAyNC42QzE3LjMgMzA0IDQ4LjYgMzU2IDk1LjQgMzk5LjRDMTQyLjUgNDQzLjIgMjA3LjIgNDgwIDI4OCA0ODBzMTQ1LjUtMzYuOCAxOTIuNi04MC42YzQ2LjgtNDMuNSA3OC4xLTk1LjQgOTMtMTMxLjFjMy4zLTcuOSAzLjMtMTYuNyAwLTI0LjZjLTE0LjktMzUuNy00Ni4yLTg3LjctOTMtMTMxLjFDNDMzLjUgNjguOCAzNjguOCAzMiAyODggMzJ6TTE0NCAyNTZhMTQ0IDE0NCAwIDEgMSAyODggMCAxNDQgMTQ0IDAgMSAxIC0yODggMHptMTQ0LTY0YzAgMzUuMy0yOC43IDY0LTY0IDY0Yy03LjEgMC0xMy45LTEuMi0yMC4zLTMuM2MtNS41LTEuOC0xMS45IDEuNi0xMS43IDcuNGMuMyA2LjkgMS4zIDEzLjggMy4yIDIwLjdjMTMuNyA1MS4yIDY2LjQgODEuNiAxMTcuNiA2Ny45czgxLjYtNjYuNCA2Ny45LTExNy42Yy0xMS4xLTQxLjUtNDcuOC02OS40LTg4LjYtNzEuMWMtNS44LS4yLTkuMiA2LjEtNy40IDExLjdjMi4xIDYuNCAzLjMgMTMuMiAzLjMgMjAuM3oiLz48L3N2Zz4="; |
|
toggleIcon.addEventListener("click", function () { |
|
const passwordInput = document.getElementById("staticrypt-password"); |
|
if (passwordInput.type === "password") { |
|
passwordInput.type = "text"; |
|
toggleIcon.src = imgSrcEyeOpened; |
|
toggleIcon.alt = templateToggleAltHide; |
|
toggleIcon.title = templateToggleAltHide; |
|
} else { |
|
passwordInput.type = "password"; |
|
toggleIcon.src = imgSrcEyeClosed; |
|
toggleIcon.alt = templateToggleAltShow; |
|
toggleIcon.title = templateToggleAltShow; |
|
} |
|
}); |
|
|
|
|
|
document.getElementById("staticrypt-form").addEventListener("submit", async function (e) { |
|
e.preventDefault(); |
|
|
|
const password = document.getElementById("staticrypt-password").value, |
|
isRememberChecked = document.getElementById("staticrypt-remember").checked; |
|
|
|
const { isSuccessful } = await staticrypt.handleDecryptionOfPage(password, isRememberChecked); |
|
|
|
if (!isSuccessful) { |
|
alert(templateError); |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |
|
|