diff --git a/Source/DECAuthenticatedCipherModesBase.pas b/Source/DECAuthenticatedCipherModesBase.pas index 85b05b5..56d9783 100644 --- a/Source/DECAuthenticatedCipherModesBase.pas +++ b/Source/DECAuthenticatedCipherModesBase.pas @@ -59,19 +59,21 @@ EDECAuthLengthException = class(EDECException); /// Base class for authenticated cipher modes (GCM, CCM, future AEAD modes). /// /// - /// Lifecycle for multi-call capable modes (e.g. GCM): + /// Lifecycle for authenticated modes (GCM, CCM): /// /// Init → set AAD / tag length / expected tag → Encode/Decode* → Done → /// read CalculatedAuthenticationTag. /// /// - /// Done must be called before the calculated authentication tag is valid - /// for multi-call streams. Done is idempotent. After Done, further - /// Encode/Decode raises until Init is called again. + /// Done must be called before CalculatedAuthenticationTag may be read. + /// Reading the tag before Done raises EDECCipherException. Done is + /// idempotent. After Done, further Encode/Decode raises until Init is + /// called again. /// /// - /// CCM remains one-shot (single Encode/Decode with full message length); - /// Done still verifies ExpectedAuthenticationTag when set. + /// GCM supports multi-call Encode/Decode. CCM currently remains one-shot + /// (single Encode/Decode with the full message length); the tag is still + /// materialized in Done so both modes share the same lifecycle. /// /// TAuthenticatedCipherModesBase = class(TObject) @@ -100,6 +102,13 @@ TAuthenticatedCipherModesBase = class(TObject) /// FEncryptionMethod : TEncodeDecodeMethod; + /// + /// True after Done has materialized the authentication tag. Reading + /// CalculatedAuthenticationTag before this is set raises. Encode/Decode + /// after finalization also raises until Init is called again. + /// + FFinalized : Boolean; + /// /// Defines the length of the resulting authentication value in bit. /// @@ -120,6 +129,24 @@ TAuthenticatedCipherModesBase = class(TObject) /// Length of the calculated authentication value in bit /// function GetAuthenticationTagBitLength: UInt32; virtual; + /// + /// Returns the calculated authentication tag. Raises if Done has not + /// been called yet. + /// + /// + /// Calculated authentication tag bytes + /// + /// + /// Raised when the tag is read before Done. + /// + function GetCalculatedAuthenticationTag: TBytes; virtual; + /// + /// Raises EDECCipherException when Encode/Decode is attempted after Done. + /// + /// + /// Raised when the mode has already been finalized. + /// + procedure CheckNotFinalized; public /// /// Should be called when starting encryption/decryption in order to @@ -169,8 +196,9 @@ TAuthenticatedCipherModesBase = class(TObject) /// /// Finalizes the authentication tag after all Encode/Decode calls. - /// Idempotent. Default implementation is a no-op (suitable for modes that - /// already compute the tag inside Encode/Decode, e.g. CCM). + /// Idempotent. Marks the tag as readable via CalculatedAuthenticationTag. + /// Concrete modes that defer tag computation (GCM, CCM) override this + /// to materialize the tag before calling inherited. /// procedure Done; virtual; @@ -179,7 +207,8 @@ TAuthenticatedCipherModesBase = class(TObject) /// the official specification of the standard. /// /// - /// List of bit lengths + /// List of bit lengths prescribed by the mode specification. If the + /// mode does not prescribe any tag lengths, an empty array is returned. /// function GetStandardAuthenticationTagBitLengths:TStandardBitLengths; virtual; @@ -200,11 +229,15 @@ TAuthenticatedCipherModesBase = class(TObject) read GetAuthenticationTagBitLength write SetAuthenticationTagLength; /// - /// Calculated authentication value. For multi-call modes this is only - /// complete after Done has been called. + /// Calculated authentication value. Valid only after Done has been + /// called. Reading this property before Done raises EDECCipherException + /// so callers follow the Init → Encode/Decode* → Done → tag lifecycle. /// + /// + /// Raised when the property is read before Done. + /// property CalculatedAuthenticationTag : TBytes - read FCalcAuthenticationTag + read GetCalculatedAuthenticationTag write FCalcAuthenticationTag; /// @@ -221,6 +254,12 @@ implementation uses DECUtil; +resourcestring + sAuthenticationTagNotFinalized = + 'Calculated authentication tag is not available before Done has been called'; + sAuthenticatedModeAlreadyFinalized = + 'Authenticated cipher mode already finalized; call Init before further Encode/Decode'; + { TAuthenticatedCipherModesBase } function TAuthenticatedCipherModesBase.GetAuthenticationTagBitLength: UInt32; @@ -228,8 +267,25 @@ function TAuthenticatedCipherModesBase.GetAuthenticationTagBitLength: UInt32; Result := FCalcAuthenticationTagLength shl 3; end; +function TAuthenticatedCipherModesBase.GetCalculatedAuthenticationTag: TBytes; +begin + if not FFinalized then + raise EDECCipherException.CreateRes(@sAuthenticationTagNotFinalized); + + Result := FCalcAuthenticationTag; +end; + +procedure TAuthenticatedCipherModesBase.CheckNotFinalized; +begin + if FFinalized then + raise EDECCipherException.CreateRes(@sAuthenticatedModeAlreadyFinalized); +end; + function TAuthenticatedCipherModesBase.GetStandardAuthenticationTagBitLengths: TStandardBitLengths; begin + // No prescribed lengths at this abstraction: return an empty array rather + // than a dummy 0-entry so callers can distinguish "none specified" from a + // specified length of 0 bits. SetLength(Result, 0); end; @@ -250,12 +306,12 @@ procedure TAuthenticatedCipherModesBase.Init(EncryptionMethod : TEncodeDecodeMet end; FEncryptionMethod := EncryptionMethod; + FFinalized := False; end; procedure TAuthenticatedCipherModesBase.Done; begin - // Default: no deferred finalization (CCM computes the tag in Encode/Decode). - // Streaming modes such as GCM override this to materialize the tag. + FFinalized := True; end; procedure TAuthenticatedCipherModesBase.SetAuthenticationTagLength(const Value: UInt32); diff --git a/Source/DECCipherFormats.pas b/Source/DECCipherFormats.pas index c459893..e9a03e6 100644 --- a/Source/DECCipherFormats.pas +++ b/Source/DECCipherFormats.pas @@ -1,4 +1,4 @@ -{***************************************************************************** +{***************************************************************************** The DEC team (see file NOTICE.txt) licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance @@ -732,8 +732,8 @@ function TDECFormattedCipher.EncodeBytes(const Source: TBytes): TBytes; if Length(Result) > 0 then Encode(Source[0], Result[0], Length(Source)) else - if (FMode = cmGCM) then - EncodeGCM(nil, nil, 0); + if (FMode = cmGCM) or (FMode = cmCCM) then + EncodeAuthenticated(nil, nil, 0); end; begin @@ -755,8 +755,8 @@ function TDECFormattedCipher.DecodeBytes(const Source: TBytes): TBytes; Decode(Source[0], Result[0], Length(Source)); end else - if (FMode = cmGCM) then - DecodeGCM(nil, nil, 0); + if (FMode = cmGCM) or (FMode = cmCCM) then + DecodeAuthenticated(nil, nil, 0); if not (FPaddingClass = nil) then Result := FPaddingClass.RemovePadding(Result, Context.BlockSize); diff --git a/Source/DECCipherInterface.pas b/Source/DECCipherInterface.pas index a4e1c6e..f9a280a 100644 --- a/Source/DECCipherInterface.pas +++ b/Source/DECCipherInterface.pas @@ -1,4 +1,4 @@ -{***************************************************************************** +{***************************************************************************** The DEC team (see file NOTICE.txt) licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance @@ -665,11 +665,12 @@ interface /// /// /// Result of the authentication. Raises an EDECCipherException if this is - /// called for a cipher mode not supporting authentication. + /// called for a cipher mode not supporting authentication, or if Done + /// has not been called yet. /// /// /// Exception raised if called for a cipher mode not supporting - /// authentication. + /// authentication, or if the tag is read before Done. /// function GetCalcAuthenticatonResult: TBytes; /// @@ -705,8 +706,10 @@ interface /// the official specification of the standard. /// /// - /// List of bit lengths. If the cipher mode used is not an authenticated - /// one, the array will just contain a single value of 0. + /// List of bit lengths prescribed by the authenticated mode. If the + /// cipher mode used is not an authenticated one, the array will just + /// contain a single value of 0. If an authenticated mode does not + /// prescribe tag lengths, an empty array is returned. /// function GetStandardAuthenticationTagBitLengths:TStandardBitLengths; @@ -756,12 +759,13 @@ interface /// /// Some block chaining modes have the ability to authenticate the message /// in addition to encrypting it. This property contains the generated - /// authentication tag. Raises an EDECCipherException if this is - /// called for a cipher mode not supporting authentication. + /// authentication tag. Call Done before reading it; reading the tag + /// before Done raises EDECCipherException. Raises an EDECCipherException + /// if this is called for a cipher mode not supporting authentication. /// /// /// Exception raised if called for a cipher mode not supporting - /// authentication. + /// authentication, or if the tag is read before Done. /// property CalculatedAuthenticationResult : TBytes read GetCalcAuthenticatonResult; diff --git a/Source/DECCipherModes.pas b/Source/DECCipherModes.pas index db5889d..4a5e817 100644 --- a/Source/DECCipherModes.pas +++ b/Source/DECCipherModes.pas @@ -81,11 +81,12 @@ TDECCipherModes = class(TDECCipher, IDECAuthenticatedCipher) /// /// /// Result of the authentication. Raises an EDECCipherException if this is - /// called for a cipher mode not supporting authentication. + /// called for a cipher mode not supporting authentication, or if Done + /// has not been called yet. /// /// /// Exception raised if called for a cipher mode not supporting - /// authentication. + /// authentication, or if the tag is read before Done. /// function GetCalcAuthenticatonResult: TBytes; /// @@ -235,16 +236,20 @@ TDECCipherModes = class(TDECCipher, IDECAuthenticatedCipher) /// procedure EncodeCTSx(Source, Dest: PUInt8Array; Size: Integer); virtual; /// - /// Authenticated encryption via FAuthObj (GCM, CCM). Kept as EncodeGCM for - /// protected-API compatibility; dispatches to the active auth mode object. + /// Authenticated encryption via FAuthObj (GCM, CCM, future AEAD modes). + /// Replaces the former protected EncodeGCM / EncodeCCM entry points. /// Callable even if source length is 0 (AAD-only / empty PT). /// - procedure EncodeGCM(Source, Dest: PUInt8Array; Size: Integer); virtual; - /// - /// Authenticated encryption via FAuthObj. Alias retained for protected-API - /// compatibility with code that overrode EncodeCCM. - /// - procedure EncodeCCM(Source, Dest: PUInt8Array; Size: Integer); virtual; + /// + /// Plain text to encrypt + /// + /// + /// Ciphertext after encryption + /// + /// + /// Number of bytes to encrypt + /// + procedure EncodeAuthenticated(Source, Dest: PUInt8Array; Size: Integer); virtual; {$IFDEF DEC3_CMCTS} /// /// double CBC, with @@ -329,15 +334,19 @@ TDECCipherModes = class(TDECCipher, IDECAuthenticatedCipher) /// procedure DecodeCTSx(Source, Dest: PUInt8Array; Size: Integer); virtual; /// - /// Authenticated decryption via FAuthObj (GCM, CCM). Kept as DecodeGCM for - /// protected-API compatibility. + /// Authenticated decryption via FAuthObj (GCM, CCM, future AEAD modes). + /// Replaces the former protected DecodeGCM / DecodeCCM entry points. /// - procedure DecodeGCM(Source, Dest: PUInt8Array; Size: Integer); virtual; - /// - /// Authenticated decryption via FAuthObj. Alias retained for protected-API - /// compatibility with code that overrode DecodeCCM. - /// - procedure DecodeCCM(Source, Dest: PUInt8Array; Size: Integer); virtual; + /// + /// Encrypted ciphertext to decrypt + /// + /// + /// Plaintext after decryption + /// + /// + /// Number of bytes to decrypt + /// + procedure DecodeAuthenticated(Source, Dest: PUInt8Array; Size: Integer); virtual; {$IFDEF DEC3_CMCTS} /// /// double CBC @@ -401,8 +410,10 @@ TDECCipherModes = class(TDECCipher, IDECAuthenticatedCipher) /// specified by the official specification of the standard. /// /// - /// List of bit lengths. If the cipher mode used is not an authenticated - /// one, the array will just contain a single value of 0. + /// List of bit lengths prescribed by the authenticated mode. If the + /// cipher mode used is not an authenticated one, the array will just + /// contain a single value of 0. If an authenticated mode does not + /// prescribe tag lengths, an empty array is returned. /// function GetStandardAuthenticationTagBitLengths:TStandardBitLengths; @@ -430,12 +441,13 @@ TDECCipherModes = class(TDECCipher, IDECAuthenticatedCipher) /// /// Some block chaining modes have the ability to authenticate the message /// in addition to encrypting it. This property contains the generated - /// authentication tag. Raises an EDECCipherException if this is - /// called for a cipher mode not supporting authentication. + /// authentication tag. Call Done before reading it; reading the tag + /// before Done raises EDECCipherException. Raises an EDECCipherException + /// if this is called for a cipher mode not supporting authentication. /// /// /// Exception raised if called for a cipher mode not supporting - /// authentication. + /// authentication, or if the tag is read before Done. /// property CalculatedAuthenticationResult : TBytes read GetCalcAuthenticatonResult; @@ -528,8 +540,8 @@ procedure TDECCipherModes.Encode(const Source; var Dest; DataSize: Integer); cmOFBx: EncodeOFBx(@Source, @Dest, DataSize); cmCFS8: EncodeCFS8(@Source, @Dest, DataSize); cmCFSx: EncodeCFSx(@Source, @Dest, DataSize); - cmGCM : EncodeGCM(@Source, @Dest, DataSize); - cmCCM : EncodeCCM(@Source, @Dest, DataSize); + cmGCM : EncodeAuthenticated(@Source, @Dest, DataSize); + cmCCM : EncodeAuthenticated(@Source, @Dest, DataSize); end; end; @@ -854,23 +866,14 @@ procedure TDECCipherModes.EncodeCTSx(Source, Dest: PUInt8Array; Size: Integer); FState := csEncode; end; -procedure TDECCipherModes.EncodeGCM(Source, Dest: PUInt8Array; Size: Integer); +procedure TDECCipherModes.EncodeAuthenticated(Source, Dest: PUInt8Array; Size: Integer); begin - if (Size < 0) then - Size := 0; - - // Dispatch through FAuthObj (TGCM when Mode=cmGCM). Independent of EncodeCCM - // so a subclass override of one entry point does not affect the other. - FAuthObj.Encode(Source, Dest, Size); -end; + if not Assigned(FAuthObj) then + raise EDECCipherException.CreateResFmt(@sInvalidModeForMethod, ['cmGCM or cmCCM']); -procedure TDECCipherModes.EncodeCCM(Source, Dest: PUInt8Array; Size: Integer); -begin if (Size < 0) then Size := 0; - // Same FAuthObj.Encode body as EncodeGCM, but a separate protected entry so - // overriding EncodeGCM does not change CCM behaviour (and vice versa). FAuthObj.Encode(Source, Dest, Size); end; @@ -916,8 +919,8 @@ procedure TDECCipherModes.Decode(const Source; var Dest; DataSize: Integer); cmOFBx: DecodeOFBx(@Source, @Dest, DataSize); cmCFS8: DecodeCFS8(@Source, @Dest, DataSize); cmCFSx: DecodeCFSx(@Source, @Dest, DataSize); - cmGCM : DecodeGCM(@Source, @Dest, DataSize); - cmCCM : DecodeCCM(@Source, @Dest, DataSize); + cmGCM : DecodeAuthenticated(@Source, @Dest, DataSize); + cmCCM : DecodeAuthenticated(@Source, @Dest, DataSize); end; end; @@ -956,21 +959,14 @@ procedure TDECCipherModes.DecodeECBx(Source, Dest: PUInt8Array; Size: Integer); end; end; -procedure TDECCipherModes.DecodeGCM(Source, Dest: PUInt8Array; Size: Integer); +procedure TDECCipherModes.DecodeAuthenticated(Source, Dest: PUInt8Array; Size: Integer); begin - if (Size < 0) then - Size := 0; - - // Independent of DecodeCCM — see EncodeGCM/EncodeCCM. - FAuthObj.Decode(Source, Dest, Size); -end; + if not Assigned(FAuthObj) then + raise EDECCipherException.CreateResFmt(@sInvalidModeForMethod, ['cmGCM or cmCCM']); -procedure TDECCipherModes.DecodeCCM(Source, Dest: PUInt8Array; Size: Integer); -begin if (Size < 0) then Size := 0; - // Separate protected entry from DecodeGCM; same FAuthObj.Decode body. FAuthObj.Decode(Source, Dest, Size); end; @@ -1130,8 +1126,8 @@ procedure TDECCipherModes.Done; if Assigned(FAuthObj) then begin - // Finalize multi-call authentication (GCM) before optional ExpectedTag check. - // CCM Done is a no-op on the mode object (tag already computed in Encode/Decode). + // Finalize authentication (GCM GHASH / CCM CBC-MAC tag) before optional + // ExpectedTag check. Both modes materialize the tag in FAuthObj.Done. FAuthObj.Done; if (Length(FAuthObj.ExpectedAuthenticationTag) > 0) and diff --git a/Source/DECCipherModesCCM.pas b/Source/DECCipherModesCCM.pas index 3f835f6..6adcf35 100644 --- a/Source/DECCipherModesCCM.pas +++ b/Source/DECCipherModesCCM.pas @@ -1,4 +1,4 @@ -{***************************************************************************** +{***************************************************************************** The DEC team (see file NOTICE.txt) licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance @@ -51,6 +51,19 @@ TCCM = class(TAuthenticatedCipherModesBase) /// Init vector which is modified during processing /// FInitVector : TBlock16Byte; + /// + /// CBC-MAC state after Encode/Decode; the authentication tag is derived + /// from this in Done (S_0 XOR T), not inside Encode/Decode. + /// + FMacBlock : TBlock16Byte; + /// + /// L parameter (octets of the length field) used to restore CTR_0 in Done + /// + FLengthFieldOctets : UInt16; + /// + /// True after Encode/Decode has produced a CBC-MAC state for Done + /// + FMacReady : Boolean; /// /// Encodes or decodes a block of data using the supplied cipher @@ -69,6 +82,11 @@ TCCM = class(TAuthenticatedCipherModesBase) /// When true it is encrypting data, else it is descrypting data /// procedure EncodeDecode(Source, Dest: PUInt8Array; Size: Integer; Encode: Boolean); + /// + /// Derives CalculatedAuthenticationTag from FMacBlock (S_0 XOR T). + /// Called from Done after Encode/Decode have finished the CBC-MAC. + /// + procedure FinalizeAuthenticationTag; strict protected /// /// Defines the length of the resulting authentication value in bit. @@ -127,6 +145,14 @@ TCCM = class(TAuthenticatedCipherModesBase) Dest : PUInt8Array; Size : Integer); override; + /// + /// Materializes CalculatedAuthenticationTag from the CBC-MAC state. + /// Must be called after the last Encode/Decode (cipher Done does this). + /// Idempotent: a second call leaves the tag unchanged. + /// After finalization, Encode/Decode raise until Init is called again. + /// + procedure Done; override; + /// /// Returns a list of authentication tag lengths explicitely specified by /// the official specification of the standard. @@ -165,6 +191,7 @@ destructor TCCM.Destroy; ProtectBytes(FOrigInitVector); ProtectBuffer(FInitVector, SizeOf(FInitVector)); + ProtectBuffer(FMacBlock, SizeOf(FMacBlock)); ProtectBytes(FCalcAuthenticationTag); ProtectBytes(FExpectedAuthenticationTag); @@ -181,9 +208,6 @@ procedure TCCM.EncodeDecode(Source, Dest: PUInt8Array; Encode: Boolean); var ecc : TBlock16Byte; // encrypted counter - FixedTagBuf : TBlock16Byte; // during calculation buffer of authentication tag - // might need to be bigger than then one specified - // by the user len : Int32; k, L : UInt16; b : UInt8; @@ -211,6 +235,8 @@ procedure TCCM.EncodeDecode(Source, Dest: PUInt8Array; end; begin + CheckNotFinalized; + if (Size > 0) and ((not Assigned(Source)) or (not Assigned(Dest))) then raise EDECCipherException.Create(sInvalidSourcePointer); @@ -234,6 +260,7 @@ procedure TCCM.EncodeDecode(Source, Dest: PUInt8Array; // Force Length(FInitVector) + L = 15. Since nLen <= 13, L is at least 2 L := 15 - InitVectLen; + FLengthFieldOctets := L; // compose B_0 = Flags | Nonce N | l(m) // octet 0: Flags = 64*HdrPresent | 8*((tLen-2) div 2 | (L-1) @@ -354,15 +381,10 @@ procedure TCCM.EncodeDecode(Source, Dest: PUInt8Array; FEncryptionMethod(@Buf[0], @Buf[0], Length(Buf)); end; - // setup counter for the tag (zero the count) - for k := 15 downto 16-L do - FInitVector[k] := 0; - - FEncryptionMethod(@FInitVector[0], @ecc[0], Length(ecc)); - - // store the TAG/Authentication result value - XORBuffers(Buf[0], ecc[0], 16, FixedTagBuf); - Move(FixedTagBuf[0], FCalcAuthenticationTag[0], length(FCalcAuthenticationTag)); + // Keep CBC-MAC state for Done; do not materialize the tag here so CCM + // shares the Init → Encode/Decode* → Done → tag lifecycle with GCM. + Move(Buf[0], FMacBlock[0], SizeOf(FMacBlock)); + FMacReady := True; ProtectBuffer(Buf, SizeOf(Buf)); end; @@ -394,6 +416,48 @@ procedure TCCM.Init(EncryptionMethod : TEncodeDecodeMethod; inherited; FOrigInitVector := InitVector; + FMacReady := False; + FLengthFieldOctets := 0; +end; + +procedure TCCM.FinalizeAuthenticationTag; +var + ecc : TBlock16Byte; + FixedTagBuf : TBlock16Byte; + k : UInt16; +begin + // Restore CTR_0 (zero the count) and encrypt to get S_0, then tag = T XOR S_0. + // See RFC 3610 §2.6 / NIST SP 800-38C: authentication tag is not part of Encode. + for k := 15 downto 16 - FLengthFieldOctets do + begin + FInitVector[k] := 0; + end; + + FEncryptionMethod(@FInitVector[0], @ecc[0], Length(ecc)); + + XORBuffers(FMacBlock[0], ecc[0], 16, FixedTagBuf); + if (Length(FCalcAuthenticationTag) > 0) then + begin + Move(FixedTagBuf[0], FCalcAuthenticationTag[0], Length(FCalcAuthenticationTag)); + end; + + ProtectBuffer(ecc, SizeOf(ecc)); + ProtectBuffer(FixedTagBuf, SizeOf(FixedTagBuf)); +end; + +procedure TCCM.Done; +begin + if FFinalized then + Exit; + + if not FMacReady then + begin + // Empty payload / AAD-only: format B_0 with l(m)=0 and process AAD. + EncodeDecode(nil, nil, 0, True); + end; + + FinalizeAuthenticationTag; + inherited; end; end. diff --git a/Source/DECCipherModesGCM.pas b/Source/DECCipherModesGCM.pas index e88476e..00f2826 100644 --- a/Source/DECCipherModesGCM.pas +++ b/Source/DECCipherModesGCM.pas @@ -104,11 +104,6 @@ TGCM = class(TAuthenticatedCipherModesBase) /// Number of unused bytes remaining in FKeystream (0..15) /// FKeystreamRemainLen : Integer; - /// - /// True after Done has materialized the authentication tag. - /// Prevents double-finalization and post-Done GHASH/CTR updates. - /// - FFinalized : Boolean; /// /// XOR implementation for unsigned 128 bit numbers @@ -346,8 +341,6 @@ TGCM = class(TAuthenticatedCipherModesBase) implementation resourcestring - sGCMAlreadyFinalized = - 'GCM authentication already finalized; call Init before further Encode/Decode'; sGCMAADLocked = 'GCM DataToAuthenticate cannot be changed after Encode/Decode has started or after Done'; sGCMAuthTagLength = @@ -541,7 +534,6 @@ procedure TGCM.Init(EncryptionMethod : TEncodeDecodeMethod; FKeystreamRemainLen := 0; FKeystreamLeftover[0] := 0; FKeystreamLeftover[1] := 0; - FFinalized := False; OldH := FH; EncryptionMethod(@Nullbytes[0], @FH[0], 16); @@ -658,8 +650,9 @@ procedure TGCM.Done; begin if FFinalized then Exit; + FinalizeAuthenticationTag; - FFinalized := True; + inherited; end; function TGCM.CalcGaloisHash(AuthenticatedData : PUInt8Array; AuthLen : integer; Ciphertext : PUInt8Array; @@ -757,8 +750,7 @@ procedure TGCM.ApplyCTR(Source, Dest: PUInt8Array; Size: Integer); procedure TGCM.Decode(Source, Dest: PUInt8Array; Size: Integer); begin - if FFinalized then - raise EDECCipherException.CreateRes(@sGCMAlreadyFinalized); + CheckNotFinalized; // AAD into GHASH once; tag finalized in Done (supports multi-call streams) EnsureAuthDataHashed; @@ -778,8 +770,7 @@ procedure TGCM.Decode(Source, Dest: PUInt8Array; Size: Integer); procedure TGCM.Encode(Source, Dest: PUInt8Array; Size: Integer); begin - if FFinalized then - raise EDECCipherException.CreateRes(@sGCMAlreadyFinalized); + CheckNotFinalized; // AAD into GHASH once; tag finalized in Done (supports multi-call streams) EnsureAuthDataHashed; diff --git a/Unit Tests/Tests/TestDECCipherModesCCM.pas b/Unit Tests/Tests/TestDECCipherModesCCM.pas index fb827fe..c83604c 100644 --- a/Unit Tests/Tests/TestDECCipherModesCCM.pas +++ b/Unit Tests/Tests/TestDECCipherModesCCM.pas @@ -56,6 +56,8 @@ TestTDECCCM = class(TTestCase) procedure DoTestInitFailureIVTooShort; procedure DoTestRFC3610(EncodeTest: Boolean); procedure DoTestAuthenticationBitLengthWrong; + procedure DoReadTagBeforeDone; + procedure DoEncodeAfterDone; public procedure SetUp; override; procedure TearDown; override; @@ -85,6 +87,18 @@ TestTDECCCM = class(TTestCase) procedure TestGetStandardAuthenticationTagBitLengths; procedure TestGetExpectedAuthenticationResult; procedure TestSetExpectedAuthenticationResult; + /// + /// Reading CalculatedAuthenticationResult before Done must raise. + /// + procedure TestCalculatedAuthenticationResultBeforeDoneRaises; + /// + /// Encode after Done must raise until Init is called again. + /// + procedure TestEncodeAfterDoneRejected; + /// + /// Done twice must leave CalculatedAuthenticationResult unchanged. + /// + procedure TestDoneIdempotent; end; @@ -760,6 +774,7 @@ procedure TestTDECCCM.DoTestRFC3610(EncodeTest: Boolean); CheckEquals(true, CompareMem(@buf,@DecodeBuf,plen), 'Plaintext wrong'); end; + CipherAES.Done; TagResult := CipherAES.CalculatedAuthenticationResult; // Test the generated tag @@ -772,6 +787,73 @@ procedure TestTDECCCM.DoTestRFC3610(EncodeTest: Boolean); end; end; +procedure TestTDECCCM.DoReadTagBeforeDone; +var + Tag: TBytes; +begin + Tag := FCipherAES.CalculatedAuthenticationResult; +end; + +procedure TestTDECCCM.DoEncodeAfterDone; +var + Data: TBytes; +begin + SetLength(Data, 4); + FillChar(Data[0], Length(Data), $A5); + FCipherAES.EncodeBytes(Data); +end; + +procedure TestTDECCCM.TestCalculatedAuthenticationResultBeforeDoneRaises; +var + TestData: TSingleAuthenticatedTestData; +begin + TestData := FTestDataList[0].TestData[0]; + FCipherAES.Init(BytesOf(TFormat_HexL.Decode(TestData.CryptKey)), + BytesOf(TFormat_HexL.Decode(TestData.InitVector)), + $FF); + FCipherAES.AuthenticationResultBitLength := FTestDataList[0].Taglen; + FCipherAES.DataToAuthenticate := TFormat_HexL.Decode(BytesOf(TestData.AAD)); + FCipherAES.EncodeBytes(TFormat_HexL.Decode(BytesOf(TestData.PT))); + CheckException(DoReadTagBeforeDone, EDECCipherException, + 'CalculatedAuthenticationResult before Done must raise EDECCipherException'); +end; + +procedure TestTDECCCM.TestEncodeAfterDoneRejected; +var + TestData: TSingleAuthenticatedTestData; +begin + TestData := FTestDataList[0].TestData[0]; + FCipherAES.Init(BytesOf(TFormat_HexL.Decode(TestData.CryptKey)), + BytesOf(TFormat_HexL.Decode(TestData.InitVector)), + $FF); + FCipherAES.AuthenticationResultBitLength := FTestDataList[0].Taglen; + FCipherAES.DataToAuthenticate := TFormat_HexL.Decode(BytesOf(TestData.AAD)); + FCipherAES.EncodeBytes(TFormat_HexL.Decode(BytesOf(TestData.PT))); + FCipherAES.Done; + CheckException(DoEncodeAfterDone, EDECCipherException, + 'Encode after Done must raise EDECCipherException'); +end; + +procedure TestTDECCCM.TestDoneIdempotent; +var + TestData: TSingleAuthenticatedTestData; + Tag1, Tag2: TBytes; +begin + TestData := FTestDataList[0].TestData[0]; + FCipherAES.Init(BytesOf(TFormat_HexL.Decode(TestData.CryptKey)), + BytesOf(TFormat_HexL.Decode(TestData.InitVector)), + $FF); + FCipherAES.AuthenticationResultBitLength := FTestDataList[0].Taglen; + FCipherAES.DataToAuthenticate := TFormat_HexL.Decode(BytesOf(TestData.AAD)); + FCipherAES.EncodeBytes(TFormat_HexL.Decode(BytesOf(TestData.PT))); + FCipherAES.Done; + Tag1 := Copy(FCipherAES.CalculatedAuthenticationResult); + FCipherAES.Done; + Tag2 := FCipherAES.CalculatedAuthenticationResult; + CheckTrue(IsEqual(Tag1, Tag2), + 'Second Done must not change CalculatedAuthenticationResult'); +end; + initialization // Register all test cases to be run {$IFDEF DUnitX} diff --git a/Unit Tests/Tests/TestDECCipherModesGCM.pas b/Unit Tests/Tests/TestDECCipherModesGCM.pas index e640c14..3b29596 100644 --- a/Unit Tests/Tests/TestDECCipherModesGCM.pas +++ b/Unit Tests/Tests/TestDECCipherModesGCM.pas @@ -158,6 +158,7 @@ TestTDECGCM = class(TTestCase) procedure DoDecodeAfterDone; procedure DoChangeAADAfterEncode; procedure DoSetAuthTagBitLengthTooLong; + procedure DoReadTagBeforeDone; public procedure SetUp; override; procedure TearDown; override; @@ -198,6 +199,10 @@ TestTDECGCM = class(TTestCase) /// procedure TestAADChangeAfterEncodeRejected; /// + /// Reading CalculatedAuthenticationResult before Done must raise. + /// + procedure TestCalculatedAuthenticationResultBeforeDoneRaises; + /// /// AuthenticationResultBitLength > 128 must raise (tag buffer is 16 bytes). /// procedure TestAuthTagBitLengthTooLongRejected; @@ -921,6 +926,27 @@ procedure TestTDECGCM.TestDoneIdempotent; 'Second Done must not change CalculatedAuthenticationResult'); end; +procedure TestTDECGCM.DoReadTagBeforeDone; +var + Tag: TBytes; +begin + Tag := FCipherAES.CalculatedAuthenticationResult; +end; + +procedure TestTDECGCM.TestCalculatedAuthenticationResultBeforeDoneRaises; +var + ptBytes: TBytes; +begin + ptBytes := TFormat_HexL.Decode(BytesOf(cCAVS_MultiChunkPT)); + FCipherAES.Init(BytesOf(TFormat_HexL.Decode(cCAVS_MultiChunkKey)), + BytesOf(TFormat_HexL.Decode(cCAVS_MultiChunkIV)), $FF); + FCipherAES.AuthenticationResultBitLength := cCAVS_MultiChunkTagBits; + FCipherAES.DataToAuthenticate := TFormat_HexL.Decode(BytesOf(cCAVS_MultiChunkAAD)); + FCipherAES.EncodeBytes(ptBytes); + CheckException(DoReadTagBeforeDone, EDECCipherException, + 'CalculatedAuthenticationResult before Done must raise EDECCipherException'); +end; + procedure TestTDECGCM.DoEncodeAfterDone; begin FCipherAES.EncodeBytes(FCallAfterDoneData);