{ +-------------------------------------------------------------+ } { | | } { | GM-Software | } { | =========== | } { | | } { | Project: All Projects | } { | | } { | Description: OpenSSL Transport Layer Stream. | } { | | } { | | } { | Copyright (C) - 2013 - Gerrit Moeller. | } { | | } { | Source code distributed under MIT license. | } { | | } { | See: https://www.gm-software.de | } { | | } { +-------------------------------------------------------------+ } {$INCLUDE GMCompilerSettings.inc} unit GMOpenSSL; interface uses {$IFDEF JEDIAPI}{$IFNDEF FPC}jwaWinType,{$ENDIF}{$ENDIF} GMStrDef, GMCollections, GMIntf, GMCommon, GMSockets, GMOpenSSLApi; type EGMOpenSSLProtocolSite = (cpsClient, cpsServer, cpsBoth); EGMTlsProtocolVersion = (pvAuto, pvTLSv1, pvTLSv1_1, pvTLSv1_2); IGMOpenSslApiCtxPtr = interface(IUnknown) ['{7CDF1549-5FC0-43FC-A680-F26990B0F4B9}'] function ApiCtxPtr: Pointer; stdcall; end; IGMTlsProtocolVersionAndSite = interface(IUnknown) ['{CA2DB22D-CA7E-4E94-9584-8DC82379A53C}'] function GetProtocolVersion: EGMTlsProtocolVersion; function GetProtocolSite: EGMOpenSSLProtocolSite; property ProtocolVersion: EGMTlsProtocolVersion read GetProtocolVersion; property ProtocolSite: EGMOpenSSLProtocolSite read GetProtocolSite; end; // // Used for lookup // TGMOpenSslContextBase = class(TGMRefCountedObj, IGMTlsProtocolVersionAndSite) protected FProtocolVersion: EGMTlsProtocolVersion; FProtocolSite: EGMOpenSSLProtocolSite; public constructor Create(const AProtocolVersion: EGMTlsProtocolVersion = pvAuto; const AProtocolSite: EGMOpenSSLProtocolSite = cpsBoth; const ARefLifeTime: Boolean = True); reintroduce; overload; virtual; function GetProtocolVersion: EGMTlsProtocolVersion; function GetProtocolSite: EGMOpenSSLProtocolSite; end; TGMOpenSslContext = class(TGMOpenSslContextBase, IGMOpenSslApiCtxPtr) protected FSslCtx: PSSL_CTX; public // constructor Create(const AProtocolVersion: EGMTlsProtocolVersion = pvAuto; const AProtocolSite: EGMOpenSSLProtocolSite = cpsBoth; const ARefLifeTime: Boolean = True); override; destructor Destroy; override; function ApiCtxPtr: Pointer; stdcall; end; IGMShowCertificateVerifyStatus = interface(IUnknown) ['{40E5752A-E010-4B54-BE12-C00679AB6745}'] procedure ShowCertificateVerifyStatus(const AHost: TGMString; const ACertCode: Int64); end; IGMTlsSocket = interface(IGMOpenSslApiCtxPtr) ['{21D4912C-B9FA-47C9-BD47-4792FE285BDE}'] procedure ExecTlsNegotiation; stdcall; procedure Initialize; stdcall; function Initialized: Boolean; stdcall; // procedure CopySession(const ASrcTlsSocket: IUnknown); end; TGMOpenSslSocketBase = class(TGMRefCountedObj, IGMSocketIO, IGMOpenSslApiCtxPtr) protected FTransportSocket: IGMSocket; FCertificateData: AnsiString; FSsl: PSSL; FProtocolVersion: EGMTlsProtocolVersion; FCheckNonBlockingErrorCode: IGMCheckNonBlockingErrorCode; function CheckIORetCode(const ARetCode: LongInt; const ASSLRoutineName: TGMString; const ARaiseIfShutdown: Boolean = False): LongInt; public function SendData(const AData: Pointer; const ADataSize: LongInt): LongInt; stdcall; function ReceiveData(const AData: Pointer; const ADataSize: LongInt): LongInt; stdcall; // function IsDataAvailable: Boolean; stdcall; function ApiCtxPtr: Pointer; stdcall; end; TGMOpenSslClientSocket = class(TGMOpenSslSocketBase, IGMTlsSocket) protected FCertificateNotifySink: IGMShowCertificateVerifyStatus; FNegotiated: Boolean; FTargetHostName: AnsiString; // procedure DisplayCertifaceVerifyStatus; procedure NotifyCertifateVerifyStatus; public constructor Create(const ATransportSocket: IGMSocket; const ATargetHostName: TGMString; const ACertificateStatusNotifySink: IUnknown = nil; const ACertificateData: AnsiString = ''; const AProtocolVersion: EGMTlsProtocolVersion = pvAuto; const ARefLifeTime: Boolean = True); reintroduce; destructor Destroy; override; // procedure CopySession(const ASrcSocket: IUnknown); procedure ShutDown; procedure Initialize; stdcall; function Initialized: Boolean; stdcall; procedure ExecTlsNegotiation; stdcall; end; TGMOpenSslCertStore = class(TGMRefCountedObj, IGMAssignToObj) protected FCertStore: PX509_STORE; public constructor Create(const ARefLifeTime: Boolean = True); override; constructor CreateFromWinCerts(const AWinCertStoreFolderPaths: TGMStringArray; const ARefLifeTime: Boolean = True); destructor Destroy; override; procedure LoadFromWinCerts(const AWinCertStoreFolderPath: TGMString); procedure AssignToObj(const ADest: TObject); stdcall; end; // // Use an seprate object to avoid circular refernces to object containing the sockets // TGMCertMessageEmitter = class(TGMRefCountedObj, IGMShowCertificateVerifyStatus) protected FAppendText: IGMAppendText; FCertMessagesShown: IGMIntfCollection; public constructor Create(const AUnkProtocol: IUnknown; const ARefLifeTime: Boolean = True); reintroduce; procedure ShowCertificateVerifyStatus(const AHost: TGMString; const ACertCode: Int64); end; EGMTlsException = class(EGMException); EGMTlsConnectionShutdown = class(EGMTlsException, IGMGetHRCode) public function GetHRCode: HResult; stdcall; end; procedure GMExecTlsNegotiation(const ATransportLayer: IUnknown); function GMAddTlsLayer(const ATransportLayer: IGMSocket; const ATargetHostName: TGMString; const ACertificateStatusNotifySink: IUnknown = nil; const ACertificateData: AnsiString = ''; const AProtocolVersion: EGMTlsProtocolVersion = pvAuto; const AExecuteTlsNegoatiation: Boolean = True): IGMSocketIO; procedure GMCopyTlsSession(const ASrcTlsSocket, ADstTlsSocket: IUnknown; const ACaller: TObject = nil); //procedure EmitCertificateVerifyStatus(const AProtokolUnk: IUnknown; const ACertCode: Int64); implementation uses GMSocketAPI {$IFDEF JEDIAPI},jwaWinBase, jwaWinCrypt{$ENDIF}; resourcestring RStrUnknownSSLError = 'Unknown SSL Error'; RStrInvalidSSLPtotocolVersion = 'Invalid SSL/TLS protocol version: %d'; RStrInvalidSSLPtotocolSite = 'Invalid SSL/TLS protocol site: %d'; RStrCleanlyShutdown = 'The SSL/TLS connection has been shut down cleanly'; RStrNoServerCertificate = 'The server did not send a certificate'; RStrAnonymousDiffieHellman = 'Anonymous Diffie-Hellman (ADH)'; RStrUnknownCertificateErrCode = 'Unknown server certificate validation error code: %d'; //X509_V_OK = 0; //X509_V_ILLEGAL = 1; RStrX509_V_ERR_UNABLE_TO_GET_ISSUER_CERT = 'The issuer certificate could not be found: this occurs if the issuer certificate of an untrusted certificate cannot be found'; RStrX509_V_ERR_UNABLE_TO_GET_CRL = 'The certificate revocation list of a certificate could not be found'; RStrX509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE = 'Unable to decrypt certificate''s signature'; RStrX509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE = 'Unable to decrypt the signature of the certifacte revocation list'; RStrX509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY = 'Unable to decode issuer public key'; RStrX509_V_ERR_CERT_SIGNATURE_FAILURE = 'The signature of the certificate is invalid'; RStrX509_V_ERR_CRL_SIGNATURE_FAILURE = 'The signature of the certificate revocation list is invalid'; RStrX509_V_ERR_CERT_NOT_YET_VALID = 'The certificate is not yet valid'; RStrX509_V_ERR_CERT_HAS_EXPIRED = 'The certificate has expired'; RStrX509_V_ERR_CRL_NOT_YET_VALID = 'The certificate revocation list is not yet valid'; RStrX509_V_ERR_CRL_HAS_EXPIRED = 'The certificate revocation list has expired'; RStrX509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD = 'The "notBefore" field of the certificate contains an invalid time value'; RStrX509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD = 'The "notAfter" field of the certificate contains an invalid time value'; RStrX509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD = 'The "lastUpdate" field of the certificate revocation list contains an invalid time value'; RStrX509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD = 'The "nextUpdate" field of the certificate revocation list contains an invalid time value'; RStrX509_V_ERR_OUT_OF_MEM = 'Out of memory'; RStrX509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT = 'The passed certificate is self signed and the same certificate cannot be found in the list of trusted certificates'; RStrX509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN = 'The certificate chain could be built up using the untrusted certificates but the root could not be found locally'; RStrX509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY = 'The issuer certificate of a locally looked up certificate could not be found. This normally means the list of trusted certificates is not complete'; RStrX509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE = 'No signatures could be verified because the chain contains only one certificate and it is not self signed'; RStrX509_V_ERR_CERT_CHAIN_TOO_LONG = 'Certificate chain too long'; RStrX509_V_ERR_CERT_REVOKED = 'The certificate has been revoked'; RStrX509_V_ERR_INVALID_CA = 'Invalid authority certificate'; RStrX509_V_ERR_PATH_LENGTH_EXCEEDED = 'The "basicConstraints" pathlength parameter has been exceeded'; RStrX509_V_ERR_INVALID_PURPOSE = 'The supplied certificate cannot be used for the specified purpose'; RStrX509_V_ERR_CERT_UNTRUSTED = 'The root authority is not marked as trusted for the specified purpose'; RStrX509_V_ERR_CERT_REJECTED = 'The root authority is marked to reject the specified purpose'; //These are 'informational' when looking for issuer cert RStrX509_V_ERR_SUBJECT_ISSUER_MISMATCH = 'The current candidate issuer certificate was rejected because its subject name did not match the issuer name of the current certificate'; RStrX509_V_ERR_AKID_SKID_MISMATCH = 'The current candidate issuer certificate was rejected because its subject key identifier was present and did not match the authority key identifier current certificate'; RStrX509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH = 'the current candidate issuer certificate was rejected because its issuer name and serial number was present and did not match the authority key identifier of the current certificate'; RStrX509_V_ERR_KEYUSAGE_NO_CERTSIGN = 'The current candidate issuer certificate was rejected because its keyUsage extension does not permit certificate signing'; RStrX509_V_ERR_UNABLE_TO_GET_CRL_ISSUER = 'Unable to get certificate revocation list issuer'; RStrX509_V_ERR_UNHANDLED_CRITICAL_EXTENSION = 'Unhandeled critical extension'; //The application is not happy RStrX509_V_ERR_APPLICATION_VERIFICATION = 'Application verification failure'; RStrSSLVeriyPrefix = 'SSL/TLS certificate verification'; RStrCertiVerifyOK = 'The server certificate is valid'; RStrForHostFmt = 'for host %s'; RStrOpenSSLRoutineFailedFmt = 'OpenSSL routine "%s" failed'; RStrExecutingTLSNegotiation = 'Executing SSL/TLS negotiation'; const cNoServerCert = -1; var vCSSslContext: IGMCriticalSection = nil; vCSSslCertStore: IGMCriticalSection = nil; vGMTlsContexts: IGMIntfCollection = nil; vGMOpenSSLCertStore: IGMAssignToObj = nil; { ------------------------- } { ---- Global Routines ---- } { ------------------------- } function OpenSSLErrorString(const AErrorCode: LongWord): AnsiString; begin SetLength(Result, 2048); ERR_error_string_n(AErrorCode, PAnsiChar(Result), Length(Result)+1); Result := PAnsiChar(Result); end; procedure GMSslCheck(const ASuccess: Boolean; const ACaller: TObject; const ACallingName: TGMString; const APrefix: TGMString = ''); var errorCode: LongWord; errorMsg, stateStr: AnsiString; begin if ASuccess then Exit; errorMsg := ''; repeat errorCode := ERR_get_error; if errorCode <> 0 then errorMsg := GMStringJoin(errorMsg, cNewLine, GMStringJoin('('+GMIntToStr(errorCode)+')', ' ', OpenSSLErrorString(errorCode))); until errorCode = 0; // ErrClearError; if Length(errorMsg) <= 0 then errorMsg := RStrUnknownSSLError; errorMsg := GMStringJoin(APrefix, ': ', errorMsg); if ACaller is TGMOpenSslSocketBase then begin stateStr := SSL_state_string_long(TGMOpenSslSocketBase(ACaller).FSsl); if Length(stateStr) > 0 then stateStr := 'SSL State: ' + stateStr; errorMsg := GMStringJoin(errorMsg, cNewLine, stateStr); end; raise EGMTlsException.ObjError(errorMsg, ACaller, ACallingName); end; procedure GMOpenSSLRoutineFailed(ARoutineName: TGMString; const ACaller: TObject = nil; const ACallingName: AnsiString = ''); begin ARoutineName := GMStrip(ARoutineName); if (Length(ARoutineName) > 0) and (ARoutineName[Length(ARoutineName)] <> ')') then ARoutineName := ARoutineName + '(...)'; raise EGMTlsException.ObjError(GMFormat(RStrOpenSSLRoutineFailedFmt, [ARoutineName]), ACaller, ACallingName); end; function GMCertValidateMsgFromCode(const ACertVerifyCode: Int64): TGMSTring; begin case ACertVerifyCode of // X509_V_ILLEGAL = 1; cNoServerCert: Result := RStrNoServerCertificate + ' (' + RStrAnonymousDiffieHellman + ')'; X509_V_OK: Result := RStrCertiVerifyOK; X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: Result := RStrX509_V_ERR_UNABLE_TO_GET_ISSUER_CERT; X509_V_ERR_UNABLE_TO_GET_CRL: Result := RStrX509_V_ERR_UNABLE_TO_GET_CRL; X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: Result := RStrX509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE; X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: Result := RStrX509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE; X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: Result := RStrX509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY; X509_V_ERR_CERT_SIGNATURE_FAILURE: Result := RStrX509_V_ERR_CERT_SIGNATURE_FAILURE; X509_V_ERR_CRL_SIGNATURE_FAILURE: Result := RStrX509_V_ERR_CRL_SIGNATURE_FAILURE; X509_V_ERR_CERT_NOT_YET_VALID: Result := RStrX509_V_ERR_CERT_NOT_YET_VALID; X509_V_ERR_CERT_HAS_EXPIRED: Result := RStrX509_V_ERR_CERT_HAS_EXPIRED; X509_V_ERR_CRL_NOT_YET_VALID: Result := RStrX509_V_ERR_CRL_NOT_YET_VALID; X509_V_ERR_CRL_HAS_EXPIRED: Result := RStrX509_V_ERR_CRL_HAS_EXPIRED; X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: Result := RStrX509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD; X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: Result := RStrX509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD; X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: Result := RStrX509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD; X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: Result := RStrX509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD; X509_V_ERR_OUT_OF_MEM: Result := RStrX509_V_ERR_OUT_OF_MEM; X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: Result := RStrX509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT; X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: Result := RStrX509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN; X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: Result := RStrX509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY; X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: Result := RStrX509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE; X509_V_ERR_CERT_CHAIN_TOO_LONG: Result := RStrX509_V_ERR_CERT_CHAIN_TOO_LONG; X509_V_ERR_CERT_REVOKED: Result := RStrX509_V_ERR_CERT_REVOKED; X509_V_ERR_INVALID_CA: Result := RStrX509_V_ERR_INVALID_CA; X509_V_ERR_PATH_LENGTH_EXCEEDED: Result := RStrX509_V_ERR_PATH_LENGTH_EXCEEDED; X509_V_ERR_INVALID_PURPOSE: Result := RStrX509_V_ERR_INVALID_PURPOSE; X509_V_ERR_CERT_UNTRUSTED: Result := RStrX509_V_ERR_CERT_UNTRUSTED; X509_V_ERR_CERT_REJECTED: Result := RStrX509_V_ERR_CERT_REJECTED; //These are 'informational' when looking for issuer cert X509_V_ERR_SUBJECT_ISSUER_MISMATCH: Result := RStrX509_V_ERR_SUBJECT_ISSUER_MISMATCH; X509_V_ERR_AKID_SKID_MISMATCH: Result := RStrX509_V_ERR_AKID_SKID_MISMATCH; X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: Result := RStrX509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH; X509_V_ERR_KEYUSAGE_NO_CERTSIGN: Result := RStrX509_V_ERR_KEYUSAGE_NO_CERTSIGN; X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER: Result := RStrX509_V_ERR_UNABLE_TO_GET_CRL_ISSUER; X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: Result := RStrX509_V_ERR_UNHANDLED_CRITICAL_EXTENSION; //The application is not happy X509_V_ERR_APPLICATION_VERIFICATION: Result := RStrX509_V_ERR_APPLICATION_VERIFICATION; else Result := GMFormat(RStrUnknownCertificateErrCode, [ACertVerifyCode]); end; end; procedure GMExecTlsNegotiation(const ATransportLayer: IUnknown); var tlsSocket: IGMTlsSocket; begin GMCheckQueryInterface(ATransportLayer, IGMTlsSocket, tlsSocket); tlsSocket.ExecTlsNegotiation; end; function GMAddTlsLayer(const ATransportLayer: IGMSocket; const ATargetHostName: TGMString; const ACertificateStatusNotifySink: IUnknown; const ACertificateData: AnsiString; const AProtocolVersion: EGMTlsProtocolVersion; const AExecuteTlsNegoatiation: Boolean): IGMSocketIO; begin if ATransportLayer = nil then Exit(nil); Result := TGMOpenSslClientSocket.Create(ATransportLayer, ATargetHostName, ACertificateStatusNotifySink, ACertificateData, AProtocolVersion); if AExecuteTlsNegoatiation then GMExecTlsNegotiation(Result); end; procedure GMCopyTlsSession(const ASrcTlsSocket, ADstTlsSocket: IUnknown; const ACaller: TObject); var srcTls, dstTls: IGMTlsSocket; session: Pointer; begin if GMQueryInterface(ASrcTlsSocket, IGMTlsSocket, srcTls) and srcTls.Initialized and GMQueryInterface(ADstTlsSocket, IGMTlsSocket, dstTls) then begin dstTls.Initialize; session := SSL_get_session(srcTls.ApiCtxPtr); if session <> nil then GMSslCheck(SSL_set_session(dstTls.ApiCtxPtr, session) <> 0, ACaller, 'SSL_set_session'); // SSLCopySessionId(FSsl, ApiCtxPtr.ApiCtxPtr); end; end; function GMOpenSSLCertStore: IGMAssignToObj; begin vCSSslCertStore.EnterCriticalSection; try if vGMOpenSSLCertStore = nil then vGMOpenSSLCertStore := TGMOpenSslCertStore.CreateFromWinCerts(['ROOT', 'CA', 'AuthRoot', 'MY']); Result := vGMOpenSSLCertStore; finally vCSSslCertStore.LeaveCriticalSection; end; end; function GMTlsContext(const AProtocolVersion: EGMTlsProtocolVersion; const AProtocolSite: EGMOpenSSLProtocolSite): IGMOpenSslApiCtxPtr; var toFind, unkFound: IUnknown; // syncLock: IUnknown; begin //syncLock := TGMCriticalSectionLock.Create(vCSSslContext); vCSSslContext.EnterCriticalSection; try toFind := TGMOpenSslContextBase.Create(AProtocolVersion, AProtocolSite); if not vGMTlsContexts.Find(toFind, unkFound) then begin unkFound := vGMTlsContexts.Add(TGMOpenSslContext.Create(AProtocolVersion, AProtocolSite, True)); GMOpenSSLCertStore.AssignToObj(GMObjFromIntf(unkFound)); end; GMCheckQueryInterface(unkFound, IGMOpenSslApiCtxPtr, Result); finally vCSSslContext.LeaveCriticalSection; end; end; function GMCompareTlsContext(const ItemA, ItemB: IUnknown): TGMCompareResult; var ctxA, ctxB: IGMTlsProtocolVersionAndSite; begin GMCheckQueryInterface(ItemA, IGMTlsProtocolVersionAndSite, ctxA, {$I %CurrentRoutine%}); GMCheckQueryInterface(ItemB, IGMTlsProtocolVersionAndSite, ctxB, {$I %CurrentRoutine%}); if ctxA.ProtocolVersion > ctxB.ProtocolVersion then Result := crAGreaterThanB else if ctxA.ProtocolVersion = ctxB.ProtocolVersion then Result := crAEqualToB else Result := crALessThanB; if Result = crAEqualToB then begin if ctxA.ProtocolSite > ctxB.ProtocolSite then Result := crAGreaterThanB else if ctxA.ProtocolSite = ctxB.ProtocolSite then Result := crAEqualToB else Result := crALessThanB; end; end; function SSLSybolicErrorName(const AErrorCode: LongInt) : TGMString; begin case AErrorCode of SSL_ERROR_NONE: Result := 'SSL_ERROR_NONE'; SSL_ERROR_SSL: Result := 'SSL_ERROR_SSL'; SSL_ERROR_WANT_READ: Result := 'SSL_ERROR_WANT_READ'; SSL_ERROR_WANT_WRITE: Result := 'SSL_ERROR_WANT_WRITE'; SSL_ERROR_WANT_X509_LOOKUP: Result := 'SSL_ERROR_WANT_X509_LOOKUP'; SSL_ERROR_SYSCALL: Result := 'SSL_ERROR_SYSCALL'; //look at error stack/return value/errno SSL_ERROR_ZERO_RETURN: Result := 'SSL_ERROR_ZERO_RETURN'; SSL_ERROR_WANT_CONNECT: Result := 'SSL_ERROR_WANT_CONNECT'; SSL_ERROR_WANT_ACCEPT: Result := 'SSL_ERROR_WANT_ACCEPT'; SSL_ERROR_WANT_ASYNC: Result := 'SSL_ERROR_WANT_ASYNC'; SSL_ERROR_WANT_ASYNC_JOB: Result := 'SSL_ERROR_WANT_ASYNC_JOB'; SSL_ERROR_WANT_CLIENT_HELLO_CB: Result := 'SSL_ERROR_WANT_CLIENT_HELLO_CB'; else Result := 'SSL error code ('+GMIntToStr(AErrorCode)+')'; end; end; //procedure EmitCertificateVerifyStatus(const AProtokolUnk: IUnknown; const ACertCode: Int64); //const clrOrange = $0080FF; cGreen = $008000; //var rprtText: IGMAppendText; msg: TGMString; //begin //if GMQueryInterface(AProtokolUnk, IGMAppendText ,rprtText) then // begin // msg := GMTerminateStr(GMCertValidateMsgFromCode(ACertCode)); // case ACertCode of // 0: rprtText.AppendText(' '+RStrSSLVeriyPrefix+': '+msg, cGreen, True) // else rprtText.AppendText(' '+RStrSSLVeriyPrefix+' '+GMSeverityName(svWarning)+': '+msg, clrOrange, True); // end; // end; //end; { ---------------------------------- } { ---- EGMTlsConnectionShutdown ---- } { ---------------------------------- } function EGMTlsConnectionShutdown.GetHRCode: HResult; begin Result := GMSocketHrResult(WSAECONNABORTED); end; { ----------------------------- } { ---- TGMOpenSslCertStore ---- } { ----------------------------- } constructor TGMOpenSslCertStore.Create(const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); FCertStore := X509_STORE_new; if FCertStore = nil then GMOpenSSLRoutineFailed('X509_STORE_new', Self); //GMSslCheck(FStore <> nil, Self, 'X509_STORE_new'); end; constructor TGMOpenSslCertStore.CreateFromWinCerts(const AWinCertStoreFolderPaths: TGMStringArray; const ARefLifeTime: Boolean); var certPath: TGMString; begin Create(ARefLifeTime); for certPath in AWinCertStoreFolderPaths do LoadFromWinCerts(certPath); //LoadFromWinCerts(AWinCertStoreFolderPaths); end; destructor TGMOpenSslCertStore.Destroy; begin if FCertStore <> nil then begin X509_STORE_free(FCertStore); FCertStore := nil; end; inherited; end; procedure TGMOpenSslCertStore.LoadFromWinCerts(const AWinCertStoreFolderPath: TGMString); // https://stackoverflow.com/questions/39772878/reliable-way-to-get-root-ca-certificates-on-windows/40710806#40710806 // //#include <boost/asio/ssl/context.hpp> //#include <wincrypt.h> // //void add_windows_root_certs(boost::asio::ssl::context &ctx) //{ // HCERTSTORE hStore = CertOpenSystemStore(0, "ROOT"); // if (hStore == NULL) { // return; // } // // X509_STORE *store = X509_STORE_new(); // PCCERT_CONTEXT pContext = NULL; // while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != NULL) { // X509 *x509 = d2i_X509(NULL, // (const unsigned char **)&pContext->pbCertEncoded, // pContext->cbCertEncoded); // if(x509 != NULL) { // X509_STORE_add_cert(store, x509); // X509_free(x509); // } // } // // CertFreeCertificateContext(pContext); // CertCloseStore(hStore, 0); // // SSL_CTX_set_cert_store(ctx.native_handle(), store); //} var winCertStore: HCERTSTORE; pWinCertCtx: PCCERT_CONTEXT; p_x509: PX509; n: Integer; begin if Length(AWinCertStoreFolderPath) <= 0 then Exit; winCertStore := CertOpenSystemStore(0, PGMChar(AWinCertStoreFolderPath)); GMApiCheckObj('CertOpenSystemStore("'+AWinCertStoreFolderPath+'")', '', GetLastError, winCertStore <> nil, Self); try pWinCertCtx := nil; n := 0; try repeat pWinCertCtx := CertEnumCertificatesInStore(winCertStore, pWinCertCtx); if pWinCertCtx = nil then Break; p_x509 := d2i_X509(nil, @pWinCertCtx.pbCertEncoded, pWinCertCtx.cbCertEncoded); if p_x509 <> nil then begin X509_STORE_add_cert(FCertStore, p_x509); X509_free(p_x509); Inc(n); end; until False; finally if pWinCertCtx <> nil then CertFreeCertificateContext(pWinCertCtx); end; finally CertCloseStore(winCertStore, 0); vfGMTrace(GMIntToStr(n) + ' Certificates in "'+AWinCertStoreFolderPath+'"', 'CERTIFICATE'); end; end; procedure TGMOpenSslCertStore.AssignToObj(const ADest: TObject); begin if ADest is TGMOpenSslContext then begin // SSL_CTX_set1_cert_store(TGMOpenSslContext(ADest).ApiCtxPtr, FCertStore); <- not in DLL file .. SSL_CTX_set_cert_store(TGMOpenSslContext(ADest).ApiCtxPtr, FCertStore); X509_STORE_up_ref(FCertStore); end; end; { ------------------------------- } { ---- TGMCertMessageEmitter ---- } { ------------------------------- } constructor TGMCertMessageEmitter.Create(const AUnkProtocol: IUnknown; const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); GMQueryInterface(AUnkProtocol, IGMAppendText, FAppendText); end; procedure TGMCertMessageEmitter.ShowCertificateVerifyStatus(const AHost: TGMString; const ACertCode: Int64); var nameObj: IUnknown; procedure EmitCertMsg; const clrOrange = $0080FF; cGreen = $008000; var msg, hostPart: TGMString; begin if FAppendText = nil then Exit; msg := GMTerminateStr(GMCertValidateMsgFromCode(ACertCode)); hostPart := ' ' + GMFormat(RStrForHostFmt, [AHost]); case ACertCode of 0: FAppendText.AppendText(' '+RStrSSLVeriyPrefix+hostPart+': '+msg, cGreen, True) else FAppendText.AppendText(' '+RStrSSLVeriyPrefix+' '+GMSeverityName(svWarning)+hostPart+': '+msg, clrOrange, True); end; end; begin if FAppendText = nil then Exit; if FCertMessagesShown = nil then FCertMessagesShown := TGMIntfArrayCollection.Create(False, True, GMCompareByName); nameObj := TGMNameObj.Create(GMIntToStr(ACertCode) + '-' + AHost); if not GMCollectionContains(FCertMessagesShown, nameObj) then begin EmitCertMsg; FCertMessagesShown.Add(nameObj); end; end; { ------------------------------- } { ---- TGMOpenSslContextBase ---- } { ------------------------------- } constructor TGMOpenSslContextBase.Create(const AProtocolVersion: EGMTlsProtocolVersion; const AProtocolSite: EGMOpenSSLProtocolSite; const ARefLifeTime: Boolean); begin Create(ARefLifeTime); FProtocolVersion := AProtocolVersion; FProtocolSite := AProtocolSite; end; function TGMOpenSslContextBase.GetProtocolVersion: EGMTlsProtocolVersion; begin Result := FProtocolVersion; end; function TGMOpenSslContextBase.GetProtocolSite: EGMOpenSSLProtocolSite; begin Result := FProtocolSite; end; { --------------------------- } { ---- TGMOpenSslContext ---- } { --------------------------- } constructor TGMOpenSslContext.Create(const AProtocolVersion: EGMTlsProtocolVersion; const AProtocolSite: EGMOpenSSLProtocolSite; const ARefLifeTime: Boolean); //var retVal: Int64; options: QWord; begin inherited Create(AProtocolVersion, AProtocolSite, ARefLifeTime); case AProtocolSite of cpsClient: case AProtocolVersion of pvTLSv1: FSslCtx := SSL_CTX_new(TLSv1_client_method); pvTLSv1_1: FSslCtx := SSL_CTX_new(TLSv1_1_client_method); pvTLSv1_2: FSslCtx := SSL_CTX_new(TLSv1_2_client_method); pvAuto: FSslCtx := SSL_CTX_new(TLS_client_method); else raise EGMTlsException.ObjError(GMFormat(RStrInvalidSSLPtotocolVersion, [Ord(FProtocolVersion)]), Self, {$I %CurrentRoutine%}); end; cpsServer: case AProtocolVersion of pvTLSv1: FSslCtx := SSL_CTX_new(TLSv1_server_method); pvTLSv1_1: FSslCtx := SSL_CTX_new(TLSv1_1_server_method); pvTLSv1_2: FSslCtx := SSL_CTX_new(TLSv1_2_server_method); pvAuto: FSslCtx := SSL_CTX_new(TLS_server_method); else raise EGMTlsException.ObjError(GMFormat(RStrInvalidSSLPtotocolVersion, [Ord(FProtocolVersion)]), Self, {$I %CurrentRoutine%}); end; cpsBoth: case AProtocolVersion of pvTLSv1: FSslCtx := SSL_CTX_new(TLSv1_method); pvTLSv1_1: FSslCtx := SSL_CTX_new(TLSv1_1_method); pvTLSv1_2: FSslCtx := SSL_CTX_new(TLSv1_2_method); pvAuto: FSslCtx := SSL_CTX_new(TLS_method); else raise EGMTlsException.ObjError(GMFormat(RStrInvalidSSLPtotocolVersion, [Ord(FProtocolVersion)]), Self, {$I %CurrentRoutine%}); end; else raise EGMTlsException.ObjError(GMFormat(RStrInvalidSSLPtotocolSite, [Ord(AProtocolSite)]), Self, {$I %CurrentRoutine%}); end; GMSslCheck(FSslCtx <> nil, Self, 'TlsMethod_Xxx'); SSL_CTX_set_verify(FSslCtx, SSL_VERIFY_NONE, nil); //options := SSL_CTX_set_options(FSslCtx, SSL_OP_ALLOW_CLIENT_RENEGOTIATION); //options := SSL_CTX_set_options(FSslCtx, SSL_OP_NO_SSLv3 or SSL_OP_NO_TLSv1 or SSL_OP_NO_TLSv1_1); // SSL_OP_NO_TLSv1_1 //options := 0; //retVal := SSL_CTX_ctrl(FSslCtx, SSL_CTRL_SET_MIN_PROTO_VERSION, TLS1_3_VERSION, nil); //retVal := 0; end; destructor TGMOpenSslContext.Destroy; begin if FSslCtx <> nil then begin SSL_CTX_free(FSslCtx); FSslCtx := nil; ERR_remove_thread_state(nil); end; // ERR_remove_state(0); inherited; end; function TGMOpenSslContext.ApiCtxPtr: Pointer; begin Result := FSslCtx; end; { ------------------------------ } { ---- TGMOpenSslSocketBase ---- } { ------------------------------ } function TGMOpenSslSocketBase.ApiCtxPtr: Pointer; begin Result := FSsl; end; function TGMOpenSslSocketBase.CheckIORetCode(const ARetCode: LongInt; const ASSLRoutineName: TGMString; const ARaiseIfShutdown: Boolean): LongInt; var errCode: LongInt; procedure CheckNonBlockingErrorCode(const AIOErrorCode: LongInt; const AOperation: TSockOperation; const ASuccesCodes: array of PtrInt; const ASocketAPIRoutineName: TGMString); begin if FCheckNonBlockingErrorCode = nil then GMCheckQueryInterface(FTransportSocket, IGMCheckNonBlockingErrorCode, FCheckNonBlockingErrorCode, {$I %CurrentRoutine%}); FCheckNonBlockingErrorCode.CheckNonBlockingErrorCode(AIOErrorCode, AOperation, ASuccesCodes, ASocketAPIRoutineName); end; begin Result := ARetCode; if Result <= 0 then begin errCode := SSL_get_error(FSsl, ARetCode); case errCode of // SSL_ERROR_WANT_CONNECT SSL_ERROR_NONE: ; // <- nothing (success), only possible if ARetCode > 0 SSL_ERROR_WANT_READ: CheckNonBlockingErrorCode(WSAEWOULDBLOCK, ioReceive, [], ASSLRoutineName); SSL_ERROR_WANT_WRITE: CheckNonBlockingErrorCode(WSAEWOULDBLOCK, ioSend, [], ASSLRoutineName); // SSL_ERROR_WANT_ACCEPT: FTransportSocket.Obj.CheckNonBlockingErrorCode(WSAEWOULDBLOCK, ioAccept, [], ASSLRoutineName); SSL_ERROR_ZERO_RETURN: if not ARaiseIfShutdown then Result := 0 else raise EGMTlsConnectionShutdown.ObjError(GMStringJoin(SSLSybolicErrorName(errCode), ': ', RStrCleanlyShutdown), Self, {$I %CurrentRoutine%}); //SSL_ERROR_SYSCALL, SSL_ERROR_SSL: GMSslCheck(False, Self, ASSLRoutineName); // <- SSL_ERROR_SYSCALL, SSL_ERROR_SSL else GMSslCheck(False, Self, ASSLRoutineName, SSLSybolicErrorName(errCode)); end; end; end; //function TGMOpenSslSocketBase.IsDataAvailable: Boolean; //begin //Result := (FTransportSocket <> nil) and FTransportSocket.IsDataAvailable; //end; function TGMOpenSslSocketBase.ReceiveData(const AData: Pointer; const ADataSize: LongInt): LongInt; begin repeat Result := CheckIORetCode(SSL_read(FSsl, AData, ADataSize), 'SSL_read'); until Result >= 0; end; function TGMOpenSslSocketBase.SendData(const AData: Pointer; const ADataSize: LongInt): LongInt; begin repeat Result := CheckIORetCode(SSL_write(FSsl, AData, ADataSize), 'SSL_write'); until Result >= 0; end; { -------------------------------- } { ---- TGMOpenSslClientSocket ---- } { -------------------------------- } constructor TGMOpenSslClientSocket.Create(const ATransportSocket: IGMSocket; const ATargetHostName: TGMString; const ACertificateStatusNotifySink: IUnknown; const ACertificateData: AnsiString; const AProtocolVersion: EGMTlsProtocolVersion; const ARefLifeTime: Boolean); begin inherited Create(ARefLifeTime); FTransportSocket := ATransportSocket; FProtocolVersion := AProtocolVersion; GMQueryInterface(ACertificateStatusNotifySink, IGMShowCertificateVerifyStatus, FCertificateNotifySink); FCertificateData := ACertificateData; FTargetHostName := ATargetHostName; //ExecTlsNegotiation; end; destructor TGMOpenSslClientSocket.Destroy; begin try ShutDown; except on ex: TObject do GMTraceException(ex); end; if FSsl <> nil then begin SSL_free(FSsl); FSsl := nil; end; inherited; end; procedure TGMOpenSslClientSocket.ShutDown; var retVal: LongInt; begin if not FNegotiated then Exit; repeat retVal := SSL_shutdown(FSsl); if retVal < 0 then CheckIORetCode(retVal, 'SSL_shutdown'); until retVal >= 0; FNegotiated := False; end; procedure TGMOpenSslClientSocket.Initialize; // // The creation of the Context may take some time, so better dont do this directly in the constructor. // var tlsCtx: IGMOpenSslApiCtxPtr; retVal: Int64; // options: QWord; function IsLetter(ACh: AnsiChar): Boolean; begin Result := ((ACh >= 'A') and (ACh <= 'Z')) or ((ACh >= 'a') and (ACh <= 'z')); end; function IsFileName(const AValue: AnsiString): Boolean; begin Result := (Length(AValue) > 2) and IsLetter(AValue[1]) and (AValue[2] = ':') and (AValue[3] = '\'); end; begin if FSsl <> nil then Exit; tlsCtx := GMTlsContext(FProtocolVersion, cpsClient); FSsl := SSL_new(tlsCtx.ApiCtxPtr); GMSslCheck(FSsl <> nil, Self, 'SSL_new'); //GMSslCheck(SSL_ctrl(FSsl, SSL_CTRL_SET_MIN_PROTO_VERSION, TLS1_2_VERSION, nil) = 1, Self, 'SSL_CTRL_SET_MIN_PROTO_VERSION'); //options := SSL_set_options(FSsl, SSL_OP_NO_SSLv3 or SSL_OP_NO_TLSv1 or SSL_OP_NO_TLSv1_1); // SSL_OP_NO_TLSv1_1 //SSL_set_options(FSsl, ); if Length(FTargetHostName) > 0 then begin retVal := SSL_ctrl(FSsl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, PAnsiChar(FTargetHostName)); end; if Length(FCertificateData) > 0 then if IsFileName(FCertificateData) then GMSslCheck(SSL_use_certificate_file(FSsl, PAnsiChar(FCertificateData), SSL_FILETYPE_PEM) = 1, Self, 'SSL_use_certificate_file') else // begin // GMSslCheck(SslCtxUseCertificateASN1(tlsCtx.ApiCtxPtr, Length(FCertificateData), PAnsiChar(FCertificateData)) = 1, Self, 'SslCtxUseCertificateASN1'); GMSslCheck(SSL_use_certificate_ASN1(FSsl, PAnsiChar(FCertificateData), Length(FCertificateData)) = 1, Self, 'SSL_use_certificate_ASN1'); // end; end; function TGMOpenSslClientSocket.Initialized: Boolean; begin Result := FSsl <> nil; end; //procedure TGMOpenSslClientSocket.CopySession(const ASrcSocket: IUnknown); //var ApiCtxPtr: IGMOpenSslApiCtxPtr; session: Pointer; //begin //Initialize; //if (FSsl <> nil) and GMQueryInterface(ASrcSocket, IGMOpenSslApiCtxPtr, ApiCtxPtr) and (ApiCtxPtr.ApiCtxPtr <> nil) then // begin // session := SSL_get_session(ApiCtxPtr.ApiCtxPtr); // if session <> nil then GMSslCheck(SSL_set_session(FSsl, session) <> 0, Self, 'SSL_set_session'); //// SSLCopySessionId(FSsl, ApiCtxPtr.ApiCtxPtr); // end; //end; procedure TGMOpenSslClientSocket.NotifyCertifateVerifyStatus; var host: TGMString; verifyCode: Int64; pServerCert: PX509; begin if FNegotiated and (FCertificateNotifySink <> nil) then begin if (FTransportSocket <> nil) and (FTransportSocket.RemoteAddress <> nil) then host := FTransportSocket.RemoteAddress.Obj.ResolvedHost else host := '?'; pServerCert := SSL_get_peer_certificate(FSsl); try if pServerCert = nil then verifyCode := cNoServerCert else verifyCode := SSL_get_verify_result(FSsl); FCertificateNotifySink.ShowCertificateVerifyStatus(host, verifyCode); finally if pServerCert <> nil then X509_free(pServerCert); end; end; end; procedure TGMOpenSslClientSocket.ExecTlsNegotiation; var retVal: LongInt; begin if FNegotiated then Exit; vfGMTrace(RStrExecutingTLSNegotiation, 'SSL/TLS'); Initialize; GMSslCheck(SSL_set_fd(FSsl, FTransportSocket.Socket) = 1, Self, 'SSL_set_fd'); repeat retVal := CheckIORetCode(SSL_connect(FSsl), 'SSL_connect', True); until retVal >= 0; //GMSslCheck(retVal > 0, Self, 'SslConnect'); FNegotiated := True; //DisplayCertifaceVerifyStatus; NotifyCertifateVerifyStatus; end; initialization vCSSslContext := TGMCriticalSection.Create; vCSSslCertStore := TGMCriticalSection.Create; vGMTlsContexts := TGMIntfArrayCollection.Create(False, True, GMCompareTlsContext, True); //InitializeOpenSSL; end.