1// This file contains a flat high-level programming interface for dokucrypt3. 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