xtool/contrib/fundamentals/TLS/flcTLSRecord.pas

969 lines
36 KiB
ObjectPascal

{******************************************************************************}
{ }
{ Library: Fundamentals TLS }
{ File name: flcTLSRecord.pas }
{ File version: 5.08 }
{ Description: TLS records }
{ }
{ Copyright: Copyright (c) 2008-2020, David J Butler }
{ All rights reserved. }
{ Redistribution and use in source and binary forms, with }
{ or without modification, are permitted provided that }
{ the following conditions are met: }
{ Redistributions of source code must retain the above }
{ copyright notice, this list of conditions and the }
{ following disclaimer. }
{ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND }
{ CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED }
{ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED }
{ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A }
{ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL }
{ THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, }
{ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR }
{ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, }
{ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF }
{ USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) }
{ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER }
{ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING }
{ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE }
{ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE }
{ POSSIBILITY OF SUCH DAMAGE. }
{ }
{ Github: https://github.com/fundamentalslib }
{ E-mail: fundamentals.library at gmail.com }
{ }
{ Revision history: }
{ }
{ 2008/01/18 0.01 Initial development. }
{ 2010/11/30 0.02 Stream cipher. }
{ 2010/12/01 0.03 Block cipher for TLS 1.0. }
{ 2010/12/02 0.04 Block cipher for TLS 1.1 and TLS 1.2. }
{ 2010/12/17 0.05 Fixes for TLS 1.1 and TLS 1.2 block ciphers. }
{ 2011/10/11 0.06 Fixes for TLS 1.1 block cipher encoding. }
{ 2011/10/12 0.07 MAC validation on decoding. }
{ 2018/07/17 5.08 Revised for Fundamentals 5. }
{ }
{******************************************************************************}
{$INCLUDE flcTLS.inc}
unit flcTLSRecord;
interface
uses
{ TLS }
flcTLSProtocolVersion,
flcTLSAlgorithmTypes,
flcTLSCipherSuite,
flcTLSCipher;
{ }
{ ContentType }
{ }
type
TTLSContentType = (
tlsctInvalid = 0,
tlsctChange_cipher_spec = 20,
tlsctAlert = 21,
tlsctHandshake = 22,
tlsctApplication_data = 23,
tlsctHeartbeat = 24, // RFC 6520
tlsctMax = 255
);
PTLSContentType = ^TTLSContentType;
const
TLSContentTypeSize = Sizeof(TTLSContentType);
function TLSContentTypeToStr(const A: TTLSContentType): String;
function IsKnownTLSContentType(const A: TTLSContentType): Boolean;
{ }
{ TLS Record Header }
{ }
type
TTLSRecordHeader = packed record
_type : TTLSContentType;
version : TTLSProtocolVersion;
length : Word;
end;
PTLSRecordHeader = ^TTLSRecordHeader;
const
TLSRecordHeaderSize = Sizeof(TTLSRecordHeader);
procedure InitTLSRecordHeader(var RecordHeader: TTLSRecordHeader;
const ContentType: TTLSContentType;
const Version: TTLSProtocolVersion;
const Length: Word);
procedure DecodeTLSRecordHeader(const RecordHeader: TTLSRecordHeader;
var ContentType: TTLSContentType;
var Version: TTLSProtocolVersion;
var Length: Word);
{ }
{ Record payload MAC }
{ }
function PrepareTLSRecordPayloadMACBuffer(
var Buffer; const Size: Integer;
const ProtocolVersion: TTLSProtocolVersion;
const MACSize: Integer;
const SequenceNumber: Int64;
const TLSCompressedHdr;
const TLSCompressedBuf; const TLSCompressedBufSize: Integer): Integer;
function GenerateTLSRecordPayloadMAC(
const MACAlgorithm: TTLSMACAlgorithm;
const Key; const KeySize: Integer;
const Buf; const BufSize: Integer;
var Digest; const DigestSize: Integer): Integer;
function GenerateRecordPayloadMAC(
const ProtocolVersion: TTLSProtocolVersion;
const CipherSuiteDetails: TTLSCipherSuiteDetails;
const Key; const KeySize: Integer;
const SequenceNumber: Int64;
const TLSCompressedHdr: PTLSRecordHeader;
const TLSCompressedBuf; const TLSCompressedBufSize: Integer;
var Digest; const DigestSize: Integer): Integer;
{ }
{ Record }
{ }
function EncodeTLSRecord(
var Buffer; const Size: Integer;
const ProtocolVersion: TTLSProtocolVersion;
const ContentType: TTLSContentType;
const ContentBuffer; const ContentSize: Integer;
const CompressionMethod: TTLSCompressionMethod;
const CipherSuiteDetails: TTLSCipherSuiteDetails;
const SequenceNumber: Int64;
const MACKey; const MACKeySize: Integer;
var CipherState: TTLSCipherState;
const IVBufPtr: Pointer; const IVBufSize: Integer): Integer;
procedure DecodeTLSRecord(
const RecHeader: PTLSRecordHeader;
const Buffer; const Size: Integer;
const ProtocolVersion: TTLSProtocolVersion;
const CompressionMethod: TTLSCompressionMethod;
const CipherSuiteDetails: TTLSCipherSuiteDetails;
const SequenceNumber: Int64;
const MACKey; const MACKeySize: Integer;
var CipherState: TTLSCipherState;
const IVBufPtr: Pointer; const IVBufSize: Integer;
var ContentBuffer; const ContentBufferSize: Integer;
out ContentSize: Integer);
{ }
{ Test cases }
{ }
{$IFDEF TLS_TEST}
procedure Test;
{$ENDIF}
implementation
uses
{ System }
SysUtils,
{ Utils }
flcStdTypes,
flcHash,
{ Cipher }
flcCipherRandom,
{ TLS }
flcTLSConsts,
flcTLSErrors,
flcTLSCompress;
{ }
{ ContentType }
{ }
function TLSContentTypeToStr(const A: TTLSContentType): String;
begin
case A of
tlsctInvalid : Result := 'Invalid';
tlsctChange_cipher_spec : Result := 'Change_cipher_spec';
tlsctAlert : Result := 'Alert';
tlsctHandshake : Result := 'Handshake';
tlsctApplication_data : Result := 'Application_data';
tlsctHeartbeat : Result := 'Heartbeat';
else
Result := '[TLSContentType#' + IntToStr(Ord(A)) + ']';
end;
end;
function IsKnownTLSContentType(const A: TTLSContentType): Boolean;
begin
Result := A in [
tlsctChange_cipher_spec,
tlsctAlert,
tlsctHandshake,
tlsctApplication_data,
tlsctHeartbeat];
end;
{ }
{ TLS Record Header }
{ }
{ SSL3 / TLS 1.0 / TLS 1.1 / TLS 1.2: }
{ ContentType type }
{ ProtocolVersion version }
{ uint16 length }
{ }
procedure InitTLSRecordHeader(var RecordHeader: TTLSRecordHeader;
const ContentType: TTLSContentType;
const Version: TTLSProtocolVersion;
const Length: Word);
begin
RecordHeader._type := ContentType;
RecordHeader.version := Version;
RecordHeader.length :=
((Length and $FF) shl 8) or
(Length shr 8);
end;
procedure DecodeTLSRecordHeader(const RecordHeader: TTLSRecordHeader;
var ContentType: TTLSContentType;
var Version: TTLSProtocolVersion;
var Length: Word);
begin
ContentType := RecordHeader._type;
Version := RecordHeader.version ;
Length :=
((RecordHeader.length and $FF) shl 8) or
(RecordHeader.length shr 8);
end;
{ }
{ Record payload MAC }
{ }
{ SSL 3: }
{ hash(MAC_write_secret + pad_2 + }
{ hash(MAC_write_secret + pad_1 + }
{ seq_num + }
{ SSLCompressed.type + }
{ SSLCompressed.length + }
{ SSLCompressed.fragment)); }
{ pad_1 = The character 0x36 repeated 48 times for MD5 or 40 times for SHA. }
{ pad_2 = The character 0x5c repeated 48 times for MD5 or 40 times for SHA. }
{ }
{ TLS 1.0 / TLS 1.1 / TLS 1.2: }
{ HMAC_hash(MAC_write_key, seq_num + }
{ TLSCompressed.type + }
{ TLSCompressed.version + }
{ TLSCompressed.length + }
{ TLSCompressed.fragment); }
{ }
procedure EncodeSequenceNumber(
const SequenceNumber: Int64;
const Buf; const BufSize: Integer);
var P : PByte;
B : array[0..7] of Byte;
I : Integer;
begin
if BufSize < 8 then
raise ETLSError.Create(TLSError_InvalidBuffer);
P := @Buf;
Move(SequenceNumber, B, 8);
for I := 0 to 7 do
begin
P^ := B[7 - I];
Inc(P);
end;
end;
function CalculateSSLHash(
const Hash: TTLSCipherSuiteHash;
const Buf; const BufSize: Integer;
var Digest; const DigestSize: Integer): Integer;
var L : Integer;
begin
L := TLSCipherSuiteHashInfo[Hash].HashSize div 8;
if DigestSize < L then
raise ETLSError.Create(TLSError_InvalidBuffer);
case Hash of
tlscshMD5 : P128BitDigest(@Digest)^ := CalcMD5(Buf, BufSize);
tlscshSHA : P160BitDigest(@Digest)^ := CalcSHA1(Buf, BufSize);
else
raise ETLSError.Create(TLSError_InvalidParameter);
end;
Result := L;
end;
const
SSL_MACBufSize = (TLS_PLAINTEXT_FRAGMENT_MAXSIZE + 1024) * 2;
function GenerateSSLRecordPayloadMAC(
const Hash: TTLSCipherSuiteHash;
const Key; const KeySize: Integer;
const SequenceNumber: Int64;
const TLSCompressedHdr: PTLSRecordHeader;
const TLSCompressedBuf; const TLSCompressedBufSize: Integer;
var Digest; const DigestSize: Integer): Integer;
var PadN : Integer;
Buf1 : array[0..SSL_MACBufSize - 1] of Byte;
Len1 : Integer;
Buf2 : array[0..SSL_MACBufSize - 1] of Byte;
Len2 : Integer;
Hash1 : array[0..TLS_MAC_MAXDIGESTSIZE - 1] of Byte;
Hash1Len : Integer;
Hash2 : array[0..TLS_MAC_MAXDIGESTSIZE - 1] of Byte;
Hash2Len : Integer;
P : PByte;
begin
case Hash of
tlscshMD5 : PadN := 48;
tlscshSHA : PadN := 40;
else
raise ETLSError.Create(TLSError_InvalidParameter);
end;
// hash(MAC_write_secret + pad_1 + seq_num + SSLCompressed.type + SSLCompressed.length + SSLCompressed.fragment)
P := @Buf1;
Len1 := 0;
Move(Key, P^, KeySize);
Inc(P, KeySize);
Inc(Len1, KeySize);
FillChar(P^, PadN, #$36);
Inc(P, PadN);
Inc(Len1, PadN);
EncodeSequenceNumber(SequenceNumber, P^, SSL_MACBufSize - Len1);
Inc(P, 8);
Inc(Len1, 8);
Move(TLSCompressedHdr^._type, P^, 1);
Inc(P);
Inc(Len1);
Move(TLSCompressedHdr^.length, P^, 2);
Inc(P, 2);
Inc(Len1, 2);
Move(TLSCompressedBuf, P^, TLSCompressedBufSize);
Inc(Len1, TLSCompressedBufSize);
Hash1Len := CalculateSSLHash(Hash, Buf1, Len1, Hash1, SizeOf(Hash1));
// hash(MAC_write_secret + pad_2 + hash1)
P := @Buf2;
Len2 := 0;
Move(Key, P^, KeySize);
Inc(P, KeySize);
Inc(Len2, KeySize);
FillChar(P^, PadN, #$5C);
Inc(P, PadN);
Inc(Len2, PadN);
Move(Hash1, P^, Hash1Len);
Inc(Len2, Hash1Len);
Hash2Len := CalculateSSLHash(Hash, Buf2, Len2, Hash2, SizeOf(Hash2));
// result
if DigestSize < Hash2Len then
raise ETLSError.Create(TLSError_InvalidBuffer);
Move(Hash2, Digest, Hash2Len);
Result := Hash2Len;
end;
function PrepareTLSRecordPayloadMACBuffer(
var Buffer; const Size: Integer;
const ProtocolVersion: TTLSProtocolVersion;
const MACSize: Integer;
const SequenceNumber: Int64;
const TLSCompressedHdr;
const TLSCompressedBuf; const TLSCompressedBufSize: Integer): Integer;
var P : PByte;
N : Integer;
begin
if not IsTLS10OrLater(ProtocolVersion) then
raise ETLSError.Create(TLSError_InvalidParameter);
N := Size;
Dec(N, 8);
Dec(N, TLSRecordHeaderSize);
Dec(N, TLSCompressedBufSize);
if N < 0 then
raise ETLSError.Create(TLSError_InvalidBuffer);
P := @Buffer;
EncodeSequenceNumber(SequenceNumber, P^, Size);
Inc(P, 8);
Move(TLSCompressedHdr, P^, TLSRecordHeaderSize);
Inc(P, TLSRecordHeaderSize);
Move(TLSCompressedBuf, P^, TLSCompressedBufSize);
Result := Size - N;
end;
function GenerateTLSRecordPayloadMAC(
const MACAlgorithm: TTLSMACAlgorithm;
const Key; const KeySize: Integer;
const Buf; const BufSize: Integer;
var Digest; const DigestSize: Integer): Integer;
var L : Integer;
begin
L := TLSMACAlgorithmInfo[MACAlgorithm].DigestSize;
if DigestSize < L then
raise ETLSError.Create(TLSError_InvalidBuffer);
case MACAlgorithm of
tlsmaHMAC_MD5 : P128BitDigest(@Digest)^ := CalcHMAC_MD5(@Key, KeySize, Buf, BufSize);
tlsmaHMAC_SHA1 : P160BitDigest(@Digest)^ := CalcHMAC_SHA1(@Key, KeySize, Buf, BufSize);
tlsmaHMAC_SHA256 : P256BitDigest(@Digest)^ := CalcHMAC_SHA256(@Key, KeySize, Buf, BufSize);
tlsmaHMAC_SHA512 : P512BitDigest(@Digest)^ := CalcHMAC_SHA512(@Key, KeySize, Buf, BufSize);
else
raise ETLSError.Create(TLSError_InvalidParameter, 'Invalid MAC algorithm[' + IntToStr(Ord(MACAlgorithm)) + ']');
end;
Result := L;
end;
const
// size of temporary buffers used in record encoding and decoding
// this should be large enough to hold any plain, compressed or encrypted record
TLS_RecordBufSize = (TLS_PLAINTEXT_FRAGMENT_MAXSIZE + 1024) * 2;
function GenerateRecordPayloadMAC(
const ProtocolVersion: TTLSProtocolVersion;
const CipherSuiteDetails: TTLSCipherSuiteDetails;
const Key; const KeySize: Integer;
const SequenceNumber: Int64;
const TLSCompressedHdr: PTLSRecordHeader;
const TLSCompressedBuf; const TLSCompressedBufSize: Integer;
var Digest; const DigestSize: Integer): Integer;
var MACSize : Integer;
MACAlgo : TTLSMACAlgorithm;
BufMAC : array[0..TLS_RecordBufSize - 1] of Byte;
BufMACSize : Integer;
begin
if IsSSL3(ProtocolVersion) then
begin
Result := GenerateSSLRecordPayloadMAC(
CipherSuiteDetails.CipherSuiteInfo^.Hash,
Key, KeySize,
SequenceNumber,
TLSCompressedHdr,
TLSCompressedBuf, TLSCompressedBufSize,
Digest, DigestSize);
end
else
if IsTLS10OrLater(ProtocolVersion) then
begin
MACSize := CipherSuiteDetails.HashInfo^.HashSize div 8;
MACAlgo := TLSCipherSuiteHashInfo[CipherSuiteDetails.CipherSuiteInfo^.Hash].MACAlgorithm;
BufMACSize := PrepareTLSRecordPayloadMACBuffer(
BufMAC, SizeOf(BufMAC),
ProtocolVersion,
MACSize,
SequenceNumber,
TLSCompressedHdr^,
TLSCompressedBuf, TLSCompressedBufSize);
Result := GenerateTLSRecordPayloadMAC(
MACAlgo,
Key, KeySize,
BufMAC, BufMACSize,
Digest, DigestSize);
SecureClear(BufMAC, BufMACSize);
end
else
raise ETLSError.Create(TLSError_InvalidParameter);
end;
{ }
{ Record }
{ }
{ SSL 3: }
{ GenericStreamCipher }
{ stream-ciphered struct }
{ opaque content[SSLCompressed.length]; }
{ opaque MAC[CipherSpec.hash_size]; }
{ }
{ TLS 1.0 / 1.1 / 1.2: }
{ GenericStreamCipher }
{ stream-ciphered struct }
{ opaque content[TLSCompressed.length]; }
{ opaque MAC[SecurityParameters.mac_length]; }
{ }
{ SSL 3: }
{ GenericBlockCipher }
{ block-ciphered struct }
{ opaque content[SSLCompressed.length]; }
{ opaque MAC[CipherSpec.hash_size]; }
{ uint8 padding[GenericBlockCipher.padding_length]; }
{ uint8 padding_length; }
{ }
{ TLS 1.0: }
{ GenericBlockCipher }
{ block-ciphered struct }
{ opaque content[TLSCompressed.length]; }
{ opaque MAC[CipherSpec.hash_size]; }
{ uint8 padding[GenericBlockCipher.padding_length]; }
{ uint8 padding_length; }
{ }
{ TLS 1.1: }
{ GenericBlockCipher }
{ block-ciphered struct }
{ opaque IV[CipherSpec.block_length]; }
{ opaque content[TLSCompressed.length]; }
{ opaque MAC[CipherSpec.hash_size]; }
{ uint8 padding[GenericBlockCipher.padding_length]; }
{ uint8 padding_length; }
{ }
{ TLS 1.2: }
{ GenericBlockCipher }
{ opaque IV[SecurityParameters.record_iv_length]; }
{ block-ciphered struct }
{ opaque content[TLSCompressed.length]; }
{ opaque MAC[SecurityParameters.mac_length]; }
{ uint8 padding[GenericBlockCipher.padding_length]; }
{ uint8 padding_length; }
{ }
// Returns number of padding bytes encoded
function EncodeTLSGenericBlockCipherPadding(
var Buffer; const BufferSize: Integer;
const GenericBlockSize: Integer;
const CipherBlockSize: Integer): Integer;
var L, I, N : Integer;
P : PByte;
begin
if (CipherBlockSize <= 0) or (CipherBlockSize > 256) then
raise ETLSError.Create(TLSError_InvalidParameter);
L := (GenericBlockSize + 1) mod CipherBlockSize;
if L > 0 then
L := CipherBlockSize - L;
Assert(L <= $FF);
Assert((GenericBlockSize + L + 1) mod CipherBlockSize = 0);
N := L + 1;
if BufferSize < N then
raise ETLSError.Create(TLSError_InvalidBuffer);
P := @Buffer;
for I := 1 to L do
begin
P^ := L;
Inc(P);
end;
P^ := L;
Result := N;
end;
// Returns number of padding bytes at end of padded buffer
// Validates padding bytes
function DecodeTLSGenericBlockCipherPadding(const Buffer; const Size: Integer): Integer;
var P : PByte;
C : Byte;
L, I : Integer;
begin
P := @Buffer;
Inc(P, Size - 1);
C := P^;
L := C + 1;
if Size < L then
raise ETLSError.Create(TLSError_InvalidBuffer);
for I := 0 to C - 1 do
begin
Dec(P);
if P^ <> C then
raise ETLSError.Create(TLSError_DecodeError);
end;
Result := C + 1;
end;
// TLS 1.0: get IV from last cipher block
procedure tls10UpdateIV(
const Buffer; const Size: Integer;
var IVBuffer; const IVBufferSize: Integer);
var P : PByte;
begin
if (IVBufferSize <= 0) or (Size < IVBufferSize) then
raise ETLSError.Create(TLSError_InvalidBuffer);
P := @Buffer;
Inc(P, Size - IVBufferSize);
Move(P^, IVBuffer, IVBufferSize);
end;
// TLS 1.1 1.2: generate random IV
procedure tls11UpdateIV(var IVBuffer; const IVBufferSize: Integer);
begin
if IVBufferSize <= 0 then
raise ETLSError.Create(TLSError_InvalidBuffer);
SecureRandomBuf(IVBuffer, IVBufferSize);
end;
// Encode a TLS record and update IV
// Returns size of encoded TLS record (including header)
function EncodeTLSRecord(
var Buffer; const Size: Integer;
const ProtocolVersion: TTLSProtocolVersion;
const ContentType: TTLSContentType;
const ContentBuffer; const ContentSize: Integer;
const CompressionMethod: TTLSCompressionMethod;
const CipherSuiteDetails: TTLSCipherSuiteDetails;
const SequenceNumber: Int64;
const MACKey; const MACKeySize: Integer;
var CipherState: TTLSCipherState;
const IVBufPtr: Pointer; const IVBufSize: Integer): Integer;
var BufP : PByte;
BufLeft : Integer;
HasCipher : Boolean;
IsBlockCipher : Boolean;
UseIV : Boolean;
IVSize : Integer;
RecHeader : PTLSRecordHeader;
RecContent : Pointer;
RecMAC : Pointer;
RecCipher : Pointer;
ComprSize : Integer;
MACSize : Integer;
CipherSize : Integer;
BlockSize : Integer;
BufCipher : array[0..TLS_RecordBufSize - 1] of Byte;
CipheredSize : Integer;
FinalSize : Integer;
begin
if ContentSize > TLS_PLAINTEXT_FRAGMENT_MAXSIZE then
raise ETLSError.Create(TLSError_InvalidParameter);
// initialise parameters
HasCipher := CipherSuiteDetails.CipherSuite <> tlscsNone;
IsBlockCipher :=
HasCipher and
(CipherSuiteDetails.CipherInfo^.CipherType = tlscsctBlock);
UseIV :=
IsBlockCipher and
(CipherSuiteDetails.CipherInfo^.IVSize > 0);
if UseIV then
IVSize := CipherSuiteDetails.CipherInfo^.IVSize
else
IVSize := 0;
if UseIV and (IVBufSize < IVSize) then
raise ETLSError.Create(TLSError_InvalidBuffer);
// encode to Buffer
BufP := @Buffer;
BufLeft := Size;
// header
Dec(BufLeft, TLSRecordHeaderSize);
if BufLeft < 0 then
raise ETLSError.Create(TLSError_InvalidBuffer);
RecHeader := Pointer(BufP);
Inc(BufP, TLSRecordHeaderSize);
// TLS 1.1/1.2: generate random IV
if UseIV then
if IsTLS11OrLater(ProtocolVersion) then
tls11UpdateIV(IVBufPtr^, IVBufSize);
// TLS 1.1/1.2: encode IV field
if UseIV then
if IsTLS11OrLater(ProtocolVersion) then
begin
Dec(BufLeft, IVSize);
if BufLeft < 0 then
raise ETLSError.Create(TLSError_InvalidBuffer);
Move(IVBufPtr^, BufP^, IVSize);
Inc(BufP, IVSize);
end;
// compress content
RecContent := BufP;
TLSCompressFragment(CompressionMethod,
ContentBuffer, ContentSize,
BufP^, BufLeft,
ComprSize);
Inc(BufP, ComprSize);
Dec(BufLeft, ComprSize);
// update header with compressed details
InitTLSRecordHeader(RecHeader^, ContentType, ProtocolVersion, ComprSize);
if HasCipher then
begin
// calculate MAC
RecMAC := BufP;
MACSize := CipherSuiteDetails.HashInfo^.HashSize div 8;
GenerateRecordPayloadMAC(
ProtocolVersion,
CipherSuiteDetails,
MACKey, MACKeySize,
SequenceNumber,
RecHeader,
RecContent^, ComprSize,
RecMAC^, BufLeft);
Inc(BufP, MACSize);
Dec(BufLeft, MACSize);
// cipher size
CipherSize := ComprSize + MACSize;
// block cipher padding
if IsBlockCipher then
begin
BlockSize := CipherSuiteDetails.CipherInfo^.BlockSize;
Inc(CipherSize,
EncodeTLSGenericBlockCipherPadding(BufP^, BufLeft, CipherSize, BlockSize));
end;
// Encrypts content (excluding IV)
RecCipher := RecContent;
TLSCipherBuf(CipherState,
RecCipher^, CipherSize,
BufCipher, SizeOf(BufCipher),
CipheredSize,
IVBufPtr, IVSize);
Move(BufCipher, RecCipher^, CipheredSize);
// update IV
if UseIV then
if IsTLS10(ProtocolVersion) or IsSSL3(ProtocolVersion) then
tls10UpdateIV(RecContent^, CipheredSize, IVBufPtr^, IVBufSize);
// final size
FinalSize := CipheredSize;
if UseIV then
if IsTLS11OrLater(ProtocolVersion) then
Inc(FinalSize, IVSize);
// update header with final encrypted details
InitTLSRecordHeader(RecHeader^, ContentType, ProtocolVersion, FinalSize);
end
else
FinalSize := ComprSize;
Result := TLSRecordHeaderSize + FinalSize;
end;
// Decode a TLS record into ContentBuffer and returns size of decoded content in ContentSize
// Updates IV for next record
// Buffer points to first byte afer record header
procedure DecodeTLSRecord(
const RecHeader: PTLSRecordHeader;
const Buffer; const Size: Integer;
const ProtocolVersion: TTLSProtocolVersion;
const CompressionMethod: TTLSCompressionMethod;
const CipherSuiteDetails: TTLSCipherSuiteDetails;
const SequenceNumber: Int64;
const MACKey; const MACKeySize: Integer;
var CipherState: TTLSCipherState;
const IVBufPtr: Pointer; const IVBufSize: Integer;
var ContentBuffer; const ContentBufferSize: Integer;
out ContentSize: Integer);
var BufP : PByte;
BufPLeft : Integer;
BufQ : PByte;
BufQLeft : Integer;
HasCipher : Boolean;
IsBlockCipher : Boolean;
UseIV : Boolean;
IVSize : Integer;
BufCipher : array[0..TLS_RecordBufSize - 1] of Byte;
CipherSize : Integer;
RecContent : Pointer;
ComprSize : Integer;
BufPlain : array[0..TLS_RecordBufSize - 1] of Byte;
PlainSize : Integer;
NextIV : array[0..TLS_CIPHERSUITE_MaxIVSize - 1] of Byte;
MACSize : Integer;
PadSize : Integer;
CalcMAC : array[0..TLS_MAC_MAXDIGESTSIZE - 1] of Byte;
RecMAC : Pointer;
MACOk : Boolean;
MACIdx : Integer;
ComprHdr : TTLSRecordHeader;
P, Q : PByte;
begin
// initialise parameters
HasCipher := CipherSuiteDetails.CipherSuite <> tlscsNone;
IsBlockCipher :=
HasCipher and
(CipherSuiteDetails.CipherInfo^.CipherType = tlscsctBlock);
UseIV :=
IsBlockCipher and
(CipherSuiteDetails.CipherInfo^.IVSize > 0);
if UseIV then
IVSize := CipherSuiteDetails.CipherInfo^.IVSize
else
IVSize := 0;
Assert(IVSize <= TLS_CIPHERSUITE_MaxIVSize);
if UseIV and (IVBufSize < IVSize) then
raise ETLSError.Create(TLSError_InvalidBuffer);
if HasCipher then
begin
// decode Buffer
BufP := @Buffer;
BufPLeft := Size;
// TLS 1.2: get IV field from unencrypted buffer
if UseIV then
if IsTLS12OrLater(ProtocolVersion) then
begin
Move(BufP^, IVBufPtr^, IVSize);
Inc(BufP, IVSize);
Dec(BufPLeft, IVSize);
end;
// TLS 1.0: get IV from encrypted block
if UseIV then
if IsTLS10(ProtocolVersion) then
tls10UpdateIV(BufP^, BufPLeft, NextIV, IVSize);
// TLS 1.1: IV is first encrypted block
// decrypt from Buffer to BufCipher
TLSCipherBuf(CipherState,
BufP^, BufPLeft,
BufCipher, SizeOf(BufCipher),
CipherSize,
IVBufPtr, IVSize);
// decode decrypted Buffer
BufQ := @BufCipher;
BufQLeft := CipherSize;
// TLS 1.1: skip over IV field
if UseIV then
if IsTLS11(ProtocolVersion) then
begin
Move(BufQ^, IVBufPtr^, IVSize);
Inc(BufQ, IVSize);
Dec(BufQLeft, IVSize);
end;
// TLS 1.0: update IV
if UseIV then
if IsTLS10(ProtocolVersion) then
Move(NextIV, IVBufPtr^, IVSize);
// decode padding
if IsBlockCipher then
begin
PadSize := DecodeTLSGenericBlockCipherPadding(BufQ^, BufQLeft);
Dec(BufQLeft, PadSize);
end;
// update size for MAC
MACSize := CipherSuiteDetails.HashInfo^.HashSize div 8;
Dec(BufQLeft, MACSize);
// compressed content
RecContent := BufQ;
ComprSize := BufQLeft;
// Calculate MAC
InitTLSRecordHeader(ComprHdr, RecHeader^._type, RecHeader^.version, ComprSize);
GenerateRecordPayloadMAC(
ProtocolVersion,
CipherSuiteDetails,
MACKey, MACKeySize,
SequenceNumber,
@ComprHdr,
RecContent^, ComprSize,
CalcMAC, MACSize);
// get MAC
Inc(BufQ, ComprSize);
RecMAC := BufQ;
// validate MAC
P := @CalcMAC;
Q := RecMAC;
MACOk := True;
for MACIdx := 0 to MACSize - 1 do
if P^ <> Q^ then
begin
MACOk := False;
break;
end
else
begin
Inc(P);
Inc(Q);
end;
if not MACOk then
raise ETLSError.Create(TLSError_DecodeError);
end
else
begin
Move(Buffer, BufCipher, Size);
RecContent := @BufCipher;
ComprSize := Size;
end;
// decompress from BufCipher to BufPlain
TLSDecompressFragment(
CompressionMethod,
RecContent^, ComprSize,
BufPlain, SizeOf(BufPlain),
PlainSize);
// plain text
Move(BufPlain, ContentBuffer, PlainSize);
ContentSize := PlainSize;
end;
{ }
{ Test cases }
{ }
//// TBytes
{$IFDEF TLS_TEST}
{$ASSERTIONS ON}
procedure SelfTestPayloadMAC;
const
MACWriteKey = RawByteString(#$85#$F0#$56#$F8#$07#$1D#$B1#$89#$89#$D0#$E1#$33#$3C#$CA#$63#$F9);
var S, T, D : RawByteString;
L : Integer;
begin
// //
// Example from http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/ReadDebug.html //
// //
T := MACWriteKey;
D := RawByteString(
#$00#$00#$00#$00#$00#$00#$00#$00 + // seq_num
#$16#$03#$01#$00#$10 + // compressed hdr
#$14#$00#$00#$0C#$F2#$62#$42#$AA#$7C#$7C#$CC#$E7#$49#$0F#$ED#$AC); // handshake msg
SetLength(S, 256);
L := GenerateTLSRecordPayloadMAC(tlsmaHMAC_MD5, T[1], Length(T), D[1], Length(D), S[1], Length(S));
Assert(L = 16);
SetLength(S, L);
Assert(S = #$FA#$06#$3C#$9F#$8C#$41#$1D#$ED#$2B#$06#$D0#$5A#$ED#$31#$F2#$80);
end;
procedure Test;
begin
Assert(TLSContentTypeSize = 1);
Assert(TLSRecordHeaderSize = 5);
SelfTestPayloadMAC;
end;
{$ENDIF}
end.