Skip to content

Replace plaintext prompt with a password prompt #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,17 @@ Hi world. I have a secret. Can you read it?

When the user hits 'Save' (or a draft is attempted to be saved) a prompt will open, asking the user to enter a pass phrase key for the encryption. Once supplied, the encryption will be done in the browser and the encrypted text submitted to the server.

## Settings

This plugin includes configuration settings.

* `copytoclipboard` - If set to true, the plugin tries to copy the decrypted value to the clipboard.
* `hidepasswordoncopytoclipboard` - If set to true, the decrypted value will not be shown after being copied to the clipboard (see option 'copytoclipboard').

## ChangeLog

* 2022-08-08: Added ability and setting for copying the contents to the clipboard on decrypt.
* Contributed by Thomas Schäfer (https://github.com/ternite).
* 2022-02-02: Preparatory fixes/testing for PHP 8. Improvements for code style PSRs.
* 2021-05-18: Fix for internal link edit toolbar button. Issue #12.
* 2021-03-03: Add wrapping for the pre tag. Contributed by dustin-something.
Expand Down
21 changes: 11 additions & 10 deletions action.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,23 @@ class action_plugin_dokucrypt2 extends DokuWiki_Action_Plugin
public function register($controller)
{
$controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'c_hookjs');
$controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, '_addconfig');
}
public function c_hookjs(&$event, $param)
{

//$event->data['script'][]=array('type'=>'text/javascript','charset'=>'utf-8','_data'=>'','_data'=>"addInitEvent(function() { return(decryptEditSetup()); });");

//$event->data['script'][]=array('type'=>'text/javascript', 'defer' => 'defer', 'charset'=>'utf-8', '_data'=>'', '_data'=>"jQuery(function(){ return(decryptEditSetup()); });");

//$event->data['script'][]=array('type'=>'text/javascript', 'defer' => 'defer', 'charset'=>'utf-8', '_data'=>'', '_data'=>"window.addEventListener('DOMContentLoaded', decryptEditSetup, false);");


public function c_hookjs(&$event, $param) {
$event->data["script"][] = array(
"type" => "text/javascript",
"src" => DOKU_BASE."lib/plugins/dokucrypt2/init.js",
"defer" => "defer",
"_data" => "",
"_data" => ""
);
}

public function _addconfig(&$event, $param)
{
global $JSINFO;
$JSINFO['plugin_dokucrypt2_CONFIG_copytoclipboard'] = $this->getConf('copytoclipboard');
$JSINFO['plugin_dokucrypt2_CONFIG_hidepasswordoncopytoclipboard'] = $this->getConf('hidepasswordoncopytoclipboard');
$JSINFO['plugin_dokucrypt2_TEXT_copied_to_clipboard'] = $this->getLang('copied_to_clipboard');
}
}
7 changes: 7 additions & 0 deletions conf/default.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
/**
* Default settings for the dokucrypt2 plugin
*/

$conf['copytoclipboard'] = 0;
$conf['hidepasswordoncopytoclipboard'] = 1;
7 changes: 7 additions & 0 deletions conf/metadata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
/**
* Options for the dokucrypt2 plugin
*/

$meta['copytoclipboard'] = array('onoff');
$meta['hidepasswordoncopytoclipboard'] = array('onoff');
253 changes: 253 additions & 0 deletions crypto_high-level.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
// This file contains a flat high-level programming interface for dokucrypt2.
// This interface is specific for the module's syntax. All functions defined
// here work synchronously.

/* DOKUWIKI:include_once crypto_low-level.js */

var tag_enc="ENCRYPTED";
var tag_pt="SECRET";
var crypt_keys=[];

function getKeyForLock(lock) {
return crypt_keys[lock];
}

function setKeyForLock(lock,key) {
crypt_keys[lock]=key;
}

/* decrypt the text between <ENCRYPTED> and </ENCRYPTED> */
function decryptMixedText(x) {
var tag=tag_enc;
var ret="", key="", ctext="";
var tagend=0, opentag=0, blockend=0, pos=0;
while((cur=x.indexOf("<" + tag,pos))!=-1) {
if((opentag_end=x.indexOf(">",cur))==-1) {
alert("unable to close to open tag"); return(false);
}
if((closetag=x.indexOf("</" + tag + ">",opentag_end))==-1) {
alert("unable to find closing of " + tag + " tag"); return(false);
}
if(!(ctext=decryptBlock(x.substring(cur,closetag+tag.length+3)))) {
return(false);
}
ret+=x.substring(pos,cur) + ctext;
pos=closetag+tag.length+3;
}
ret+=x.substring(pos);
return(ret);
}

/**
* Tries to encrypt a given text with <SECRET>s contained. Works and returns synchronously.
*
* @param string x The text to be encrypted (usually that's the content of the
* textfield containing the wiki pages text source).
*
* @return string The encrypted mixed text, if all <SECRET>s could be encrypted with
* an already cached key or if there were no <SECRET>s contained.
* Returns null, if a key still must be provided.
*/
function encryptMixedText(x) {
var tag=tag_pt;
var ret="", kctext="";
var tagend=0, opentag=0, blockend=0, pos=0;
while((cur=x.indexOf("<" + tag,pos))!=-1) {
if((opentag_end=x.indexOf(">",cur))==-1) {
alert("unable to find closing angle bracked of <SECRET> tag"); return(null);
}
if((closetag=x.indexOf("</" + tag + ">",opentag_end))==-1) {
x=x+"</" + tag + ">";
// if there is no close tag, add one to the end.
//closetag=x.indexOf("</" + tag + ">",opentag_end); // removed this because it can cause the loss of plaintext that was not intended to be encrypted (e.g. unvoluntarily encrypting <SECRET>1<(SECRET>... would encrypt more text than intended just because of a syntax error.
alert("unable to find close of " + tag + " tag"); return(false);
}
if(!(ctext=encryptBlock(x.substring(cur,closetag+tag.length+3)))) {
return(null);
}
ret+=x.substring(pos,cur) + ctext;
pos=closetag+tag.length+3;
}
ret+=x.substring(pos);
return(ret);
}

function decryptBlock(data) {
var tagend=0, ptend=0, lock=null, ptext;
if((tagend=data.indexOf(">"))==-1) {
//crypt_debug("no > in " + data);
return(false);
}
if((ptend=data.lastIndexOf("</"))==-1) {
//crypt_debug(" no </ in " + data);
return(false);
}
lock=getTagAttr(data.substring(0,tagend+1),"LOCK");
if(lock===null) { lock="default"; }

collapsed=getTagAttr(data.substring(0,tagend+1),"COLLAPSED");
if(collapsed===null || collapsed=="null") { collapsed="1"; }

var key=getKeyForLock(lock);
if(key===false) {
return(false);
} else {
if(!(ptext=decryptTextString(data.substring(tagend+1,ptend),key))) {
return(false);
}
}
return("<" + tag_pt + " LOCK=" + lock + " " +
"COLLAPSED=" + collapsed + ">" + ptext + "</" + tag_pt + ">");
}

// for getTagAttr("<FOO ATTR=val>","ATTR"), return "val"
function getTagAttr(opentag,attr) {
var loff=0;
if((loff=opentag.indexOf(attr + "=" ))!=-1) {
if((t=opentag.indexOf(" ",loff+attr.length+1))!=-1) {
return(opentag.substring(loff+attr.length+1,t));
} else {
return(opentag.substring(loff+attr.length+1,opentag.length-1));
}
}
return(null);
}

/**
* Tries to encrypt a given <SECRET> block. Works and returns synchronously.
*
* @param string data A block of text to be encrypted. This should be a text enclosed by a <SECRET> tag, which also contains arguments LOCK and COLLAPSED.
*
* @return string The encrypted block as a string value. Returns null if there was no key chached for the LOCK specified in the given block.
*/
function encryptBlock(data) {
var tagend=0, ptend=0, lock=null, ctext;
var collapsed = "1";

if((tagend=data.indexOf(">"))==-1) {
//crypt_debug("no > in " + data);
return(null);
}
if((ptend=data.lastIndexOf("</"))==-1) {
//crypt_debug(" no </ in " + data);
return(null);
}
lock=getTagAttr(data.substring(0,tagend+1),"LOCK");
if(lock===null) { lock="default"; }

collapsed=getTagAttr(data.substring(0,tagend+1),"COLLAPSED");
if(collapsed===null || collapsed=="null") { collapsed="1"; }

var key=getKeyForLock(lock);
if(key===false) {
return(null);
} else {
if(!(ctext=encryptTextString(data.substring(tagend+1,ptend),key))) {
return(null);
}
return("<"+tag_enc+" LOCK=" + lock + " " + "COLLAPSED=" + collapsed + ">" + ctext + "</"+tag_enc+">");
}
}


/* encrypt the string in text with ascii key in akey
modified from Encrypt_Text to expect ascii key and take input params
and to return base64 encoded
*/
function encryptTextString(ptext,akey) {
var v, i, ret, key;
var prefix = "##### Encrypted: decrypt with ";
prefix+="http://www.fourmilab.ch/javascrypt/\n";
suffix = "##### End encrypted message\n";

if (akey.length === 0) {
alert("Please specify a key with which to encrypt the message.");
return;
}
if (ptext.length === 0) {
alert("No plain text to encrypt!");
return;
}
ret="";
key=setKeyFromAscii(akey);

// addEntroptyTime eventually results in setting of global entropyData
// which is used by keyFromEntropy
addEntropyTime();
prng = new AESprng(keyFromEntropy());
var plaintext = encode_utf8(ptext);

// Compute MD5 sum of message text and add to header

md5_init();
for (i = 0; i < plaintext.length; i++) {
md5_update(plaintext.charCodeAt(i));
}
md5_finish();
var header = "";
for (i = 0; i < digestBits.length; i++) {
header += String.fromCharCode(digestBits[i]);
}

// Add message length in bytes to header

i = plaintext.length;
header += String.fromCharCode(i >>> 24);
header += String.fromCharCode(i >>> 16);
header += String.fromCharCode(i >>> 8);
header += String.fromCharCode(i & 0xFF);

/* The format of the actual message passed to rijndaelEncrypt
is:
Bytes Content
0-15 MD5 signature of plaintext
16-19 Length of plaintext, big-endian order
20-end Plaintext

Note that this message will be padded with zero bytes
to an integral number of AES blocks (blockSizeInBits / 8).
This does not include the initial vector for CBC
encryption, which is added internally by rijndaelEncrypt.
*/

var ct = rijndaelEncrypt(header + plaintext, key, "CBC");
delete prng;
return(prefix + armour_base64(ct) + suffix);
}

function decryptTextString(ctext,akey) {
key=setKeyFromAscii(akey);
var ct=[];

// remove line breaks
ct=disarm_base64(ctext);
var result=rijndaelDecrypt(ct,key,"CBC");
var header=result.slice(0,20);
result=result.slice(20);
var dl=(header[16]<<24)|(header[17]<<16)|(header[18]<<8)|header[19];

if((dl<0)||(dl>result.length)) {
// alert("Message (length "+result.length+") != expected (" + dl + ")");
dl=result.length;
}

var i,plaintext="";
md5_init();

for(i=0;i<dl;i++) {
plaintext+=String.fromCharCode(result[i]);
md5_update(result[i]);
}

md5_finish();

successful = true;

for(i=0;i<digestBits.length;i++) {
if(digestBits[i]!=header[i]) {
//crypt_debug("Invalid decryption key.");
return(false);
}
}
return(decode_utf8(plaintext));
}
Loading