UNPKG

blocktrail-sdk

Version:

BlockTrail's Developer Friendly API binding for NodeJS

431 lines (339 loc) 12.7 kB
/** * Counter with CBC-MAC (CCM) * * Due to JS limitations (52 bits of Number precision) maximum encrypted message length * is limited to ~4 PiB ( 2^52 - 16 ) per `nonce`-`key` pair. * That also limits `lengthSize` parameter maximum value to 7 (not 8 as described in RFC3610). * * Additional authenticated data `adata` maximum length is choosen to be no more than 65279 bytes ( 2^16 - 2^8 ), * wich is considered enough for the most of use-cases. * * And one more important thing: in case of progressive ciphering of a data stream (in other * words when data can't be held in-memory at a whole and are ciphered chunk-by-chunk) * you have to know the `dataLength` in advance and pass that value to the cipher options. */ function _cbc_mac_process ( data ) { var heap = this.heap, asm = this.asm, dpos = 0, dlen = data.length || 0, wlen = 0; while ( dlen > 0 ) { wlen = _heap_write( heap, 0, data, dpos, dlen ); while ( wlen & 15 ) heap[ wlen++ ] = 0; dpos += wlen; dlen -= wlen; asm.mac( AES_asm.MAC.CBC, AES_asm.HEAP_DATA, wlen ); } } var _AES_CCM_adata_maxLength = 65279, // 2^16 - 2^8 _AES_CCM_data_maxLength = 4503599627370480; // 2^52 - 2^4 function AES_CCM ( options ) { this.tagSize = 16; this.lengthSize = 4; this.nonce = null; this.adata = null; this.iv = null; this.counter = 1; this.dataLength = -1; AES.call( this, options ); this.mode = 'CCM'; } function AES_CCM_Encrypt ( options ) { AES_CCM.call( this, options ); } function AES_CCM_Decrypt ( options ) { AES_CCM.call( this, options ); } function AES_CCM_calculate_iv () { var nonce = this.nonce, adata = this.adata, tagSize = this.tagSize, lengthSize = this.lengthSize, dataLength = this.dataLength; var data = new Uint8Array( 16 + ( adata ? 2 + adata.length : 0 ) ); // B0: flags(adata?, M', L'), nonce, len(data) data[0] = ( adata ? 64 : 0 ) | ( (tagSize-2)<<2 ) | ( lengthSize-1 ); data.set( nonce, 1 ); if (lengthSize > 6) data[9] = ( dataLength / 0x100000000 )>>>16&15; if (lengthSize > 5) data[10] = ( dataLength / 0x100000000 )>>>8&255; if (lengthSize > 4) data[11] = ( dataLength / 0x100000000 )&255; if (lengthSize > 3) data[12] = dataLength>>>24; if (lengthSize > 2) data[13] = dataLength>>>16&255; data[14] = dataLength>>>8&255; data[15] = dataLength&255; // B*: len(adata), adata if ( adata ) { data[16] = adata.length>>>8&255; data[17] = adata.length&255; data.set( adata, 18 ); } _cbc_mac_process.call( this, data ); this.asm.get_state( AES_asm.HEAP_DATA ); this.iv = new Uint8Array( this.heap.subarray( 0, 16 ) ); } function AES_CCM_reset ( options ) { options = options || {}; AES_reset.call( this, options ); var lengthSize = options.lengthSize, tagSize = options.tagSize, dataLength = options.dataLength, nonce = options.nonce, counter = options.counter, adata = options.adata, iv = options.iv; if ( tagSize !== undefined ) { if ( !is_number(tagSize) ) throw new TypeError("tagSize must be a number"); if ( tagSize < 4 || tagSize > 16 || tagSize & 1 ) throw new IllegalArgumentError("illegal tagSize value"); this.tagSize = tagSize; } else { this.tagSize = 16; } if ( nonce !== undefined ) { if ( is_bytes(nonce) || is_buffer(nonce) ) { nonce = new Uint8Array(nonce); } else if ( is_string(nonce) ) { nonce = string_to_bytes(nonce); } else { throw new TypeError("unexpected nonce type"); } if ( nonce.length < 8 || nonce.length > 13 ) throw new IllegalArgumentError("illegal nonce length"); this.nonce = nonce; this.lengthSize = lengthSize = 15 - nonce.length; nonce = new Uint8Array( nonce.length + 1 ); nonce[0] = lengthSize - 1, nonce.set( this.nonce, 1 ); } else { throw new Error("nonce is required"); } // Either counter, iv if ( iv !== undefined ) { if ( adata !== undefined ) throw new IllegalStateError("you should specify either adata or iv, not both"); if ( !is_number(counter) ) throw new TypeError("counter must be a number"); if ( counter < 1 || counter >= ( Math.pow( 2, 8*lengthSize ) - 16 ) ) throw new IllegalArgumentError("illegal counter value"); this.counter = counter; } // Or adata, dataLength else if ( adata !== undefined && adata !== null ) { if ( is_bytes(adata) || is_buffer(adata) ) { adata = new Uint8Array(adata); } else if ( is_string(adata) ) { adata = string_to_bytes(adata); } else { throw new TypeError("unexpected adata type"); } if ( adata.length > _AES_CCM_adata_maxLength ) throw new IllegalArgumentError("illegal adata length"); if ( !is_number(dataLength) ) throw new TypeError("dataLength must be a number"); if ( dataLength < 0 || dataLength > _AES_CCM_data_maxLength || dataLength > ( Math.pow( 2, 8*lengthSize ) - 16 ) ) throw new IllegalArgumentError("illegal dataLength value"); this.adata = adata.length ? adata : null; this.dataLength = dataLength; this.counter = counter = 1; AES_CCM_calculate_iv.call(this); iv = this.iv; } // Assume adata is empty, check dataLength else { if ( !is_number(dataLength) ) throw new TypeError("dataLength must be a number"); if ( dataLength < 0 || dataLength > _AES_CCM_data_maxLength || dataLength > ( Math.pow( 2, 8*lengthSize ) - 16 ) ) throw new IllegalArgumentError("illegal dataLength value"); this.adata = null; this.dataLength = dataLength; this.counter = counter = 1; AES_CCM_calculate_iv.call(this); iv = this.iv; } AES_set_iv.call( this, iv ); AES_CTR_set_options.call( this, nonce, counter, 8*lengthSize ); return this; } function AES_CCM_Encrypt_process ( data ) { if ( is_string(data) ) data = string_to_bytes(data); if ( is_buffer(data) ) data = new Uint8Array(data); if ( !is_bytes(data) ) throw new TypeError("data isn't of expected type"); var dpos = 0, dlen = data.length || 0, asm = this.asm, heap = this.heap, counter = this.counter, pos = this.pos, len = this.len, rpos = 0, rlen = ( len + dlen ) & -16, wlen = 0; if ( ((counter-1)<<4) + len + dlen > _AES_CCM_data_maxLength ) // ??? should check against lengthSize throw new RangeError("counter overflow"); var result = new Uint8Array(rlen); while ( dlen > 0 ) { wlen = _heap_write( heap, pos+len, data, dpos, dlen ); len += wlen; dpos += wlen; dlen -= wlen; wlen = asm.mac( AES_asm.MAC.CBC, AES_asm.HEAP_DATA + pos, len ); wlen = asm.cipher( AES_asm.ENC.CTR, AES_asm.HEAP_DATA + pos, wlen ); if ( wlen ) result.set( heap.subarray( pos, pos + wlen ), rpos ); counter += (wlen>>>4); rpos += wlen; if ( wlen < len ) { pos += wlen; len -= wlen; } else { pos = 0; len = 0; } } this.result = result; this.counter = counter; this.pos = pos; this.len = len; return this; } function AES_CCM_Encrypt_finish () { var asm = this.asm, heap = this.heap, tagSize = this.tagSize, pos = this.pos, len = this.len, wlen = 0; var result = new Uint8Array( len + tagSize ); for ( var i = len; i & 15; i++ ) heap[ pos + i ] = 0; wlen = asm.mac( AES_asm.MAC.CBC, AES_asm.HEAP_DATA + pos, i ); wlen = asm.cipher( AES_asm.ENC.CTR, AES_asm.HEAP_DATA + pos, i ); if ( len ) result.set( heap.subarray( pos, pos + len ) ); asm.set_counter( 0, 0, 0, 0 ); asm.get_iv( AES_asm.HEAP_DATA ); asm.cipher( AES_asm.ENC.CTR, AES_asm.HEAP_DATA, 16 ); result.set( heap.subarray( 0, tagSize ), len ); this.result = result; this.counter = 1; this.pos = 0; this.len = 0; return this; } function AES_CCM_encrypt ( data ) { this.dataLength = data.length || 0; var result1 = AES_CCM_Encrypt_process.call( this, data ).result, result2 = AES_CCM_Encrypt_finish.call(this).result, result; result = new Uint8Array( result1.length + result2.length ); if ( result1.length ) result.set(result1); if ( result2.length ) result.set( result2, result1.length ); this.result = result; return this; } function AES_CCM_Decrypt_process ( data ) { if ( is_string(data) ) data = string_to_bytes(data); if ( is_buffer(data) ) data = new Uint8Array(data); if ( !is_bytes(data) ) throw new TypeError("data isn't of expected type"); var dpos = 0, dlen = data.length || 0, asm = this.asm, heap = this.heap, counter = this.counter, tagSize = this.tagSize, pos = this.pos, len = this.len, rpos = 0, rlen = len + dlen > tagSize ? ( len + dlen - tagSize ) & -16 : 0, tlen = len + dlen - rlen, wlen = 0; if ( ((counter-1)<<4) + len + dlen > _AES_CCM_data_maxLength ) throw new RangeError("counter overflow"); var result = new Uint8Array(rlen); while ( dlen > tlen ) { wlen = _heap_write( heap, pos+len, data, dpos, dlen-tlen ); len += wlen; dpos += wlen; dlen -= wlen; wlen = asm.cipher( AES_asm.DEC.CTR, AES_asm.HEAP_DATA + pos, wlen ); wlen = asm.mac( AES_asm.MAC.CBC, AES_asm.HEAP_DATA + pos, wlen ); if ( wlen ) result.set( heap.subarray( pos, pos+wlen ), rpos ); counter += (wlen>>>4); rpos += wlen; pos = 0; len = 0; } if ( dlen > 0 ) { len += _heap_write( heap, 0, data, dpos, dlen ); } this.result = result; this.counter = counter; this.pos = pos; this.len = len; return this; } function AES_CCM_Decrypt_finish () { var asm = this.asm, heap = this.heap, tagSize = this.tagSize, pos = this.pos, len = this.len, rlen = len - tagSize, wlen = 0; if ( len < tagSize ) throw new IllegalStateError("authentication tag not found"); var result = new Uint8Array(rlen), atag = new Uint8Array( heap.subarray( pos+rlen, pos+len ) ); wlen = asm.cipher( AES_asm.DEC.CTR, AES_asm.HEAP_DATA + pos, (rlen + 15) & -16 ); result.set( heap.subarray( pos, pos + rlen ) ); for ( var i = rlen; i & 15; i++ ) heap[ pos + i ] = 0; wlen = asm.mac( AES_asm.MAC.CBC, AES_asm.HEAP_DATA + pos, i ); asm.set_counter( 0, 0, 0, 0 ); asm.get_iv( AES_asm.HEAP_DATA ); asm.cipher( AES_asm.ENC.CTR, AES_asm.HEAP_DATA, 16 ); var acheck = 0; for ( var i = 0; i < tagSize; ++i ) acheck |= atag[i] ^ heap[i]; if ( acheck ) throw new SecurityError("data integrity check failed"); this.result = result; this.counter = 1; this.pos = 0; this.len = 0; return this; } function AES_CCM_decrypt ( data ) { this.dataLength = data.length || 0; var result1 = AES_CCM_Decrypt_process.call( this, data ).result, result2 = AES_CCM_Decrypt_finish.call(this).result, result; result = new Uint8Array( result1.length + result2.length ); if ( result1.length ) result.set(result1); if ( result2.length ) result.set( result2, result1.length ); this.result = result; return this; } var AES_CCM_prototype = AES_CCM.prototype; AES_CCM_prototype.BLOCK_SIZE = 16; AES_CCM_prototype.reset = AES_CCM_reset; AES_CCM_prototype.encrypt = AES_CCM_encrypt; AES_CCM_prototype.decrypt = AES_CCM_decrypt; var AES_CCM_Encrypt_prototype = AES_CCM_Encrypt.prototype; AES_CCM_Encrypt_prototype.BLOCK_SIZE = 16; AES_CCM_Encrypt_prototype.reset = AES_CCM_reset; AES_CCM_Encrypt_prototype.process = AES_CCM_Encrypt_process; AES_CCM_Encrypt_prototype.finish = AES_CCM_Encrypt_finish; var AES_CCM_Decrypt_prototype = AES_CCM_Decrypt.prototype; AES_CCM_Decrypt_prototype.BLOCK_SIZE = 16; AES_CCM_Decrypt_prototype.reset = AES_CCM_reset; AES_CCM_Decrypt_prototype.process = AES_CCM_Decrypt_process; AES_CCM_Decrypt_prototype.finish = AES_CCM_Decrypt_finish;