twonly-app-dependencies/pointycastle/lib/block/modes/ccm.dart
2025-12-07 16:10:41 +01:00

344 lines
8.5 KiB
Dart

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<KeyParameter>) {
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<KeyParameter>(_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<KeyParameter>(_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));
}
}