      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 DKEK key wrapper
     25  */
     26
     27
     28
     29 /**
     30  * Class supporting DKEK functions outside the SmartCard-HSM
     31  *
     32  * @constructor
     33  * @param {Crypto} crypto the crypto provider
     34  */
     35 function DKEK(crypto) {
     36 	this.crypto = crypto;
     37 	this.dkek = new ByteString("0000000000000000000000000000000000000000000000000000000000000000", HEX);
     38 }
     39
     40 exports.DKEK = DKEK;
     41
     42
     43
     44 /**
     45  * Import a DKEK share
     46  *
     47  * @param {ByteString} share a 32 byte share
     48  */
     49 DKEK.prototype.importDKEKShare = function(share) {
     50 	assert(share instanceof ByteString, "Share must be of type ByteString");
     51 	assert(share.length == 32, "Share must be 32 byte long");
     52 	var ndkek = this.dkek.xor(share);
     53 	this.dkek.clear();
     54 	this.dkek = ndkek;
     55 }
     56
     57
     58
     59 /**
     60  * Zeroize DKEK
     61  *
     62  */
     63 DKEK.prototype.clear = function() {
     64 	this.dkek.clear();
     65 }
     66
     67
     68
     69 /**
     70  * Return the Key Check Value (KCV) of the internal DKEK
     71  *
     72  * @type ByteString
     73  * @return the KCV
     74  */
     75 DKEK.prototype.getKCV = function() {
     76 	return this.crypto.digest(Crypto.SHA_256, this.dkek).left(8);
     77 }
     78
     79
     80
     81 /**
     82  * Derive the encryption key from the DKEK
     83  *
     84  * @type ByteString
     85  * @return the encryption key
     86  */
     87 DKEK.prototype.getKENC = function() {
     88 	var kencval = this.crypto.digest(Crypto.SHA_256, this.dkek.concat(new ByteString("00000001", HEX)));
     89 	var kenc = new Key();
     90 	kenc.setComponent(Key.AES, kencval);
     91 	return kenc;
     92 }
     93
     94
     95
     96 /**
     97  * Derive the message authentication key from the DKEK
     98  *
     99  * @type ByteString
    100  * @return the message authentication key
    101  */
    102 DKEK.prototype.getKMAC = function() {
    103 	var kmacval = this.crypto.digest(Crypto.SHA_256, this.dkek.concat(new ByteString("00000002", HEX)));
    104 	var kmac = new Key();
    105 	kmac.setComponent(Key.AES, kmacval);
    106 	return kmac;
    107 }
    108
    109
    110
    111 /**
    112  * Derive DKEK share encryption key from password
    113  *
    114  * @param {ByteString} password the password
    115  * @type ByteString
    116  * @return the derived key (32 Byte) concatenated with the IV (16 Byte)
    117  */
    118 DKEK.deriveDKEKShareKey = function(salt, password) {
    119 	var crypto = new Crypto();
    120
    121 	var d = new ByteString("", HEX);
    122 	var keyivbuff = new ByteBuffer(48);
    123
    124 	for (j = 0; j < 3; j++) {
    125 		print("Derive DKEK share encryption key (Step " + (j + 1) + " of 3)...");
    126 		var nd = d.concat(password);
    127 		d.clear();
    128 		d = nd;
    129
    130 		var nd = d.concat(salt);
    131 		d.clear();
    132 		d = nd;
    133
    134 		try	{
    135 			// Try the fast hash available in scdp4j 3.8
    136 			var h = crypto.digest(Crypto.MD5, d, 10000000);
    137 			d.clear();
    138 			d = h;
    139 		}
    140 		catch(e) {
    141 			// Fallback to slow variant
    142 			for (var i = 10000000; i > 0; i--) {
    143 				d = crypto.digest(Crypto.MD5, d);
    144 			}
    145 		}
    146 		keyivbuff.append(d);
    147 	}
    148
    149 	var keyiv = keyivbuff.toByteString();
    150 	keyivbuff.clear();
    151 	return keyiv;
    152 }
    153
    154
    155
    156 /**
    157  * Encrypt a DKEK share
    158  *
    159  * @param {ByteString} keyshare the key share
    160  * @param {ByteString} password the password
    161  * @type ByteString
    162  * @return Encrypted DKEK share value
    163  */
    164 DKEK.encryptKeyShare = function(keyshare, password) {
    165 	assert(keyshare instanceof ByteString, "Argument keyshare must be ByteString");
    166 	assert(keyshare.length == 32, "Argument keyshare must be 32 bytes");
    167 	assert(password instanceof ByteString, "Argument password must be ByteString");
    168
    169 	var crypto = new Crypto();
    170 	var salt = crypto.generateRandom(8);
    171
    172 	var keyiv = DKEK.deriveDKEKShareKey(salt, password);
    173
    174 	var k = new Key();
    175 	k.setComponent(Key.AES, keyiv.bytes(0, 32));
    176 	var iv = keyiv.bytes(32, 16);
    177 	keyiv.clear();
    178
    179 	var plain = keyshare.concat(new ByteString("10101010101010101010101010101010", HEX));
    180 	var cipher = crypto.encrypt(k, Crypto.AES_CBC, plain, iv);
    181 	plain.clear();
    182 	k.getComponent(Key.AES).clear();
    183
    184 	var blob = (new ByteString("Salted__", ASCII)).concat(salt).concat(cipher);
    185 	return blob;
    186 }
    187
    188
    189
    190 /**
    191  * Decrypt a DKEK share
    192  *
    193  * @param {ByteString} keyshare the encrypted key share as read from the .pbe file
    194  * @param {ByteString} password the password
    195  * @type ByteString
    196  * @return plain DKEK value
    197  */
    198 DKEK.decryptKeyShare = function(keyshare, password) {
    199 	assert(password instanceof ByteString, "Argument password must be ByteString");
    200 	if ((keyshare.length != 64) || !keyshare.bytes(0, 8).toString(ASCII).equals("Salted__")) {
    201 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "Does not seem to be an encrypted key share");
    202 	}
    203
    204 	var crypto = new Crypto();
    205 	var salt = keyshare.bytes(8, 8);
    206
    207 	var keyiv = DKEK.deriveDKEKShareKey(salt, password);
    208
    209 	var k = new Key();
    210 	k.setComponent(Key.AES, keyiv.bytes(0, 32));
    211 	var iv = keyiv.bytes(32, 16);
    212 	keyiv.clear();
    213
    214 	var plain = crypto.decrypt(k, Crypto.AES_CBC, keyshare.bytes(16), iv);
    215 	k.getComponent(Key.AES).clear();
    216
    217 	if (!(new ByteString("10101010101010101010101010101010", HEX)).equals(plain.right(16))) {
    218 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "Decryption of DKEK failed. Wrong password ?");
    219 	}
    220
    221 	var val = plain.left(32);
    222 	plain.clear();
    223
    224 	return val;
    225 }
    226
    227
    228
    229 /**
    230  * Test DKEK share encryption / decryption
    231  *
    232  * @private
    233  */
    234 DKEK.testDKEK = function() {
    235 	var crypto = new Crypto();
    236 	var dkek = crypto.generateRandom(32);
    237 	var password = new ByteString("Password", ASCII);
    238 	var enc = DKEK.encryptKeyShare(dkek, password);
    239 	print(enc);
    240 	var plain = DKEK.decryptKeyShare(enc, password);
    241 	assert(dkek.equals(plain), "Reference does not match");
    242 }
    243
    244
    245
    246 /**
    247  * Wrap AES key
    248  *
    249  * @param {Key} key the secret key
    250  * @type ByteString
    251  * @return the secret key wrapped with the DKEK
    252  */
    253 DKEK.prototype.encodeAESKey = function(key) {
    254 	var bb = new ByteBuffer();
    255 	bb.append(this.getKCV());
    256
    257 	assert(key.getType() == Key.SECRET)
    258
    259 	bb.append(15);
    260
    261 	var daid = new ByteString("2.16.840.1.101.3.4.1", OID);
    262 	bb.append(ByteString.valueOf(daid.length, 2));
    263 	bb.append(daid);
    264
    265 	var aaid = new ByteString("10 11 18 99", HEX);
    266 	bb.append(ByteString.valueOf(aaid.length, 2));
    267 	bb.append(aaid);
    268
    269 	bb.append(ByteString.valueOf(0, 2));
    270 	bb.append(ByteString.valueOf(0, 2));
    271
    272 	var kb = new ByteBuffer(256);
    273 	kb.append(this.crypto.generateRandom(8));
    274
    275 	kb.append(ByteString.valueOf(key.getSize() / 8, 2));
    276 	kb.append(key.getComponent(Key.AES));
    277
    278 	var unpadded = kb.toByteString();
    279 	var plain = unpadded.pad(Crypto.ISO9797_METHOD_2);
    280 	unpadded.clear();
    281
    282 	if (plain.length & 0xF) {		// pad() padds to 8 byte blocks, but we 16 byte blocks
    283 		var nplain = plain.concat(new ByteString("0000000000000000", HEX));
    284 		plain.clear();
    285 		plain = nplain;
    286 	}
    287
    288 	var iv = new ByteString("00000000000000000000000000000000", HEX);
    289 	var kenc = this.getKENC();
    290 	var cipher = this.crypto.encrypt(kenc, Crypto.AES_CBC, plain, iv);
    291 	kenc.getComponent(Key.AES).clear();
    292 	plain.clear();
    293
    294 	bb.append(cipher);
    295
    296 	var kmac = this.getKMAC();
    297 	bb.append(this.crypto.sign(kmac, Crypto.AES_CMAC, bb.toByteString()));
    298 	kmac.getComponent(Key.AES).clear();
    299
    300 	return bb.toByteString();
    301 }
    302
    303
    304
    305 /**
    306  * Wrap RSA or ECC key
    307  *
    308  * @param {Key} pri the private key in CRT format
    309  * @param {Key} pub the public key
    310  * @type Key
    311  * @return the private key in private exponent / modulus format
    312  */
    313 DKEK.prototype.convertCRT2PEM = function(pri, pub) {
    314 	var n = pub.getComponent(Key.MODULUS);
    315 	var r = ByteString.valueOf(0).concat(n);
    316 	r = r.sub(ByteString.valueOf(0).concat(pri.getComponent(Key.CRT_P)));
    317 	r = r.sub(ByteString.valueOf(0).concat(pri.getComponent(Key.CRT_Q)));
    318 	r = r.biAdd(ByteString.valueOf(1));
    319 	var e = pub.getComponent(Key.EXPONENT);
    320 	var d = e.modInverse(r);
    321
    322 	// Strip leading zero in private exponent
    323 	if (d.byteAt(0) == 0) {
    324 		d = d.bytes(1);
    325 	}
    326
    327 	var nk = new Key();
    328 	nk.setType(Key.PRIVATE);
    329 	nk.setComponent(Key.MODULUS, n);
    330 	nk.setComponent(Key.EXPONENT, d);
    331 	return nk;
    332 }
    333
    334
    335
    336 /**
    337  * Wrap RSA or ECC key
    338  *
    339  * @param {Key} pri the private key
    340  * @param {Key} pub the public key
    341  * @type ByteString
    342  * @return the private key wrapped with the DKEK
    343  */
    344 DKEK.prototype.encodeKey = function(pri, pub) {
    345 	var bb = new ByteBuffer();
    346 	bb.append(this.getKCV());
    347
    348 	var mod = pub.getComponent(Key.MODULUS);
    349 	if (mod) {
    350 		// Convert RSA keys larger than 2048 bit and in CRT format
    351 		if ((mod.length > 256) && pri.getComponent(Key.CRT_P)) {
    352 			pri = this.convertCRT2PEM(pri, pub);
    353 		}
    354 		if (pri.getComponent(Key.CRT_P)) {
    355 			bb.append(6);
    356 		} else {
    357 			bb.append(5);
    358 		}
    359 		var algo = new ByteString("id-TA-RSA-v1-5-SHA-256", OID);
    360 	} else {
    361 		bb.append(12);
    362 		var algo = new ByteString("id-TA-ECDSA-SHA-256", OID);
    363 	}
    364
    365 	bb.append(ByteString.valueOf(algo.length, 2));
    366 	bb.append(algo);
    367
    368 	bb.append(ByteString.valueOf(0, 2));
    369 	bb.append(ByteString.valueOf(0, 2));
    370 	bb.append(ByteString.valueOf(0, 2));
    371
    372 	var kb = new ByteBuffer(4096);
    373 	kb.append(this.crypto.generateRandom(8));
    374
    375 	if (pub.getComponent(Key.MODULUS)) {
    376 		kb.append(ByteString.valueOf(pub.getComponent(Key.MODULUS).length << 3, 2));
    377
    378 		if (pri.getComponent(Key.CRT_P)) {
    379 			var c = pri.getComponent(Key.CRT_DP1);
    380 			kb.append(ByteString.valueOf(c.length, 2));
    381 			kb.append(c);
    382
    383 			var c = pri.getComponent(Key.CRT_DQ1);
    384 			kb.append(ByteString.valueOf(c.length, 2));
    385 			kb.append(c);
    386
    387 			var c = pri.getComponent(Key.CRT_P);
    388 			kb.append(ByteString.valueOf(c.length, 2));
    389 			kb.append(c);
    390
    391 			var c = pri.getComponent(Key.CRT_PQ);
    392 			kb.append(ByteString.valueOf(c.length, 2));
    393 			kb.append(c);
    394
    395 			var c = pri.getComponent(Key.CRT_Q);
    396 			kb.append(ByteString.valueOf(c.length, 2));
    397 			kb.append(c);
    398 		} else {
    399 			var c = pri.getComponent(Key.EXPONENT);
    400 			kb.append(ByteString.valueOf(c.length, 2));
    401 			kb.append(c);
    402 		}
    403
    404 		var c = pub.getComponent(Key.MODULUS);
    405 		kb.append(ByteString.valueOf(c.length, 2));
    406 		kb.append(c);
    407
    408 		var c = pub.getComponent(Key.EXPONENT);
    409 		kb.append(ByteString.valueOf(c.length, 2));
    410 		kb.append(c);
    411 	} else {
    412 		kb.append(ByteString.valueOf(pub.getComponent(Key.ECC_P).length << 3, 2));
    413
    414 		var c = pri.getComponent(Key.ECC_A);
    415 		kb.append(ByteString.valueOf(c.length, 2));
    416 		kb.append(c);
    417
    418 		var c = pri.getComponent(Key.ECC_B);
    419 		kb.append(ByteString.valueOf(c.length, 2));
    420 		kb.append(c);
    421
    422 		var c = pri.getComponent(Key.ECC_P);
    423 		kb.append(ByteString.valueOf(c.length, 2));
    424 		kb.append(c);
    425
    426 		var c = pri.getComponent(Key.ECC_N);
    427 		kb.append(ByteString.valueOf(c.length, 2));
    428 		kb.append(c);
    429
    430 		var c = (new ByteString("04", HEX)).concat(pri.getComponent(Key.ECC_GX)).concat(pri.getComponent(Key.ECC_GY));
    431 		kb.append(ByteString.valueOf(c.length, 2));
    432 		kb.append(c);
    433
    434 		var c = pri.getComponent(Key.ECC_D);
    435 		kb.append(ByteString.valueOf(c.length, 2));
    436 		kb.append(c);
    437
    438 		var c = (new ByteString("04", HEX)).concat(pub.getComponent(Key.ECC_QX)).concat(pub.getComponent(Key.ECC_QY));
    439 		kb.append(ByteString.valueOf(c.length, 2));
    440 		kb.append(c);
    441 	}
    442
    443 	var unpadded = kb.toByteString();
    444 	var plain = unpadded.pad(Crypto.ISO9797_METHOD_2);
    445 	unpadded.clear();
    446
    447 	if (plain.length & 0xF) {		// pad() padds to 8 byte blocks, but we 16 byte blocks
    448 		var nplain = plain.concat(new ByteString("0000000000000000", HEX));
    449 		plain.clear();
    450 		plain = nplain;
    451 	}
    452 	// print(plain);
    453
    454 	var iv = new ByteString("00000000000000000000000000000000", HEX);
    455 	var kenc = this.getKENC();
    456 	var cipher = this.crypto.encrypt(kenc, Crypto.AES_CBC, plain, iv);
    457 	kenc.getComponent(Key.AES).clear();
    458 	plain.clear();
    459
    460 	bb.append(cipher);
    461
    462 	var kmac = this.getKMAC();
    463 	bb.append(this.crypto.sign(kmac, Crypto.AES_CMAC, bb.toByteString()));
    464 	kmac.getComponent(Key.AES).clear();
    465
    466 //	this.dumpKeyBLOB(bb.toByteString());
    467 	return bb.toByteString();
    468 }
    469
    470 DKEK.prototype.encodeRSAKey = DKEK.prototype.encodeKey;
    471
    472
    473
    474 DKEK.prototype.dumpKeyBLOB = function(keyblob) {
    475 	// Verify MAC on last 16 byte of blob
    476
    477 	var macok = this.crypto.verify(this.getKMAC(), Crypto.AES_CMAC, keyblob.bytes(0, keyblob.length - 16), keyblob.right(16));
    478
    479 	var keytype = keyblob.byteAt(8);
    480 	print("Values from key blob:");
    481 	print("---------------------");
    482 	print("Checking the MAC      : " + (macok ? "Passed" : "Failed"));
    483 	print("KCV                   : " + keyblob.bytes(0, 8).toString(HEX) + "    [Must match the KCV of the DKEK for import]");
    484 	print("Key type              : " + keytype + "    [5=RSA, 6=RSA-CRT, 12=ECC, 15=AES]");
    485
    486 	var ofs = 9;
    487 	var len = keyblob.bytes(ofs, 2).toUnsigned();
    488
    489 	if ((keytype == 15) && (keyblob.byteAt(ofs + 2) != 0x60)) {
    490 		print("Default Algorithm ID  : " + keyblob.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")     [Wrong encoding in V3.0 to V3.2]");
    491 	} else {
    492 		print("Default Algorithm ID  : " + keyblob.bytes(ofs + 2, len).toString(OID) + " (" + len + ")     [Default algorithm]");
    493 	}
    494
    495 	ofs += len + 2;
    496 	var len = keyblob.bytes(ofs, 2).toUnsigned();
    497
    498 	print("Allowed Algorithm IDs : " + keyblob.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    499
    500 	ofs += len + 2;
    501 	var len = keyblob.bytes(ofs, 2).toUnsigned();
    502
    503 	print("Access Conditions     : " + keyblob.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")    [Not used]");
    504
    505 	ofs += len + 2;
    506 	var len = keyblob.bytes(ofs, 2).toUnsigned();
    507
    508 	print("Key OID               : " + keyblob.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")    [Not used]");
    509
    510 	ofs += len + 2;
    511
    512
    513 	// Decrypt key data
    514
    515 	var iv = new ByteString("00000000000000000000000000000000", HEX);
    516 	var plainkey = this.crypto.decrypt(this.getKENC(), Crypto.AES_CBC, keyblob.bytes(ofs, keyblob.length - 16 - ofs), iv);
    517 	// print(plainkey);
    518
    519 	print("Randomize             : " + plainkey.bytes(0, 8).toString(HEX) + "    [Random data prepended at export]");
    520
    521 	var keysize = plainkey.bytes(8, 2).toUnsigned();
    522 	print("Key size              : " + keysize + "    [Key size in bits (ECC/RSA) or bytes (AES)]");
    523
    524 	var ofs = 10;
    525
    526 	if (keytype == 5) {
    527 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    528
    529 		print("Private Exponent      : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    530
    531 		ofs += len + 2;
    532 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    533
    534 		print("Modulus               : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    535
    536 		ofs += len + 2;
    537 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    538
    539 		print("Public Exponent       : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    540 	} else if (keytype == 6) {
    541 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    542
    543 		print("DP1 = d mod (p - 1)   : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    544
    545 		ofs += len + 2;
    546 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    547
    548 		print("DQ1 = d mod (q - 1)   : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    549
    550 		ofs += len + 2;
    551 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    552
    553 		print("Prime factor p        : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    554
    555 		ofs += len + 2;
    556 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    557
    558 		print("PQ = q - 1 mod p      : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    559
    560 		ofs += len + 2;
    561 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    562
    563 		print("Prime factor q        : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    564
    565 		ofs += len + 2;
    566 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    567
    568 		print("Modulus               : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    569
    570 		ofs += len + 2;
    571 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    572
    573 		print("Public Exponent       : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    574 	} else if (keytype == 12) {
    575 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    576
    577 		print("A                     : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    578
    579 		ofs += len + 2;
    580 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    581
    582 		print("B                     : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    583
    584 		ofs += len + 2;
    585 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    586
    587 		print("Prime factor P        : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    588
    589 		ofs += len + 2;
    590 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    591
    592 		print("Order                 : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    593
    594 		ofs += len + 2;
    595 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    596
    597 		print("Generator G           : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    598
    599 		ofs += len + 2;
    600 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    601
    602 		print("Secret D              : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    603
    604 		ofs += len + 2;
    605 		var len = plainkey.bytes(ofs, 2).toUnsigned();
    606
    607 		print("Public Point Q        : " + plainkey.bytes(ofs + 2, len).toString(HEX) + " (" + len + ")");
    608 	} else if (keytype == 15) {
    609 		print("Key Value             : " + plainkey.bytes(ofs, keysize).toString(HEX));
    610 	} else {
    611 		throw new GPError(module.id, GPError.INVALID_DATA, 0, "Unknown key type " + keytype);
    612 	}
    613 }
    614