Skip to content
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