Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 123 additions & 16 deletions Source/DECAuthenticatedCipherModesBase.pas
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,23 @@ EDECAuthLengthException = class(EDECException);
/// Base class for authenticated cipher modes (GCM, CCM, future AEAD modes).
/// </summary>
/// <remarks>
/// Lifecycle for multi-call capable modes (e.g. GCM):
/// Lifecycle for authenticated modes (GCM, CCM):
/// <para>
/// Init → set AAD / tag length / expected tag → Encode/Decode* → Done →
/// read CalculatedAuthenticationTag.
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// CCM remains one-shot (single Encode/Decode with full message length);
/// Done still verifies ExpectedAuthenticationTag when set.
/// GCM supports multi-call Encode/Decode without a pre-declared length.
/// CCM can process several Encode/Decode chunks if the total payload
/// length is known first (DeclarePayloadLength / one-shot Size).
/// The authentication tag is materialized in Done so both modes share
/// the same lifecycle.
/// </para>
/// </remarks>
TAuthenticatedCipherModesBase = class(TObject)
Expand Down Expand Up @@ -100,6 +104,13 @@ TAuthenticatedCipherModesBase = class(TObject)
/// </summary>
FEncryptionMethod : TEncodeDecodeMethod;

/// <summary>
/// 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.
/// </summary>
FFinalized : Boolean;

/// <summary>
/// Defines the length of the resulting authentication value in bit.
/// </summary>
Expand All @@ -120,6 +131,24 @@ TAuthenticatedCipherModesBase = class(TObject)
/// Length of the calculated authentication value in bit
/// </returns>
function GetAuthenticationTagBitLength: UInt32; virtual;
/// <summary>
/// Returns the calculated authentication tag. Raises if Done has not
/// been called yet.
/// </summary>
/// <returns>
/// Calculated authentication tag bytes
/// </returns>
/// <exception cref="EDECCipherException">
/// Raised when the tag is read before Done.
/// </exception>
function GetCalculatedAuthenticationTag: TBytes; virtual;
/// <summary>
/// Raises EDECCipherException when Encode/Decode is attempted after Done.
/// </summary>
/// <exception cref="EDECCipherException">
/// Raised when the mode has already been finalized.
/// </exception>
procedure CheckNotFinalized;
public
/// <summary>
/// Should be called when starting encryption/decryption in order to
Expand All @@ -136,7 +165,8 @@ TAuthenticatedCipherModesBase = class(TObject)

/// <summary>
/// Encodes a block of data using the supplied cipher. May be called
/// multiple times for modes that support streaming (e.g. GCM).
/// multiple times for modes that support streaming (e.g. GCM, CCM
/// with a declared payload length).
/// </summary>
/// <param name="Source">
/// Plain text to encrypt
Expand All @@ -152,7 +182,8 @@ TAuthenticatedCipherModesBase = class(TObject)
Size : Integer); virtual; abstract;
/// <summary>
/// Decodes a block of data using the supplied cipher. May be called
/// multiple times for modes that support streaming (e.g. GCM).
/// multiple times for modes that support streaming (e.g. GCM, CCM
/// with a declared payload length).
/// </summary>
/// <param name="Source">
/// Encrypted ciphertext to decrypt
Expand All @@ -169,17 +200,51 @@ TAuthenticatedCipherModesBase = class(TObject)

/// <summary>
/// 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.
/// </summary>
procedure Done; virtual;

/// <summary>
/// True when Encode/Decode may be called more than once before Done.
/// GCM always supports this. CCM supports it when the total payload
/// length is known in advance (CCM is not an online AEAD: B_0 encodes
/// l(m); see RFC 3610 §1 and NIST SP 800-38C).
/// </summary>
/// <returns>
/// True if the mode can process the payload in several Encode/Decode calls
/// </returns>
function SupportsMultiChunk: Boolean; virtual;

/// <summary>
/// Declares the total payload length in bytes. Required by CCM before
/// the first Encode/Decode when the message will be supplied in several
/// chunks. Ignored by GCM. A later call is ignored once a length has
/// been set or processing has started. One-shot Encode/Decode still
/// works without this: the first call's Size is treated as the total.
/// </summary>
/// <param name="AByteLength">
/// Total plaintext/ciphertext length in bytes (not including the tag)
/// </param>
procedure DeclarePayloadLength(const AByteLength: UInt64); virtual;

/// <summary>
/// Returns the payload length last declared via DeclarePayloadLength or
/// taken from a one-shot Encode/Decode. 0 if none.
/// </summary>
/// <returns>
/// Declared payload length in bytes
/// </returns>
function GetDeclaredPayloadLength: UInt64; virtual;

/// <summary>
/// Returns a list of authentication tag lengths explicitely specified by
/// the official specification of the standard.
/// </summary>
/// <returns>
/// 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.
/// </returns>
function GetStandardAuthenticationTagBitLengths:TStandardBitLengths; virtual;

Expand All @@ -200,11 +265,15 @@ TAuthenticatedCipherModesBase = class(TObject)
read GetAuthenticationTagBitLength
write SetAuthenticationTagLength;
/// <summary>
/// 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.
/// </summary>
/// <exception cref="EDECCipherException">
/// Raised when the property is read before Done.
/// </exception>
property CalculatedAuthenticationTag : TBytes
read FCalcAuthenticationTag
read GetCalculatedAuthenticationTag
write FCalcAuthenticationTag;

/// <summary>
Expand All @@ -221,15 +290,38 @@ 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;
begin
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;

Expand All @@ -250,12 +342,27 @@ 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;

function TAuthenticatedCipherModesBase.SupportsMultiChunk: Boolean;
begin
Result := False;
end;

procedure TAuthenticatedCipherModesBase.DeclarePayloadLength(const AByteLength: UInt64);
begin
// Default: GCM and other online AEADs ignore a pre-declared length.
end;

function TAuthenticatedCipherModesBase.GetDeclaredPayloadLength: UInt64;
begin
Result := 0;
end;

procedure TAuthenticatedCipherModesBase.SetAuthenticationTagLength(const Value: UInt32);
Expand Down
15 changes: 10 additions & 5 deletions Source/DECCipherFormats.pas
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -780,6 +780,11 @@ procedure TDECFormattedCipher.DoEncodeDecodeStream(const Source, Dest: TStream;
if DataSize < 0 then
DataSize := Source.Size - Pos;

if Assigned(FAuthObj) then
begin
FAuthObj.DeclarePayloadLength(UInt64(DataSize));
end;

Max := Pos + DataSize;
StartPos := Pos;
doPadding := false;
Expand Down
20 changes: 12 additions & 8 deletions Source/DECCipherInterface.pas
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -665,11 +665,12 @@ interface
/// </summary>
/// <returns>
/// 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.
/// </returns>
/// <exception cref="EDECCipherException">
/// Exception raised if called for a cipher mode not supporting
/// authentication.
/// authentication, or if the tag is read before Done.
/// </exception>
function GetCalcAuthenticatonResult: TBytes;
/// <summary>
Expand Down Expand Up @@ -705,8 +706,10 @@ interface
/// the official specification of the standard.
/// </summary>
/// <returns>
/// 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.
/// </returns>
function GetStandardAuthenticationTagBitLengths:TStandardBitLengths;

Expand Down Expand Up @@ -756,12 +759,13 @@ interface
/// <summary>
/// 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.
/// </summary>
/// <exception cref="EDECCipherException">
/// Exception raised if called for a cipher mode not supporting
/// authentication.
/// authentication, or if the tag is read before Done.
/// </exception>
property CalculatedAuthenticationResult : TBytes
read GetCalcAuthenticatonResult;
Expand Down
Loading