969 lines
36 KiB
ObjectPascal
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.
|
|
|
|
|
|
|