344 lines
8.5 KiB
Dart
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));
|
|
}
|
|
}
|