{******************************************************************************} { } { Library: Fundamentals TLS } { File name: flcTLSHandshake.pas } { File version: 5.04 } { Description: TLS handshake protocol } { } { 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: Headers } { 2010/11/26 0.02 Initial development: Messages } { 2016/01/08 0.03 String changes } { 2018/07/17 5.04 Revised for Fundamentals 5. } { } {******************************************************************************} {$INCLUDE flcTLS.inc} unit flcTLSHandshake; interface uses { Utils } flcStdTypes, { Cipher } flcCipherRSA, { TLS } flcTLSProtocolVersion, flcTLSAlgorithmTypes, flcTLSRandom, flcTLSCipherSuite, flcTLSSessionID, flcTLSKeyExchangeParams, flcTLSCertificate, flcTLSHandshakeExtension; { } { HandshakeHeader } { } type TTLSHandshakeType = ( tlshtHello_request = 0, // Not used in TLS 1.3 tlshtClient_hello = 1, tlshtServer_hello = 2, tlshtNew_session_ticket = 4, // TLS 1.3 tlshtEnd_of_early_data = 5, // TLS 1.3 tlshtEncrypted_extensions = 8, // TLS 1.3 tlshtCertificate = 11, tlshtServer_key_exchange = 12, // Not used in TLS 1.3 tlshtCertificate_request = 13, tlshtServer_hello_done = 14, // Not used in TLS 1.3 tlshtCertificate_verify = 15, tlshtClient_key_exchange = 16, // Not used in TLS 1.3 tlshtFinished = 20, tlshtCertificate_url = 21, // RFC 6066 // Not used in TLS 1.3 tlshtCertificate_status = 22, // RFC 6066 // Not used in TLS 1.3 tlshtKey_update = 24, // TLS 1.3 tlshtMessage_hash = 254, // TLS 1.3 tlshtMax = 255 ); TTLSHandshakeHeader = packed record msg_type : TTLSHandshakeType; // handshake type length : array[0..2] of Byte; // bytes in message end; PTLSHandshakeHeader = ^TTLSHandshakeHeader; const TLSHandshakeHeaderSize = Sizeof(TTLSHandshakeHeader); function TLSHandshakeTypeToStr(const A: TTLSHandshakeType): String; procedure EncodeTLSHandshakeHeader( var Handshake: TTLSHandshakeHeader; const MsgType: TTLSHandshakeType; const Length: Integer); procedure DecodeTLSHandshakeHeader( const Handshake: TTLSHandshakeHeader; var MsgType: TTLSHandshakeType; var Length: Integer); function TLSHandshakeDataPtr( const Buffer; const Size: Integer; var DataSize: Integer): Pointer; function EncodeTLSHandshake( var Buffer; const Size: Integer; const MsgType: TTLSHandshakeType; const MsgSize: Integer): Integer; { } { HelloRequest } { } function EncodeTLSHandshakeHelloRequest(var Buffer; const Size: Integer): Integer; { } { ClientHello } { } type TTLSClientHello = record ProtocolVersion : TTLSProtocolVersion; SessionID : TTLSSessionID; CipherSuites : TTLSCipherSuites; CompressionMethods : TTLSCompressionMethods; Random : TTLSRandom; ExtensionsPresent : Boolean; SignAndHashAlgos : TTLSSignatureAndHashAlgorithmArray; end; procedure InitTLSClientHello( var ClientHello: TTLSClientHello; const ProtocolVersion: TTLSProtocolVersion; const SessionID: RawByteString); function EncodeTLSClientHello( var Buffer; const Size: Integer; var ClientHello: TTLSClientHello): Integer; function DecodeTLSClientHello( const Buffer; const Size: Integer; var ClientHello: TTLSClientHello): Integer; function EncodeTLSHandshakeClientHello( var Buffer; const Size: Integer; var ClientHello: TTLSClientHello): Integer; { } { ServerHello } { } type TTLSServerHello = record ProtocolVersion : TTLSProtocolVersion; SessionID : TTLSSessionID; CipherSuite : TTLSCipherSuiteRec; CompressionMethod : TTLSCompressionMethod; Random : TTLSRandom; end; procedure InitTLSServerHello( var ServerHello: TTLSServerHello; const ProtocolVersion: TTLSProtocolVersion; const SessionID: RawByteString; const CipherSuite: TTLSCipherSuiteRec; const CompressionMethod: TTLSCompressionMethod); function EncodeTLSServerHello( var Buffer; const Size: Integer; const ServerHello: TTLSServerHello): Integer; function DecodeTLSServerHello( const Buffer; const Size: Integer; var ServerHello: TTLSServerHello): Integer; function EncodeTLSHandshakeServerHello( var Buffer; const Size: Integer; const ServerHello: TTLSServerHello): Integer; { } { Certificate } { } function EncodeTLSHandshakeCertificate( var Buffer; const Size: Integer; const CertificateList: TTLSCertificateList): Integer; { } { ServerKeyExchange } { } function EncodeTLSHandshakeServerKeyExchange( var Buffer; const Size: Integer; const KeyExchangeAlgorithm: TTLSKeyExchangeAlgorithm; const ServerKeyExchange: TTLSServerKeyExchange): Integer; { } { CertificateRequest } { } type TTLSClientCertificateTypeArray = array of TTLSClientCertificateType; TTLSCertificateRequest = record CertificateTypes : TTLSClientCertificateTypeArray; SupportedSignatureAlgorithms : TTLSSignatureAndHashAlgorithmArray; DistinguishedName : RawByteString; end; function EncodeTLSCertificateRequest( var Buffer; const Size: Integer; const CertificateRequest: TTLSCertificateRequest): Integer; function DecodeTLSCertificateRequest( const Buffer; const Size: Integer; var CertificateRequest: TTLSCertificateRequest): Integer; function EncodeTLSHandshakeCertificateRequest( var Buffer; const Size: Integer; const CertificateRequest: TTLSCertificateRequest): Integer; { } { ServerHelloDone } { } function EncodeTLSHandshakeServerHelloDone(var Buffer; const Size: Integer): Integer; { } { CertificateVerify } { } function EncodeTLSHandshakeCertificateVerify(var Buffer; const Size: Integer): Integer; { } { PreMasterSecret } { } type TTLSPreMasterSecret = packed record client_version : TTLSProtocolVersion; random : array[0..45] of Byte; end; const TLSPreMasterSecretSize = SizeOf(TTLSPreMasterSecret); procedure InitTLSPreMasterSecret_Random( var PreMasterSecret: TTLSPreMasterSecret; const ClientVersion: TTLSProtocolVersion); function TLSPreMasterSecretToStr(const PreMasterSecret: TTLSPreMasterSecret): RawByteString; procedure InitTLSEncryptedPreMasterSecret_RSA( var EncryptedPreMasterSecret: RawByteString; const PreMasterSecret: TTLSPreMasterSecret; const RSAPublicKey: TRSAPublicKey); { } { ClientKeyExchange } { } function EncodeTLSHandshakeClientKeyExchange( var Buffer; const Size: Integer; const KeyExchangeAlgorithm: TTLSKeyExchangeAlgorithm; const ClientKeyExchange: TTLSClientKeyExchange): Integer; { } { Finished } { } function EncodeTLSHandshakeFinished(var Buffer; const Size: Integer; const MasterSecret: RawByteString; const ProtocolVersion: TTLSProtocolVersion; const HandshakeData: RawByteString; const SenderIsClient: Boolean): Integer; { } { ChangeCipherSpec Protocol } { } type TTLSChangeCipherSpecType = ( spectype_change_cipher_spec = 1); TTLSChangeCipherSpec = packed record _type : TTLSChangeCipherSpecType; end; PTLSChangeCipherSpec = ^TTLSChangeCipherSpec; const TLSChangeCipherSpecSize = SizeOf(TTLSChangeCipherSpec); procedure InitTLSChangeCipherSpec(var ChangeCipherSpec: TTLSChangeCipherSpec); { } { Test } { } {$IFDEF TLS_TEST} procedure Test; {$ENDIF} implementation uses { System } SysUtils, { Utils } flcStrings, flcHash, { Cipher } flcCipherRandom, { TLS } flcTLSAlert, flcTLSErrors, flcTLSPRF, flcTLSOpaqueEncoding; { } { Handshake Header } { } function TLSHandshakeTypeToStr(const A: TTLSHandshakeType): String; begin case A of tlshtHello_request : Result := 'Hello request'; tlshtClient_hello : Result := 'Client hello'; tlshtServer_hello : Result := 'Server hello'; tlshtCertificate : Result := 'Certificate'; tlshtServer_key_exchange : Result := 'Server key exchange'; tlshtCertificate_request : Result := 'Certficiate request'; tlshtServer_hello_done : Result := 'Server hello done'; tlshtCertificate_verify : Result := 'Certificate verify'; tlshtClient_key_exchange : Result := 'Client key exchange'; tlshtFinished : Result := 'Finished'; else Result := '[Handshake#' + IntToStr(Ord(A)) + ']'; end; end; procedure EncodeTLSHandshakeHeader( var Handshake: TTLSHandshakeHeader; const MsgType: TTLSHandshakeType; const Length: Integer); var I : Byte; L : Integer; begin Assert(Length >= 0); Assert(Length <= $FFFFFF); Handshake.msg_type := MsgType; L := Length; I := L div $10000; Handshake.length[0] := I; L := L - I * $10000; I := L div $100; Handshake.length[1] := I; L := L - I * $100; I := Byte(L); Handshake.length[2] := I; end; procedure DecodeTLSHandshakeHeader( const Handshake: TTLSHandshakeHeader; var MsgType: TTLSHandshakeType; var Length: Integer); begin MsgType := Handshake.msg_type; Length := Handshake.length[0] * $10000 + Handshake.length[1] * $100 + Handshake.length[2]; end; function TLSHandshakeDataPtr( const Buffer; const Size: Integer; var DataSize: Integer): Pointer; var P : PByte; begin P := @Buffer; if not Assigned(P) or (Size < TLSHandshakeHeaderSize) then raise ETLSError.Create(TLSError_InvalidBuffer); DataSize := Size - TLSHandshakeHeaderSize; Inc(P, TLSHandshakeHeaderSize); Result := P; end; function EncodeTLSHandshake( var Buffer; const Size: Integer; const MsgType: TTLSHandshakeType; const MsgSize: Integer): Integer; var L : Integer; P : PByte; begin Assert(MsgSize >= 0); P := @Buffer; L := TLSHandshakeHeaderSize + MsgSize; if not Assigned(P) or (Size < L) then raise ETLSError.CreateAlertBufferEncode; EncodeTLSHandshakeHeader(PTLSHandshakeHeader(P)^, MsgType, MsgSize); Result := L; end; { } { Hello Request } { } function EncodeTLSHandshakeHelloRequest(var Buffer; const Size: Integer): Integer; begin Result := EncodeTLSHandshake(Buffer, Size, tlshtHello_request, 0); end; { } { ClientHello } { client_version : TTLSProtocolVersion; } { random : TTLSRandom; } { session_id : TLSSessionID; } { cipher_suites_count : Word; } { cipher_suites : <2..2^16-1> TTLSCipherSuite; } { compression_methods_count : Byte; } { compression_methods : <1..2^8-1> TTLSCompressionMethod; } { select (extensions_present) } { case false: ; } { case true: Extension extensions<0..2^16-1>; } { } procedure InitTLSClientHello( var ClientHello: TTLSClientHello; const ProtocolVersion: TTLSProtocolVersion; const SessionID: RawByteString); begin ClientHello.ProtocolVersion := ProtocolVersion; InitTLSSessionID(ClientHello.SessionID, SessionID); InitTLSRandom(ClientHello.Random); end; function EncodeTLSClientHello( var Buffer; const Size: Integer; var ClientHello: TTLSClientHello): Integer; var P : PByte; L, N : Integer; C : TTLSCipherSuite; D : TTLSCompressionMethod; Suite : TTLSCipherSuiteRec; SignRSA : Boolean; begin Assert(Assigned(@Buffer)); N := Size; P := @Buffer; // client_version Dec(N, TLSProtocolVersionSize); if N < 0 then raise ETLSError.CreateAlertBufferEncode; PTLSProtocolVersion(P)^ := ClientHello.ProtocolVersion; Inc(P, TLSProtocolVersionSize); // random Dec(N, TLSRandomSize); if N < 0 then raise ETLSError.CreateAlertBufferEncode; PTLSRandom(P)^ := ClientHello.Random; Inc(P, TLSRandomSize); // session_id L := EncodeTLSSessionID(P^, N, ClientHello.SessionID); Dec(N, L); Inc(P, L); // cipher_suites L := 0; for C := Low(TTLSCipherSuite) to High(TTLSCipherSuite) do if C in ClientHello.CipherSuites then Inc(L); Assert(L <= $FFFF); EncodeTLSLen16(P^, N, L * TLSCipherSuiteRecSize); Inc(P, 2); Dec(N, 2 + L * TLSCipherSuiteRecSize); if N < 0 then raise ETLSError.CreateAlertBufferEncode; SignRSA := False; for C := Low(TTLSCipherSuite) to High(TTLSCipherSuite) do if C in ClientHello.CipherSuites then begin Suite.B1 := TLSCipherSuiteInfo[C].Rec.B1; Suite.B2 := TLSCipherSuiteInfo[C].Rec.B2; PTLSCipherSuiteRec(P)^ := Suite; Inc(P, TLSCipherSuiteRecSize); if TLSCipherSuiteInfo[C].Authentication = tlscsaRSA then SignRSA := True; end; // compression_methods L := 0; for D := Low(TTLSCompressionMethod) to High(TTLSCompressionMethod) do if D in ClientHello.CompressionMethods then Inc(L); Assert(L <= $FF); Dec(N, 1 + L); if N < 0 then raise ETLSError.CreateAlertBufferEncode; P^ := L; Inc(P); for D := Low(TTLSCompressionMethod) to High(TTLSCompressionMethod) do if D in ClientHello.CompressionMethods then begin P^ := Ord(D); Inc(P); end; // extensions { if IsTLS12(ClientHello.ProtocolVersion) then if SignRSA then begin SetLength(ClientHello.SignAndHashAlgos, 5); ClientHello.SignAndHashAlgos[0].Hash := tlshaSHA1; ClientHello.SignAndHashAlgos[0].Signature := tlssaRSA; ClientHello.SignAndHashAlgos[1].Hash := tlshaSHA224; ClientHello.SignAndHashAlgos[1].Signature := tlssaRSA; ClientHello.SignAndHashAlgos[2].Hash := tlshaSHA256; ClientHello.SignAndHashAlgos[2].Signature := tlssaRSA; ClientHello.SignAndHashAlgos[3].Hash := tlshaSHA384; ClientHello.SignAndHashAlgos[3].Signature := tlssaRSA; ClientHello.SignAndHashAlgos[4].Hash := tlshaSHA512; ClientHello.SignAndHashAlgos[4].Signature := tlssaRSA; ClientHello.ExtensionsPresent := True; L := EncodeTLSClientHelloExtension_SignatureAlgorithms(P^, N, ClientHello); Dec(N, L); end; } Result := Size - N; end; function DecodeTLSClientHello( const Buffer; const Size: Integer; var ClientHello: TTLSClientHello): Integer; var P : PByte; L, N, C, ExtType, ExtLen : Integer; I : Word; E : TTLSCipherSuite; F : TTLSCipherSuiteRec; D : TTLSCompressionMethod; begin Assert(Assigned(@Buffer)); Assert(Size >= 0); N := Size; P := @Buffer; // client_version Dec(N, TLSProtocolVersionSize); if N < 0 then raise ETLSError.CreateAlertBufferDecode; ClientHello.ProtocolVersion := PTLSProtocolVersion(P)^; Inc(P, TLSProtocolVersionSize); // random Dec(N, TLSRandomSize); if N < 0 then raise ETLSError.CreateAlertBufferDecode; ClientHello.Random := PTLSRandom(P)^; Inc(P, TLSRandomSize); // session_id L := DecodeTLSSessionID(P^, N, ClientHello.SessionID); Dec(N, L); Inc(P, L); // cipher_suites ClientHello.CipherSuites := []; L := DecodeTLSLen16(P^, N, C); Dec(N, L); Inc(P, L); Dec(N, C); if N < 0 then raise ETLSError.CreateAlertBufferDecode; C := C div TLSCipherSuiteRecSize; for I := 0 to C - 1 do begin F := PTLSCipherSuiteRec(P)^; E := GetCipherSuiteByRec(F.B1, F.B2); Include(ClientHello.CipherSuites, E); Inc(P, TLSCipherSuiteRecSize); end; // compression_methods Dec(N); if N < 0 then raise ETLSError.CreateAlertBufferDecode; ClientHello.CompressionMethods := []; C := P^; Inc(P); Dec(N, C); if N < 0 then raise ETLSError.CreateAlertBufferDecode; for I := 0 to C - 1 do begin D := PTLSCompressionMethod(P)^; Include(ClientHello.CompressionMethods, D); Inc(P, TLSCompressionMethodSize); end; // extensions ClientHello.ExtensionsPresent := N > 0; while N > 0 do begin if N < 4 then raise ETLSError.CreateAlertBufferDecode; DecodeTLSWord16(P^, N, ExtType); Inc(P, 2); Dec(N, 2); DecodeTLSLen16(P^, N, ExtLen); Inc(P, 2); Dec(N, 2); Dec(N, ExtLen); if N < 0 then raise ETLSError.CreateAlertBufferDecode; Inc(P, ExtLen); end; Result := Size - N; end; function EncodeTLSHandshakeClientHello( var Buffer; const Size: Integer; var ClientHello: TTLSClientHello): Integer; var P : Pointer; N, L : Integer; begin P := TLSHandshakeDataPtr(Buffer, Size, N); L := EncodeTLSClientHello(P^, N, ClientHello); Result := EncodeTLSHandshake(Buffer, Size, tlshtClient_hello, L); end; { } { ServerHello } { server_version : TTLSProtocolVersion; } { random : TTLSRandom; } { session_id : TLSSessionID; } { cipher_suite : TTLSCipherSuite; } { compression_method : TTLSCompressionMethod; } { select (extensions_present) } { case false: ; } { case true: Extension extensions<0..2^16-1>; } { } procedure InitTLSServerHello( var ServerHello: TTLSServerHello; const ProtocolVersion: TTLSProtocolVersion; const SessionID: RawByteString; const CipherSuite: TTLSCipherSuiteRec; const CompressionMethod: TTLSCompressionMethod); begin ServerHello.ProtocolVersion := ProtocolVersion; InitTLSSessionID(ServerHello.SessionID, SessionID); ServerHello.CipherSuite := CipherSuite; ServerHello.CompressionMethod := CompressionMethod; InitTLSRandom(ServerHello.Random); end; function EncodeTLSServerHello( var Buffer; const Size: Integer; const ServerHello: TTLSServerHello): Integer; var P : PByte; L, N : Integer; begin Assert(Assigned(@Buffer)); Assert(Size >= 0); N := Size; P := @Buffer; // server_version Dec(N, TLSProtocolVersionSize); if N < 0 then raise ETLSError.CreateAlertBufferEncode; PTLSProtocolVersion(P)^ := ServerHello.ProtocolVersion; Inc(P, TLSProtocolVersionSize); // random Dec(N, TLSRandomSize); if N < 0 then raise ETLSError.CreateAlertBufferEncode; PTLSRandom(P)^ := ServerHello.Random; Inc(P, TLSRandomSize); // session_id L := EncodeTLSSessionID(P^, N, ServerHello.SessionID); Dec(N, L); Inc(P, L); // cipher_suite Dec(N, TLSCipherSuiteRecSize); if N < 0 then raise ETLSError.CreateAlertBufferEncode; PTLSCipherSuiteRec(P)^ := ServerHello.CipherSuite; Inc(P, TLSCipherSuiteRecSize); // compression_method Dec(N, TLSCompressionMethodSize); if N < 0 then raise ETLSError.CreateAlertBufferEncode; PTLSCompressionMethod(P)^ := ServerHello.CompressionMethod; Result := Size - N; end; function DecodeTLSServerHello( const Buffer; const Size: Integer; var ServerHello: TTLSServerHello): Integer; var P : PByte; L, N : Integer; begin Assert(Assigned(@Buffer)); Assert(Size >= 0); N := Size; P := @Buffer; // server_version Dec(N, TLSProtocolVersionSize); if N < 0 then raise ETLSError.CreateAlertBufferDecode; ServerHello.ProtocolVersion := PTLSProtocolVersion(P)^; Inc(P, TLSProtocolVersionSize); // random Dec(N, TLSRandomSize); if N < 0 then raise ETLSError.CreateAlertBufferDecode; ServerHello.Random := PTLSRandom(P)^; Inc(P, TLSRandomSize); // session_id L := DecodeTLSSessionID(P^, N, ServerHello.SessionID); Dec(N, L); Inc(P, L); // cipher_suite Dec(N, TLSCipherSuiteRecSize); if N < 0 then raise ETLSError.CreateAlertBufferDecode; ServerHello.CipherSuite := PTLSCipherSuiteRec(P)^; Inc(P, TLSCipherSuiteRecSize); // compression_method Dec(N, TLSCompressionMethodSize); if N < 0 then raise ETLSError.CreateAlertBufferDecode; ServerHello.CompressionMethod := PTLSCompressionMethod(P)^; Result := Size - N; end; function EncodeTLSHandshakeServerHello( var Buffer; const Size: Integer; const ServerHello: TTLSServerHello): Integer; var P : Pointer; N, L : Integer; begin P := TLSHandshakeDataPtr(Buffer, Size, N); L := EncodeTLSServerHello(P^, N, ServerHello); Result := EncodeTLSHandshake(Buffer, Size, tlshtServer_hello, L); end; { } { Certificate } { certificate_list : <0..2^24-1> ASN.1Cert; } { } { ASN.1Cert = <1..2^24-1> opaque; } { } function EncodeTLSHandshakeCertificate( var Buffer; const Size: Integer; const CertificateList: TTLSCertificateList): Integer; var P : Pointer; N, L : Integer; begin P := TLSHandshakeDataPtr(Buffer, Size, N); L := EncodeTLSCertificate(P^, N, CertificateList); Result := EncodeTLSHandshake(Buffer, Size, tlshtCertificate, L); end; { } { ServerKeyExchange } { } function EncodeTLSHandshakeServerKeyExchange( var Buffer; const Size: Integer; const KeyExchangeAlgorithm: TTLSKeyExchangeAlgorithm; const ServerKeyExchange: TTLSServerKeyExchange): Integer; var P : Pointer; N, L : Integer; begin P := TLSHandshakeDataPtr(Buffer, Size, N); L := EncodeTLSServerKeyExchange(P^, N, KeyExchangeAlgorithm, ServerKeyExchange); Result := EncodeTLSHandshake(Buffer, Size, tlshtServer_key_exchange, L); end; { } { Certificate Request } { certificate_types : ClientCertificateType <1..2^8-1>; } { supported_signature_algorithms : SignatureAndHashAlgorithm <2^16-1>; } { certificate_authorities : DistinguishedName <0..2^16-1>; } { DistinguishedName = opaque <1..2^16-1> } { } function EncodeTLSCertificateRequest( var Buffer; const Size: Integer; const CertificateRequest: TTLSCertificateRequest): Integer; var P : PByte; N, L, I : Integer; begin Assert(Assigned(@Buffer)); Assert(Size >= 0); N := Size; P := @Buffer; // certificate_types L := Length(CertificateRequest.CertificateTypes); if (L <= 0) or (L > 255) then raise ETLSError.Create(TLSError_InvalidParameter); Dec(N, 1 + L); if N < 0 then raise ETLSError.Create(TLSError_InvalidBuffer); P^ := L; Inc(P); for I := 0 to L - 1 do begin P^ := Ord(CertificateRequest.CertificateTypes[I]); Inc(P); end; // supported_signature_algorithms L := Length(CertificateRequest.SupportedSignatureAlgorithms); EncodeTLSLen16(P^, N, L * TLSSignatureAndHashAlgorithmSize); Inc(P, 2); Dec(N, 2 + L * TLSSignatureAndHashAlgorithmSize); if N < 0 then raise ETLSError.Create(TLSError_InvalidBuffer); for I := 0 to L - 1 do begin PTLSSignatureAndHashAlgorithm(P)^ := CertificateRequest.SupportedSignatureAlgorithms[I]; Inc(P, TLSSignatureAndHashAlgorithmSize); end; // certificate_authorities L := EncodeTLSOpaque16(P^, N, CertificateRequest.DistinguishedName); Dec(N, L); Result := Size - N; end; function DecodeTLSCertificateRequest( const Buffer; const Size: Integer; var CertificateRequest: TTLSCertificateRequest): Integer; var P : PByte; N, L, I : Integer; begin Assert(Assigned(@Buffer)); Assert(Size >= 0); N := Size; P := @Buffer; // certificate_types Dec(N); if N < 0 then raise ETLSError.CreateAlertBufferDecode; L := P^; Inc(P); Dec(N, L); if N < 0 then raise ETLSError.CreateAlertBufferDecode; SetLength(CertificateRequest.CertificateTypes, L); for I := 0 to L - 1 do begin CertificateRequest.CertificateTypes[I] := TTLSClientCertificateType(P^); Inc(P); end; // supported_signature_algorithms DecodeTLSLen16(P^, N, L); Assert(L mod TLSSignatureAndHashAlgorithmSize = 0); L := L div TLSSignatureAndHashAlgorithmSize; Inc(P, 2); Dec(N, 2 + L * TLSSignatureAndHashAlgorithmSize); if N < 0 then raise ETLSError.CreateAlertBufferDecode; SetLength(CertificateRequest.SupportedSignatureAlgorithms, L); for I := 0 to L - 1 do begin CertificateRequest.SupportedSignatureAlgorithms[I] := PTLSSignatureAndHashAlgorithm(P)^; Inc(P, TLSSignatureAndHashAlgorithmSize); end; // certificate_authorities L := DecodeTLSOpaque16(P^, N, CertificateRequest.DistinguishedName); Dec(N, L); Result := Size - N; end; function EncodeTLSHandshakeCertificateRequest( var Buffer; const Size: Integer; const CertificateRequest: TTLSCertificateRequest): Integer; var P : Pointer; N, L : Integer; C : TTLSCertificateRequest; begin P := TLSHandshakeDataPtr(Buffer, Size, N); L := EncodeTLSCertificateRequest(P^, N, C); Result := EncodeTLSHandshake(Buffer, Size, tlshtCertificate_request, L); end; { } { Server Hello Done } { } function EncodeTLSHandshakeServerHelloDone(var Buffer; const Size: Integer): Integer; begin Result := EncodeTLSHandshake(Buffer, Size, tlshtServer_hello_done, 0); end; { } { Certificate Verify } { } function EncodeTLSHandshakeCertificateVerify(var Buffer; const Size: Integer): Integer; begin Result := EncodeTLSHandshake(Buffer, Size, tlshtCertificate_verify, 0); end; { } { PreMasterSecret } { client_version : ProtocolVersion; } { random : opaque[46]; } { } { EncryptedPreMasterSecret = public-key-encrypted PreMasterSecret } { Public-key-encrypted data is represented as an opaque vector <0..2^16-1>. } { Thus, the RSA-encrypted PreMasterSecret in a ClientKeyExchange is preceded } { by two length bytes. } { } procedure InitTLSPreMasterSecret_Random(var PreMasterSecret: TTLSPreMasterSecret; const ClientVersion: TTLSProtocolVersion); begin PreMasterSecret.client_version := ClientVersion; SecureRandomBuf(PreMasterSecret.random, SizeOf(PreMasterSecret.random)); end; function TLSPreMasterSecretToStr(const PreMasterSecret: TTLSPreMasterSecret): RawByteString; begin SetLength(Result, TLSPreMasterSecretSize); Move(PreMasterSecret, Result[1], TLSPreMasterSecretSize); end; procedure InitTLSEncryptedPreMasterSecret_RSA( var EncryptedPreMasterSecret: RawByteString; const PreMasterSecret: TTLSPreMasterSecret; const RSAPublicKey: TRSAPublicKey); var B : array[0..1023] of Byte; L : Integer; begin L := RSAEncrypt(rsaetRSAES_PKCS1, RSAPublicKey, PreMasterSecret, SizeOf(PreMasterSecret), B, SizeOf(B)); SetLength(EncryptedPreMasterSecret, L + 2); EncodeTLSLen16(EncryptedPremasterSecret[1], L + 2, L); Move(B, EncryptedPreMasterSecret[3], L); end; { } { ClientKeyExchange } { } { select (KeyExchangeAlgorithm) } { case rsa : EncryptedPreMasterSecret; } { case dhe_dss : } { case dhe_rsa : } { case dh_dss : } { case dh_rsa : } { case dh_anon : ClientDiffieHellmanPublic; } { } function EncodeTLSHandshakeClientKeyExchange( var Buffer; const Size: Integer; const KeyExchangeAlgorithm: TTLSKeyExchangeAlgorithm; const ClientKeyExchange: TTLSClientKeyExchange): Integer; var N, L : Integer; P : PByte; begin P := TLSHandshakeDataPtr(Buffer, Size, N); L := EncodeTLSClientKeyExchange(P^, N, KeyExchangeAlgorithm, ClientKeyExchange); Result := EncodeTLSHandshake(Buffer, Size, tlshtClient_key_exchange, L); end; { } { Finished } { } { SSL 3: } { Finished } { opaque md5_hash[16]; } { opaque sha_hash[20]; } { } { TLS 1.0 1.1 1.2: } { Finished } { opaque verify_data[12]; } { } type TSSLFinishedMD5Hash = array[0..15] of Byte; TSSLFinishedSHAHash = array[0..19] of Byte; TSSLFinished = packed record md5_hash : TSSLFinishedMD5Hash; sha_hash : TSSLFinishedSHAHash; end; PSSLFinished = ^TSSLFinished; TTLSFinished = packed record verify_data : array[0..11] of Byte; end; PTLSFinished = ^TTLSFinished; const SSLFinishedSize = SizeOf(TSSLFinished); TLSFinishedSize = Sizeof(TTLSFinished); { } { SSL 3.0: } { md5_hash = MD5(master_secret + pad2 + } { MD5(handshake_messages + Sender + master_secret + pad1)); } { sha_hash = SHA(master_secret + pad2 + } { SHA(handshake_messages + Sender + master_secret + pad1)); } { 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. } { Sender = enum ( client(0x434C4E54), server(0x53525652) ) } { } { TLS 1.0: verify_data = } { PRF(master_secret, finished_label, MD5(handshake_messages) + } { SHA-1(handshake_messages)) [0..11]; } { } { TLS 1.2: verify_data = } { PRF(master_secret, finished_label, Hash(handshake_messages)) } { [0..verify_data_length-1]; } { } { For the PRF defined in Section 5, the Hash MUST be the Hash used as the } { basis for the PRF. Any cipher suite which defines a different PRF MUST } { also define the Hash to use in the Finished computation. } { In previous versions of TLS, the verify_data was always 12 octets } { long. In the current version of TLS, it depends on the cipher suite. } { Any cipher suite which does not explicitly specify verify_data_length has a } { verify_data_length equal to 12. This includes all existing cipher suites. } { All of the data from all messages in this handshake (not } { including any HelloRequest messages) up to, but not including, } { this message. This is only data visible at the handshake layer } { and does not include record layer headers. This is the } { concatenation of all the Handshake structures as defined in } { Section 7.4, exchanged thus far. } { } procedure CalcSSLVerifyData( const MasterSecret: RawByteString; const HandshakeData: RawByteString; const SenderIsClient: Boolean; var md5_hash: TSSLFinishedMD5Hash; var sha_hash: TSSLFinishedSHAHash); var M, S, T : RawByteString; begin if SenderIsClient then T := #$43#$4C#$4E#$54 else T := #$53#$52#$56#$52; M := MD5DigestToStrA( CalcMD5(MasterSecret + DupCharB(#$5C, 48) + MD5DigestToStrA( CalcMD5(HandshakeData + T + MasterSecret + DupCharB(#$36, 48))))); S := SHA1DigestToStrA( CalcSHA1(MasterSecret + DupCharB(#$5C, 40) + SHA1DigestToStrA( CalcSHA1(HandshakeData + T + MasterSecret + DupCharB(#$36, 40))))); Move(M[1], md5_hash, 16); Move(S[1], sha_hash, 20); end; const client_finished_label = 'client finished'; server_finished_label = 'server finished'; function CalcTLSVerifyData( const ProtocolVersion: TTLSProtocolVersion; const MasterSecret: RawByteString; const HandshakeData: RawByteString): RawByteString; var VDL : Integer; D1 : T128BitDigest; D2 : T160BitDigest; D3 : T256BitDigest; Seed : RawByteString; PRF : RawByteString; begin if IsTLS12OrLater(ProtocolVersion) then begin VDL := 12; D3 := CalcSHA256(HandshakeData[1], Length(HandshakeData)); SetLength(Seed, SizeOf(D3)); Move(D3, Seed[1], SizeOf(D3)); PRF := tls12PRF_SHA256(MasterSecret, client_finished_label, Seed, VDL); end else if IsTLS10OrLater(ProtocolVersion) then begin VDL := 12; D1 := CalcMD5(HandshakeData); D2 := CalcSHA1(HandshakeData); SetLength(Seed, SizeOf(D1) + SizeOf(D2)); Move(D1, Seed[1], SizeOf(D1)); Move(D2, Seed[SizeOf(D1) + 1], SizeOf(D2)); PRF := tls10PRF(MasterSecret, client_finished_label, Seed, VDL); end else raise ETLSError.Create(TLSError_InvalidParameter); Result := PRF; end; function EncodeTLSHandshakeFinished(var Buffer; const Size: Integer; const MasterSecret: RawByteString; const ProtocolVersion: TTLSProtocolVersion; const HandshakeData: RawByteString; const SenderIsClient: Boolean): Integer; var N : Integer; P : PByte; V : RawByteString; F : PSSLFinished; begin P := TLSHandshakeDataPtr(Buffer, Size, N); if IsTLS10OrLater(ProtocolVersion) then begin V := CalcTLSVerifyData(ProtocolVersion, MasterSecret, HandshakeData); Assert(Length(V) >= 12); Move(V[1], PTLSFinished(P)^.verify_data[0], 12); Result := EncodeTLSHandshake(Buffer, Size, tlshtFinished, TLSFinishedSize); end else if IsSSL3(ProtocolVersion) then begin F := PSSLFinished(P); CalcSSLVerifyData(MasterSecret, HandshakeData, SenderIsClient, F^.md5_hash, F^.sha_hash); Result := EncodeTLSHandshake(Buffer, Size, tlshtFinished, SSLFinishedSize); end else raise ETLSError.Create(TLSError_InvalidParameter); end; { } { ChangeCipherSpec Protocol } { } procedure InitTLSChangeCipherSpec(var ChangeCipherSpec: TTLSChangeCipherSpec); begin ChangeCipherSpec._type := spectype_change_cipher_spec; end; { } { Unit test } { } {$IFDEF TLS_TEST} {$ASSERTIONS ON} procedure Test; begin Assert(TLSHandshakeHeaderSize = 4); end; {$ENDIF} end.