xref: /plugin/dokucrypt3/crypto_high-level.js (revision 97c734d516051c5b55fc0c340717b771d97a295d)
1// This file contains a flat high-level programming interface for dokucrypt2.
2// This interface is specific for the module's syntax. All functions defined
3// here work synchronously.
4
5/* DOKUWIKI:include_once crypto_low-level.js */
6
7var tag_enc="ENCRYPTED";
8var tag_pt="SECRET";
9var crypt_keys=[];
10
11function getKeyForLock(lock) {
12	return crypt_keys[lock];
13}
14
15function setKeyForLock(lock,key) {
16	crypt_keys[lock]=key;
17}
18
19/* decrypt the text between <ENCRYPTED> and </ENCRYPTED> */
20function decryptMixedText(x) {
21  var tag=tag_enc;
22  var ret="", key="", ctext="";
23  var tagend=0, opentag=0, blockend=0, pos=0;
24  while((cur=x.indexOf("<" + tag,pos))!=-1) {
25    if((opentag_end=x.indexOf(">",cur))==-1) {
26      alert("unable to close to open tag"); return(false);
27    }
28    if((closetag=x.indexOf("</" + tag + ">",opentag_end))==-1) {
29      alert("unable to find closing of " + tag + " tag"); return(false);
30    }
31    if(!(ctext=decryptBlock(x.substring(cur,closetag+tag.length+3)))) {
32      return(false);
33    }
34    ret+=x.substring(pos,cur) + ctext;
35    pos=closetag+tag.length+3;
36  }
37  ret+=x.substring(pos);
38  return(ret);
39}
40
41/**
42 * Tries to encrypt a given text with <SECRET>s contained. Works and returns synchronously.
43 *
44 * @param  string   x   The text to be encrypted (usually that's the content of the
45 *                      textfield containing the wiki pages text source).
46 *
47 * @return string       The encrypted mixed text, if all <SECRET>s could be encrypted with
48 *                      an already cached key or if there were no <SECRET>s contained.
49 *                      Returns null, if a key still must be provided.
50 */
51function encryptMixedText(x) {
52  var tag=tag_pt;
53  var ret="", kctext="";
54  var tagend=0, opentag=0, blockend=0, pos=0;
55  while((cur=x.indexOf("<" + tag,pos))!=-1) {
56    if((opentag_end=x.indexOf(">",cur))==-1) {
57      alert("unable to find closing angle bracked of <SECRET> tag"); return(null);
58    }
59    if((closetag=x.indexOf("</" + tag + ">",opentag_end))==-1) {
60      x=x+"</" + tag + ">";
61      // if there is no close tag, add one to the end.
62      //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.
63      alert("unable to find close of " + tag + " tag"); return(false);
64    }
65    if(!(ctext=encryptBlock(x.substring(cur,closetag+tag.length+3)))) {
66      return(null);
67    }
68    ret+=x.substring(pos,cur) + ctext;
69    pos=closetag+tag.length+3;
70  }
71  ret+=x.substring(pos);
72  return(ret);
73}
74
75function decryptBlock(data) {
76  var tagend=0, ptend=0, lock=null, ptext;
77  if((tagend=data.indexOf(">"))==-1) {
78    //crypt_debug("no > in " + data);
79    return(false);
80  }
81  if((ptend=data.lastIndexOf("</"))==-1) {
82    //crypt_debug(" no </ in " + data);
83    return(false);
84  }
85  lock=getTagAttr(data.substring(0,tagend+1),"LOCK");
86  if(lock===null) { lock="default"; }
87
88  collapsed=getTagAttr(data.substring(0,tagend+1),"COLLAPSED");
89  if(collapsed===null || collapsed=="null") { collapsed="1"; }
90
91  var key=getKeyForLock(lock);
92  if(key===false) {
93    return(false);
94  } else {
95    if(!(ptext=decryptTextString(data.substring(tagend+1,ptend),key))) {
96      return(false);
97    }
98  }
99  return("<" + tag_pt + " LOCK=" + lock + " " +
100     "COLLAPSED=" + collapsed + ">" + ptext + "</" + tag_pt + ">");
101}
102
103// for getTagAttr("<FOO ATTR=val>","ATTR"), return "val"
104function getTagAttr(opentag,attr) {
105  var loff=0;
106  if((loff=opentag.indexOf(attr + "=" ))!=-1) {
107    if((t=opentag.indexOf(" ",loff+attr.length+1))!=-1) {
108      return(opentag.substring(loff+attr.length+1,t));
109    } else {
110      return(opentag.substring(loff+attr.length+1,opentag.length-1));
111    }
112  }
113  return(null);
114}
115
116/**
117 * Tries to encrypt a given <SECRET> block. Works and returns synchronously.
118 *
119 * @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.
120 *
121 * @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.
122 */
123function encryptBlock(data) {
124  var tagend=0, ptend=0, lock=null, ctext;
125  var collapsed = "1";
126
127  if((tagend=data.indexOf(">"))==-1) {
128    //crypt_debug("no > in " + data);
129    return(null);
130  }
131  if((ptend=data.lastIndexOf("</"))==-1) {
132    //crypt_debug(" no </ in " + data);
133    return(null);
134  }
135  lock=getTagAttr(data.substring(0,tagend+1),"LOCK");
136  if(lock===null) { lock="default"; }
137
138  collapsed=getTagAttr(data.substring(0,tagend+1),"COLLAPSED");
139  if(collapsed===null || collapsed=="null") { collapsed="1"; }
140
141  var key=getKeyForLock(lock);
142  if(key===false) {
143    return(null);
144  } else {
145    if(!(ctext=encryptTextString(data.substring(tagend+1,ptend),key))) {
146      return(null);
147    }
148    return("<"+tag_enc+" LOCK=" + lock + " " + "COLLAPSED=" + collapsed + ">" + ctext + "</"+tag_enc+">");
149  }
150}
151
152
153/* encrypt the string in text with ascii key in akey
154  modified from Encrypt_Text to expect ascii key and take input params
155  and to return base64 encoded
156*/
157function encryptTextString(ptext,akey) {
158  var v, i, ret, key;
159  var prefix = "#####  Encrypted: decrypt with ";
160  prefix+="http://www.fourmilab.ch/javascrypt/\n";
161  suffix = "#####  End encrypted message\n";
162
163  if (akey.length === 0) {
164    alert("Please specify a key with which to encrypt the message.");
165    return;
166  }
167  if (ptext.length === 0) {
168    alert("No plain text to encrypt!");
169    return;
170  }
171  ret="";
172  key=setKeyFromAscii(akey);
173
174  // addEntroptyTime eventually results in setting of global entropyData
175  // which is used by keyFromEntropy
176  addEntropyTime();
177  prng = new AESprng(keyFromEntropy());
178  var plaintext = encode_utf8(ptext);
179
180  //  Compute MD5 sum of message text and add to header
181
182  md5_init();
183  for (i = 0; i < plaintext.length; i++) {
184    md5_update(plaintext.charCodeAt(i));
185  }
186  md5_finish();
187  var header = "";
188  for (i = 0; i < digestBits.length; i++) {
189    header += String.fromCharCode(digestBits[i]);
190  }
191
192  //  Add message length in bytes to header
193
194  i = plaintext.length;
195  header += String.fromCharCode(i >>> 24);
196  header += String.fromCharCode(i >>> 16);
197  header += String.fromCharCode(i >>> 8);
198  header += String.fromCharCode(i & 0xFF);
199
200  /*  The format of the actual message passed to rijndaelEncrypt
201  is:
202     Bytes  Content
203     0-15   MD5 signature of plaintext
204     16-19  Length of plaintext, big-endian order
205     20-end Plaintext
206
207  Note that this message will be padded with zero bytes
208  to an integral number of AES blocks (blockSizeInBits / 8).
209  This does not include the initial vector for CBC
210  encryption, which is added internally by rijndaelEncrypt.
211  */
212
213  var ct = rijndaelEncrypt(header + plaintext, key, "CBC");
214  delete prng;
215  return(prefix + armour_base64(ct) + suffix);
216}
217
218function decryptTextString(ctext,akey) {
219  key=setKeyFromAscii(akey);
220  var ct=[];
221
222  // remove line breaks
223  ct=disarm_base64(ctext);
224  var result=rijndaelDecrypt(ct,key,"CBC");
225  var header=result.slice(0,20);
226  result=result.slice(20);
227  var dl=(header[16]<<24)|(header[17]<<16)|(header[18]<<8)|header[19];
228
229  if((dl<0)||(dl>result.length)) {
230   // alert("Message (length "+result.length+") != expected (" + dl + ")");
231   dl=result.length;
232  }
233
234  var i,plaintext="";
235  md5_init();
236
237  for(i=0;i<dl;i++) {
238    plaintext+=String.fromCharCode(result[i]);
239    md5_update(result[i]);
240  }
241
242  md5_finish();
243
244  successful = true;
245
246  for(i=0;i<digestBits.length;i++) {
247    if(digestBits[i]!=header[i]) {
248      //crypt_debug("Invalid decryption key.");
249      return(false);
250    }
251  }
252  return(decode_utf8(plaintext));
253}
254