Skip to content
This repository has been archived by the owner on Mar 16, 2023. It is now read-only.

Commit

Permalink
password-protection feature publication on github
Browse files Browse the repository at this point in the history
  • Loading branch information
yahesh committed Aug 25, 2016
1 parent 9c0ca2d commit 2bcd2db
Show file tree
Hide file tree
Showing 13 changed files with 2,066 additions and 22 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
# 0.3b0 (2016-08-25)

* version bump for password-protection feature publication on github

# 0.2b2 (2016-08-25)

* improve length and handling of server-defined PBKDF2 salt
* fix escaping of password-protected secrets
* harmonize variable names of encryption and decryption code
* test implementation of password-protection feature within chroot environment

# 0.2b1 (2016-08-24)

* implemented the password-protection feature

# 0.2b0 (2016-08-22)

* version bump for initial publication on github
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ Using the Shared-Secrets service allows you to transfer the actual secret in an

Secrets can only be retrieved once. Further retrievals are rejected by matching the secret against the fingerprints of secrets that have been retrieved before. By disallowing repeating retrievals of a secret, it is at least possible to detect when the confidentiality of a secret has been compromised.

To protect your secret from getting known by the server or an attacker, you can additionally protect the secret with a password before sharing it. The secret will be encrypted and decrypted locally without an interaction with the server. You can provide the chosen password to the recipient through a second communication channel to prevent an attacker that is able to control one communication channel from compromising the confidentiallity of your secret.

## Usage

### Share a Secret

Simply enter your secret on the default page of the Shared-Secrets service and press the "Share the Secret!" button. The secret will be GPG-encrypted and converted into a secret sharing link.
Simply enter your secret on the default page of the Shared-Secrets service. You can decide to password-protect the entered secret before sending it to the server by checking the "Password-protected:" box, entering your password and pressing the "Protect!" button. After that, press the "Share the Secret!" button. The secret will be GPG-encrypted and converted into a secret sharing link.

### Read a Secret

To retrieve the secret, simply open the secret sharing link and press the "Read the Secret!" button.
To retrieve the secret, simply open the secret sharing link and press the "Read the Secret!" button. Should your secret be password-protected, check the "Password-protected:" box, enter your password and read your actual secret by pressing the "Unprotect!" button.

## Installation

Expand Down Expand Up @@ -68,7 +70,9 @@ It is strongly recommended to use TLS to protect the connection between the serv

## Attributions

* asmCrypto (https://github.com/vibornoff/asmcrypto.js/): for providing PBKDF2 and AES functions
* Bootstrap (https://getbootstrap.com): for providing an easy-to-use framework to build nice-looking applications
* buffer (https://github.com/feross/buffer): for providing Base64-encoding and array-conversion functions
* clipboard.js (https://clipboardjs.com): for simplifying the copy-to-clipboard use-case a lot
* html5shiv (https://github.com/aFarkas/html5shiv): for handling Internet Explorer compatibility stuff
* jQuery (https://jquery.com): for just existing
Expand All @@ -79,6 +83,7 @@ It is strongly recommended to use TLS to protect the connection between the serv

* switch to the GnuPG PECL (https://pecl.php.net/package/gnupg) once the PHP 7 support is stable
* switch to a more personalized design (current design is taken from https://github.com/twbs/bootstrap/tree/master/docs/examples/starter-template)
* implement an expiry date functionality

## License

Expand Down
6 changes: 6 additions & 0 deletions config.php.default
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
# this is the URL the imprint link shall forward to
define("IMPRINT_URL", "https://localhost.local/");

# this is the configuration to either enable or disable the
# password-protection of secrets on top of the server-side
# encryption, set this configuration value to true to enable
# the optional password-protection
define("ENABLE_PASSWORD_PROTECTION", false);

# this is the configuration to either enable or disable the
# logging of IP addresses for retrieved secrets, set this
# configuration value to false to disable the logging of
Expand Down
2 changes: 1 addition & 1 deletion index.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

# Shared-Secrets v0.2b0
# Shared-Secrets v0.3b0
#
# Copyright (c) 2016, SysEleven GmbH
# All rights reserved.
Expand Down
11 changes: 11 additions & 0 deletions pages/how/get.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@

<?php

if (ENABLE_PASSWORD_PROTECTION) {

?>

<h2>Short description of the password-protection feature.</h2>
<p>When using the password-protection feature, the secret is encrypted locally using the AES algorithm in GCM mode. The encryption key is derived from the entered password and a server-provided salt using the PBKDF2 algorithm. The server-provided salt is prepended to the encrypted secret. The password-protection feature is implemented using client-side JavaScript. Please beware that a compromised server may serve you JavaScript code that defeats the purpose of the local encryption. If you do not trust the server that provides the secret sharing service, then encrypt your secret with a locally installed application before sharing it.

<?php

}

# include header
require_once(ROOT_DIR."/template/footer.php");

Expand Down
21 changes: 17 additions & 4 deletions pages/read/get.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,27 @@
# include header
require_once(ROOT_DIR."/template/header.php");

if (ENABLE_PASSWORD_PROTECTION) {

?>

<noscript>
<div class="alert alert-warning">
<strong>Warning!</strong> You don't have JavaScript enabled. You will not be able to read password-protected secrets.
</div>
</noscript>

<?php

}

?>

<h1>Read a Secret:</h1>
<p><pre id="read-secret"><?php print(htmlentities(SECRET_URI)); ?></pre></p>

<form role="form" action="/<?php print(htmlentities(urlencode(SECRET_URI))); ?>" method="post">
<div class="form-group">
<pre><?php print(htmlentities(SECRET_URI)); ?></pre>
</div>
<button type="submit" class="btn btn-default pull-right">Read the Secret!</button>
<button type="submit" class="btn btn-default pull-right" id="read-secret-btn" name="read-secret-btn">Read the Secret!</button>
</form>

<?php
Expand Down
106 changes: 104 additions & 2 deletions pages/read/post.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,116 @@
# include header
require_once(ROOT_DIR."/template/header.php");

if (ENABLE_PASSWORD_PROTECTION) {

?>

<noscript>
<div class="alert alert-warning">
<strong>Warning!</strong> You don't have JavaScript enabled. You will not be able to read password-protected secrets.
</div>
</noscript>
<div class="alert alert-danger" id="decrypt-error" style="display: none;">
<strong>Error!</strong> Local decryption failed.
</div>

<?php

}

?>

<h1>Read a Secret:</h1>
<p><pre id="read-secret"><?php print(read_secret(SECRET_URI)); ?></pre>
<button type="btn" class="btn btn-default" data-clipboard-target="#read-secret" id="copy-to-clipboard">Copy to Clipboard!</button></p>
<p><pre id="secret"><?php print(read_secret(SECRET_URI)); ?></pre>
<button type="btn" class="btn btn-default pull-right" data-clipboard-target="#secret" id="copy-to-clipboard">Copy to Clipboard!</button></p>

<?php

if (ENABLE_PASSWORD_PROTECTION) {

?>

<label class="checkbox-inline" for="decrypt-locally"><input type="checkbox" autocomplete="off" id="decrypt-locally" value="" onclick="decrypt_locally();" />Password-protected: </label>
<input type="password" autocomplete="off" class="form-control" id="password" maxlength="64" size="32" style="display: inline; visibility: hidden; width: 25%;" />
<input type="button" class="btn btn-default" id="decrypt" style="visibility: hidden;" value="Unprotect!" onclick="decrypt();" />

<script src="/vendors/asmcrypto/asmcrypto.js" type="text/javascript"></script>
<script src="/vendors/buffer/index.js" type="text/javascript"></script>

<script type="text/javascript">
// action happening on local decryption
function decrypt() {
var result = decrypt_secret(document.getElementById("secret").innerHTML,
document.getElementById("password").value);

if (null != result) {
document.getElementById("secret").innerHTML = html_entities(result);

document.getElementById("decrypt").disabled = true;
document.getElementById("decrypt-locally").disabled = true;

document.getElementById("password").readOnly = "readonly";

document.getElementById("decrypt-error").style.display = "none";
} else {
document.getElementById("decrypt-error").style.display = "block";
}
}

// show/hide local decryption
function decrypt_locally(checkbox) {
if (document.getElementById("decrypt-locally").checked) {
document.getElementById("decrypt").style.visibility = "visible";
document.getElementById("password").style.visibility = "visible";
} else {
document.getElementById("decrypt").style.visibility = "hidden";
document.getElementById("password").style.visibility = "hidden";
}
}

// prevent code injection through locally decrypted secret
function html_entities(content) {
return content.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

function decrypt_secret(concatSecret, password) {
// these variables configure the PBKDF2 call
var outputLength = 32;
var workFactor = 1024;

// split concatenation of Base64-encoded salt and Base64-encoded encrypted secret
var base64Salt = concatSecret.substring(0, 44);
var base64Secret = concatSecret.substring(44);

// retrieve plain salt from Base64-encoded salt
var salt = (new buffer.SlowBuffer(base64Salt, "base64")).toArrayBuffer();

// retrieve plain secret from Base64-encoded encrypted secret
var secret = (new buffer.SlowBuffer(base64Secret, "base64")).toArrayBuffer();

// derive decryption key
var pbkdf2Key = asmCrypto.PBKDF2_HMAC_SHA256.bytes(password, salt, workFactor, outputLength);

try {
// decrypt secret with derived decryption key
var aesResult = asmCrypto.AES_GCM.decrypt(secret, pbkdf2Key, new Uint8Array(12));
} catch(err) {
var aesResult = null;
}

if (null != aesResult) {
// return UTF-8-encoded decrypted secret
return (new buffer.SlowBuffer(aesResult)).toString("utf-8");
} else {
return aesResult;
}
}
</script>

<?php

}

# include footer
require_once(ROOT_DIR."/template/footer.php");

Expand Down
109 changes: 104 additions & 5 deletions pages/share/get.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,117 @@
# include header
require_once(ROOT_DIR."/template/header.php");

if (ENABLE_PASSWORD_PROTECTION) {

?>

<form role="form" action="/<?php print(htmlentities(urlencode(SECRET_URI))); ?>" method="post">
<div class="form-group">
<label for="secret"><h1>Share a Secret:</h1></label>
<input type="text" autocomplete="off" class="form-control" id="secret" name="secret" maxlength="512" size="512" />
<noscript>
<div class="alert alert-warning">
<strong>Warning!</strong> You don't have JavaScript enabled. You will not be able to share password-protected secrets.
</div>
</noscript>
<div class="alert alert-danger" id="encrypt-error" style="display: none;">
<strong>Error!</strong> Local encryption failed.
</div>
<button type="submit" class="btn btn-default pull-right">Share the Secret!</button>

<?php

}

?>

<form role="form" action="/<?php print(htmlentities(urlencode(SECRET_URI))); ?>" method="post">
<label for="secret"><h1>Share a Secret:</h1></label>
<input type="text" autocomplete="off" class="form-control" id="secret" name="secret" maxlength="512" size="512" />
<button type="submit" class="btn btn-default pull-right" id="share-secret-btn" name="share-secret-btn" style="margin-top: 0.5em;">Share the Secret!</button>
</form>

<?php

if (ENABLE_PASSWORD_PROTECTION) {

?>

<label class="checkbox-inline" for="encrypt-locally"><input type="checkbox" autocomplete="off" id="encrypt-locally" value="" onclick="encrypt_locally();" />Password-protected: </label>
<input type="password" autocomplete="off" class="form-control" id="password" maxlength="64" size="32" style="display: inline; margin-top: 0.5em; visibility: hidden; width: 25%;" />
<input type="button" class="btn btn-default" id="encrypt" style="visibility: hidden;" value="Protect!" onclick="encrypt();" />

<script src="/vendors/asmcrypto/asmcrypto.js" type="text/javascript"></script>
<script src="/vendors/buffer/index.js" type="text/javascript"></script>

<script type="text/javascript">
// action happening on local encryption
function encrypt() {
var result = encrypt_secret(document.getElementById("secret").value,
document.getElementById("password").value,
"<?php print(base64_encode(openssl_random_pseudo_bytes(32))); ?>");

if (null != result) {
document.getElementById("secret").value = result;

document.getElementById("share-secret-btn").disabled = false;

document.getElementById("encrypt").disabled = true;
document.getElementById("encrypt-locally").disabled = true;

document.getElementById("password").readOnly = "readonly";
document.getElementById("secret").readOnly = "readonly";

document.getElementById("encrypt-error").style.display = "none";
} else {
document.getElementById("encrypt-error").style.display = "block";
}
}

// show/hide local encryption
function encrypt_locally(checkbox) {
if (document.getElementById("encrypt-locally").checked) {
document.getElementById("share-secret-btn").disabled = true;

document.getElementById("encrypt").style.visibility = "visible";
document.getElementById("password").style.visibility = "visible";
} else {
document.getElementById("share-secret-btn").disabled = false;

document.getElementById("encrypt").style.visibility = "hidden";
document.getElementById("password").style.visibility = "hidden";
}
}

function encrypt_secret(secret, password, base64Salt) {
// these variables configure the PBKDF2 call
var outputLength = 32;
var workFactor = 1024;

// retrieve salt from Base64-encoded salt
var salt = (new buffer.SlowBuffer(base64Salt, "base64")).toArrayBuffer();

// derive encryption key
var pbkdf2Key = asmCrypto.PBKDF2_HMAC_SHA256.bytes(password, salt, workFactor, outputLength);

try {
// encrypt secret with derived encryption key
var aesResult = asmCrypto.AES_GCM.encrypt(secret, pbkdf2Key, new Uint8Array(12));
} catch (err) {
var aesResult = null;
}

if (null != aesResult) {
// create Base64-encoded encrypted secret
var base64Secret = (new buffer.SlowBuffer(aesResult)).toString("base64");

// return concatenation of Base64-encoded salt and Base64-encoded encrypted secret
return (base64Salt + base64Secret);
} else {
return aesResult;
}
}
</script>

<?php

}

# include footer
require_once(ROOT_DIR."/template/footer.php");

Expand Down
16 changes: 15 additions & 1 deletion pages/share/post.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,25 @@
# include header
require_once(ROOT_DIR."/template/header.php");

if (ENABLE_PASSWORD_PROTECTION) {

?>

<noscript>
<div class="alert alert-warning">
<strong>Warning!</strong> You don't have JavaScript enabled. You will not be able to share password-protected secrets.
</div>
</noscript>

<?php

}

?>

<h1>Share a Secret:</h1>
<p><pre id="share-secret"><?php print(share_secret(SECRET_PARAM)); ?></pre>
<button type="btn" class="btn btn-default" data-clipboard-target="#share-secret" id="copy-to-clipboard">Copy to Clipboard!</button></p>
<button type="btn" class="btn btn-default pull-right" data-clipboard-target="#share-secret" id="copy-to-clipboard">Copy to Clipboard!</button></p>

<?php

Expand Down
Loading

0 comments on commit 2bcd2db

Please sign in to comment.