library impl.block_cipher.modes.ccm; import 'dart:typed_data'; import 'package:pointycastle/export.dart'; import 'package:pointycastle/src/ufixnum.dart'; import 'package:pointycastle/src/utils.dart'; import '../../src/impl/base_aead_block_cipher.dart'; import '../../src/registry/registry.dart'; /// Implementation of the CCM block cipher mode. CCM is authenticated, meaning /// that you can pass AEAD data (and that appending a MAC of the ciphertext is /// unnecessary). class CCMBlockCipher extends BaseAEADBlockCipher { late Uint8List _macBlock; @override late Uint8List nonce; @override Uint8List? aad; @override late int macSize; late KeyParameter _keyParam; BytesBuilder associatedText = BytesBuilder(); BytesBuilder data = BytesBuilder(); late bool _forEncryption; // ignore: non_constant_identifier_names static final FactoryConfig factoryConfig = DynamicFactoryConfig.suffix( BlockCipher, '/CCM', (_, final Match match) => () { var underlying = BlockCipher(match.group(1)!); return CCMBlockCipher(underlying); }); CCMBlockCipher(super.underlyingCipher) { _macBlock = Uint8List(blockSize); if (blockSize != 16) { throw ArgumentError('CCM requires a block size of 16'); } } @override void reset() { underlyingCipher.reset(); associatedText.clear(); data.clear(); } @override void init(bool forEncryption, covariant CipherParameters params) { _forEncryption = forEncryption; KeyParameter key; if (params is AEADParameters) { nonce = params.nonce; aad = params.associatedData; macSize = _getMacSize(forEncryption, params.macSize); key = params.parameters as KeyParameter; } else if (params is ParametersWithIV) { nonce = params.iv; aad = null; macSize = _getMacSize(forEncryption, 64); key = params.parameters!; } else { throw ArgumentError('Invalid parameter class'); } if (nonce.length < 7 || nonce.length > 13) { throw ArgumentError('nonce must have length from 7 to 13 octets'); } _keyParam = key; reset(); } @override String get algorithmName => '${underlyingCipher.algorithmName}/CCM'; @override int processBytes( Uint8List inp, int inpOff, int len, Uint8List out, int outOff) { data.write(inp, inpOff, len); return 0; } @override int doFinal(Uint8List out, int outOff) { var len = _processPacket(data.toBytes(), 0, data.length, out, outOff); reset(); return len; } @override void processAADBytes(Uint8List inp, int inpOff, int len) { associatedText.write(inp, inpOff, len); } @override int processBlock(Uint8List inp, int inpOff, Uint8List out, int outOff) => processBytes(inp, inpOff, inp.length, out, outOff); int _processPacket( Uint8List inp, int inpOff, int len, Uint8List out, int outOff) { var n = nonce.length; var q = 15 - n; if (q < 4) { var limitLen = 1 << (8 * q); if (len >= limitLen) { throw StateError('CCM packet too large for choice of q.'); } } var iv = Uint8List(blockSize); iv[0] = (q - 1) & 0x7; arrayCopy(nonce, 0, iv, 1, nonce.length); BlockCipher ctrCipher = SICBlockCipher(blockSize, SICStreamCipher(underlyingCipher)); ctrCipher.init( forEncryption, ParametersWithIV(_keyParam, iv)); int outputLen; var inIndex = inpOff; var outIndex = outOff; if (forEncryption) { outputLen = len + macSize; if (out.length < (outputLen + outOff)) { throw ArgumentError('Output buffer too short.'); } _calculateMac(inp, inpOff, len, _macBlock); var encMac = Uint8List(blockSize); ctrCipher.processBlock(_macBlock, 0, encMac, 0); // S0 while (inIndex < (inpOff + len - blockSize)) // S1... { ctrCipher.processBlock(inp, inIndex, out, outIndex); outIndex += blockSize; inIndex += blockSize; } var block = Uint8List(blockSize); arrayCopy(inp, inIndex, block, 0, len + inpOff - inIndex); ctrCipher.processBlock(block, 0, block, 0); arrayCopy(block, 0, out, outIndex, len + inpOff - inIndex); arrayCopy(encMac, 0, out, outOff + len, macSize); } else { if (len < macSize) { throw InvalidCipherTextException('data too short'); } outputLen = len - macSize; if (out.length < (outputLen + outOff)) { throw ArgumentError('Output buffer too short.'); } arrayCopy(inp, inpOff + outputLen, _macBlock, 0, macSize); ctrCipher.processBlock(_macBlock, 0, _macBlock, 0); for (var i = macSize; i != _macBlock.length; i++) { _macBlock[i] = 0; } while (inIndex < (inpOff + outputLen - blockSize)) { ctrCipher.processBlock(inp, inIndex, out, outIndex); outIndex += blockSize; inIndex += blockSize; } var block = Uint8List(blockSize); arrayCopy(inp, inIndex, block, 0, outputLen - (inIndex - inpOff)); ctrCipher.processBlock(block, 0, block, 0); arrayCopy(block, 0, out, outIndex, outputLen - (inIndex - inpOff)); var calculatedMacBlock = Uint8List(blockSize); _calculateMac(out, outOff, outputLen, calculatedMacBlock); if (!(_macBlock.length == calculatedMacBlock.length)) { throw StateError('mac check in CCM failed'); } else { for (var i = 0; i < _macBlock.length; i++) { if (_macBlock[i] != calculatedMacBlock[i]) { throw StateError('mac check in CCM failed'); } } } } return outputLen; } int _calculateMac( Uint8List data, int dataOff, int dataLen, Uint8List macBlock) { Mac cMac = CBCBlockCipherMac(underlyingCipher, macSize * 8, null); cMac.init(ParametersWithIV(_keyParam, Uint8List(blockSize))); // // build b0 // var b0 = Uint8List(16); if (_hasAssociatedText()) { b0[0] |= 0x40; } b0[0] |= (((cMac.macSize - 2) ~/ 2) & 0x7) << 3; b0[0] |= ((15 - nonce.length) - 1) & 0x7; arrayCopy(nonce, 0, b0, 1, nonce.length); var q = dataLen; var count = 1; while (q > 0) { b0[b0.length - count] = q & 0xff; q = cshiftr32(q, 8); count++; } cMac.update(b0, 0, b0.length); // // process associated text // if (_hasAssociatedText()) { int extra; var textLength = _getAssociatedTextLength(); if (textLength < ((1 << 16) - (1 << 8))) { cMac.updateByte(textLength >> 8); cMac.updateByte(textLength); extra = 2; } else { cMac.updateByte(0xff); cMac.updateByte(0xfe); cMac.updateByte(textLength >> 24); cMac.updateByte(textLength >> 16); cMac.updateByte(textLength >> 8); cMac.updateByte(textLength); extra = 6; } if (aad != null) { cMac.update(aad!, 0, aad!.length); } if (associatedText.length > 0) { cMac.update(associatedText.toBytes(), 0, associatedText.length); } extra = (extra + textLength) % 16; if (extra != 0) { for (var i = extra; i != 16; i++) { cMac.updateByte(0x00); } } } // // add the text // cMac.update(data, dataOff, dataLen); return cMac.doFinal(macBlock, 0); } int _getMacSize(bool forEncryption, int requestedMacBits) { if (forEncryption && (requestedMacBits < 32 || requestedMacBits > 128 || 0 != (requestedMacBits & 15))) { throw ArgumentError( 'tag length in octets must be one of {4,6,8,10,12,14,16}'); } return cshiftr32(requestedMacBits, 3); } @override Uint8List get mac { var out = Uint8List(macSize); arrayCopy(_macBlock, 0, out, 0, out.length); return out; } @override bool get forEncryption => _forEncryption; @override void prepare(KeyParameter keyParam) { // Unused artifact of class hierarchy. } @override int getOutputSize(int length) { var totalData = length + data.length; if (forEncryption) { return totalData + macSize; } return totalData < macSize ? 0 : totalData - macSize; } int _getAssociatedTextLength() { return associatedText.length + ((aad == null) ? 0 : aad!.length); } bool _hasAssociatedText() { return _getAssociatedTextLength() > 0; } @override Uint8List get remainingInput => data.toBytes(); } extension WriteLen on BytesBuilder { void write(Uint8List b, int off, int len) { add(b.sublist(off, off + len)); } }