      1 /**
      2  *  ---------
      3  * |.##> <##.|  SmartCard-HSM Support Scripts
      4  * |#       #|
      5  * |#       #|  Copyright (c) 2011-2012 CardContact Software & System Consulting
      6  * |'##> <##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
      7  *  ---------
      8  *
      9  *  This file is part of OpenSCDP.
     10  *
     11  *  OpenSCDP is free software; you can redistribute it and/or modify
     12  *  it under the terms of the GNU General Public License version 2 as
     13  *  published by the Free Software Foundation.
     14  *
     15  *  OpenSCDP is distributed in the hope that it will be useful,
     16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18  *  GNU General Public License for more details.
     19  *
     20  *  You should have received a copy of the GNU General Public License
     21  *  along with OpenSCDP; if not, write to the Free Software
     22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     23  *
     24  * @fileoverview PaperKey Encoder and Decoder
     25  */
     26
     27
     28
     29 /**
     30  * Create a PaperKey encoder/decoder
     31  *
     32  * @class Encoder / Decoder for PaperKeys
     33  * @constructor
     34  */
     35 function PaperKeyEncoding() {
     36 	this.base  = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
     37 	this.alpha = "ABCDEFGH!JKLMN*PQRSTUVWXYZabcdefghijk%mnopqrstuvwxyz0123456789+/";
     38 }
     39
     40 exports.PaperKeyEncoding = PaperKeyEncoding;
     41
     42 PaperKeyEncoding.E_INV_LENGTH = "Input does not contain 12 characters";
     43 PaperKeyEncoding.E_WRONG_SEGMENT = "Wrong segment number";
     44 PaperKeyEncoding.E_CHECK_FAILED = "Check value failed";
     45
     46
     47
     48 /**
     49  * Encode an 8 byte segment in human readable form.
     50  *
     51  * Based on BASE64 encoding with visually confusable characters replaced.
     52  *
     53  * I is replaced by !
     54  * O is replaced by *
     55  * l is replaced by %
     56  *
     57  * A LUHN-64 character is appended.
     58  *
     59  * A segment identifier is encoded in 2 bit at the end of the encoded 64 bit string.
     60  *
     61  * @param {Number} segment
     62  * @param {ByteString} data
     63  * @type String
     64  * @return the encoded string
     65  */
     66 PaperKeyEncoding.prototype.encodeSegment = function(segment, data) {
     67 	if (typeof(segment) != "number" || segment < 0) {
     68 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "segment must be a positive number");
     69 	}
     70
     71 	if (!(data instanceof ByteString)) {
     72 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "data must be ByteString");
     73 	}
     74
     75 	if (data.length != 8) {
     76 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "data must be 8 byte");
     77 	}
     78
     79 	var bb = new ByteBuffer();
     80
     81 	// Append segment number's less 2 bit
     82 	data = data.concat(ByteString.valueOf((segment & 3) << 6));
     83
     84 //	print(data.toString(BASE64));
     85
     86 	var cs = 0;
     87 	var cf = 1;
     88
     89 	for (var i = 0; i < data.length; i += 3) {
     90 		var a = data.bytes(i, 3).toUnsigned();
     91 		var s = (a >> 18) & 0x3F;
     92
     93 		cs += s << cf;
     94 //		print("s = " + s + " cf = " + cf + " cs = " + cs);
     95 		cf = cf ^ 1;
     96
     97 		bb.append(this.alpha.charAt(s));
     98
     99 		s = (a >> 12) & 0x3F;
    100
    101 		cs += s << cf;
    102 //		print("s = " + s + " cf = " + cf + " cs = " + cs);
    103 		cf = cf ^ 1;
    104
    105 		bb.append(this.alpha.charAt(s));
    106
    107 		s = (a >> 6) & 0x3F;
    108
    109 		cs += s << cf;
    110 //		print("s = " + s + " cf = " + cf + " cs = " + cs);
    111 		cf = cf ^ 1;
    112
    113 		bb.append(this.alpha.charAt(s));
    114
    115 		if (i <= 3) {
    116 			s = a & 0x3F;
    117
    118 			cs += s << cf;
    119 //			print("s = " + s + " cf = " + cf + " cs = " + cs);
    120 			cf = cf ^ 1;
    121
    122 			bb.append(this.alpha.charAt(s));
    123 		}
    124 	}
    125
    126 	bb.append(this.alpha.charAt(cs & 0x3F));
    127
    128 	return bb.toString(ASCII);
    129 }
    130
    131
    132
    133 /**
    134  * Decode an 8 byte segment from 12 input characters.
    135  *
    136  * Characters not in the alphabet are ignored.
    137  *
    138  * Throws GPError with GPError.INVALID_LENGTH if the input does not contain at least 12 usable characters.
    139  * Throws GPError with GPError.SIGNATURE_FAILED if the check digit based validation fails.
    140  * Throws GPError with GPError.INVALID_DATA if the segment number does not match the expected value.
    141  *
    142  * @param {Number} segment
    143  * @param {String} str the user input
    144  * @type ByteString
    145  * @return the 8 byte segment
    146  */
    147 PaperKeyEncoding.prototype.decodeSegment = function(segment, str) {
    148 	if (typeof(segment) != "number" || segment < 0) {
    149 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "segment must be a positive number");
    150 	}
    151
    152 	if (typeof(str) != "string") {
    153 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "str must be String");
    154 	}
    155
    156 //	print(str);
    157 	var bb = new ByteBuffer();
    158
    159 	var cs = 0;
    160 	var cf = 1;
    161 	var a = 0;
    162 	var p = 0;
    163
    164 	for (var i = 0; i < 12; i++) {
    165 		do	{
    166 			var ch = str.charAt(p++);
    167 			if (ch == "") {
    168 				throw new GPError(module.id, GPError.INVALID_LENGTH, 0, PaperKeyEncoding.E_INV_LENGTH);
    169 			}
    170 			var v = this.alpha.indexOf(ch);
    171 			if (v < 0) {
    172 				var v = this.base.indexOf(ch);
    173 			}
    174 		} while (v < 0);
    175
    176 		cs += v << cf;
    177 //		print("v = " + v + " cf = " + cf + " cs = " + cs);
    178 		cf = cf ^ 1;
    179
    180 		a = (a << 6) | v;
    181
    182 		if ((i + 1 & 3) == 0) {
    183 			bb.append(ByteString.valueOf(a, 3));
    184 			a = 0;
    185 		}
    186 	}
    187
    188 	bb.append(ByteString.valueOf(a, 3));
    189
    190 	cs -= v << (1 - cf);
    191 	if (v != (cs & 0x3F)) {
    192 		throw new GPError(module.id, GPError.SIGNATURE_FAILED, 0, PaperKeyEncoding.E_CHECK_FAILED);
    193 	}
    194
    195 //	print(bb);
    196 	if ((bb.byteAt(8) >> 6) != (segment & 3)) {
    197 		throw new GPError(module.id, GPError.INVALID_DATA, 0, PaperKeyEncoding.E_WRONG_SEGMENT);
    198 	}
    199
    200 	var r = bb.toByteString().left(8);
    201 	bb.clear();
    202 	return r;
    203 }
    204
    205
    206
    207 /**
    208  * Format a segment by splitting the input after each 4 characters and inserting " - "
    209  *
    210  * @param {String} str the unformatted string
    211  * @type String
    212  * @return the formatted string
    213  */
    214 PaperKeyEncoding.formatSegment = function(str) {
    215 	var res = "";
    216 	var j = 0;
    217 	while(j < str.length) {
    218 		if (j > 0) {
    219 			res += " - ";
    220 		}
    221 		res += str.substr(j, 4);
    222 		j += 4;
    223 	}
    224 	return res;
    225 }
    226
    227
    228
    229 /**
    230  * Dump a key
    231  *
    232  * @param {ByteString} data the key value (length must be a multiple of 8)
    233  * @type String
    234  * @return the formatted key with 3 groups of 4 digits per line
    235  */
    236 PaperKeyEncoding.dumpKey = function(data) {
    237 	if (!(data instanceof ByteString)) {
    238 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "data must be ByteString");
    239 	}
    240
    241 	if (data.length & 7 != 0) {
    242 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "data must be a multiple of 8 byte");
    243 	}
    244
    245 	var pc = new PaperKeyEncoding();
    246
    247 	var segments = data.length >> 3;
    248
    249 	var str = "";
    250 	for (var i = 0; i < segments; i++) {
    251 		var s = pc.encodeSegment(i, data.bytes(i * 8, 8));
    252 		str += PaperKeyEncoding.formatSegment(s) + "\n";
    253 	}
    254 	return str;
    255 }
    256
    257
    258
    259 PaperKeyEncoding.test = function() {
    260 	var pc = new PaperKeyEncoding();
    261
    262 	var ref = ByteString.valueOf(0, 8);
    263 	var enc = pc.encodeSegment(0, ref);
    264 	assert("AAAAAAAAAAAA" == enc, "All zero seg 0 encoding failed");
    265 	var res = pc.decodeSegment(0, enc);
    266 	assert(ref.equals(res), "Decoding failed");
    267
    268 	var enc = pc.encodeSegment(3, ref);
    269 	assert("AAAAAAAAAADG" == enc, "All zero seg 3 encoding failed");
    270 	var res = pc.decodeSegment(3, enc);
    271 	assert(ref.equals(res), "Decoding failed");
    272
    273 	var enc = pc.encodeSegment(7, ref);
    274 	assert("AAAAAAAAAADG" == enc, "All zero seg 7 encoding failed");
    275
    276 	try {
    277 		var res = pc.decodeSegment(6, enc);
    278 		assert(false, "Did not detect wrong segment");
    279 	}
    280 	catch(e) {
    281 		assert(e.error == GPError.INVALID_DATA, "Must be GPError.INVALID_DATA");
    282 		assert(e.message == PaperKeyEncoding.E_WRONG_SEGMENT, "Must be PaperKeyEncoding.E_WRONG_SEGMENT");
    283 	}
    284
    285 	try {
    286 		var res = pc.decodeSegment(7, "AAAAAAAAAADA");
    287 		assert(false, "Did not detect wrong check digit");
    288 	}
    289 	catch(e) {
    290 		assert(e.error == GPError.SIGNATURE_FAILED, "Must be GPError.SIGNATURE_FAILED");
    291 		assert(e.message == PaperKeyEncoding.E_CHECK_FAILED, "Must be PaperKeyEncoding.E_CHECK_FAILED");
    292 	}
    293
    294 	var ref = ref.not();
    295 	var enc = pc.encodeSegment(0, ref);
    296 	assert("//////////8p" == enc, "All one seg 0 encoding failed");
    297 	var res = pc.decodeSegment(0, enc);
    298 	assert(ref.equals(res), "Decoding failed");
    299 	var enc = pc.encodeSegment(3, ref);
    300 	assert("///////////v" == enc, "All one seg 3 encoding failed");
    301 	var res = pc.decodeSegment(3, enc);
    302 	assert(ref.equals(res), "Decoding failed");
    303
    304 	var ref = ByteString.valueOf(0x04).concat(ByteString.valueOf(0, 7));
    305 	var enc = pc.encodeSegment(0, ref);
    306 	assert("BAAAAAAAAAAC" == enc, "First one encoding failed");
    307 	var res = pc.decodeSegment(0, enc);
    308 	assert(ref.equals(res), "Decoding failed");
    309
    310 	var ref = ByteString.valueOf(0x10, 2).concat(ByteString.valueOf(0, 6));
    311 	var enc = pc.encodeSegment(0, ref);
    312 	assert("ABAAAAAAAAAB" == enc, "Second one encoding failed");
    313 	var res = pc.decodeSegment(0, enc);
    314 	assert(ref.equals(res), "Decoding failed");
    315
    316 	var ref = ByteString.valueOf(0xFC).concat(ByteString.valueOf(0, 7));
    317 	var enc = pc.encodeSegment(0, ref);
    318 	assert("/AAAAAAAAAA+" == enc, "First max encoding failed");
    319 	var res = pc.decodeSegment(0, enc);
    320 	assert(ref.equals(res), "Decoding failed");
    321
    322 	var ref = ByteString.valueOf(0x03F0, 2).concat(ByteString.valueOf(0, 6));
    323 	var enc = pc.encodeSegment(0, ref);
    324 	assert("A/AAAAAAAAA/" == enc, "Second max encoding failed");
    325 	var res = pc.decodeSegment(0, enc);
    326 	assert(ref.equals(res), "Decoding failed");
    327
    328
    329
    330 	var crypto = new Crypto();
    331
    332 	var kv = crypto.generateRandom(32);
    333 	print(PaperKeyEncoding.dumpKey(kv));
    334
    335 	while (true) {
    336 		var kv = crypto.generateRandom(8);
    337 		var str = PaperKeyEncoding.formatSegment(pc.encodeSegment(0, kv));
    338 		print(str);
    339 		var inp = Dialog.prompt("Enter segment " + str, "");
    340 		if (inp == null) {
    341 			throw new Error("User abort");
    342 		}
    343 		try	{
    344 			var val = pc.decodeSegment(0, inp);
    345 			if (!kv.equals(val)) {
    346 				throw new GPError(module.id, GPError.INVALID_DATA, 0, "Entered value does not match key");
    347 			}
    348 		}
    349 		catch(e) {
    350 			Dialog.prompt(e.message);
    351 		}
    352 	}
    353 }
    354