September 29, 2021

ICS V8.67 adds Delphi 11 and OpenSSL 3 support

ICS V8.67 has been released at: http://wiki.overbyte.eu/wiki/index.php/ICS_Download

ICS is a free internet component library for Delphi 7, 2006 to 2010, XE to XE8, 10 Seattle, 10.1 Berlin, 10.2 Tokyo, 10.3 Rio, 10.4 Sydney and 11.0 and C++ Builder 2006 to XE3, 10.2 Tokyo, 10.3 Rio, 10.4 Sydney and 11.0. ICS supports VCL and FMX, Win32, Win64 and MacOS 32-bit targets. The distribution zip includes the latest OpenSSL 1.1.1i win32, with OpenSSL 3.0 and Win64 versions of OpenSSL being available from the download page.

Changes in ICS V8.67 include:

1 - Added support and packages for RAD Studio 11.0.  Updated SSL/TLS root certificate bundles, old certificates gone, new ones added, nothing major.

2 - Added support for OpenSSL 3.0 which is a major new release, primarily a lot of internal changes to ease long term support. There is an optional FIPS module with 3.0 but not available here since our DLLs are not built to the standards required for certification. The old engines for special extensions are replaced by new more versatile providers of which the FIPS module is one, a provider legacy.dll has obsolete ciphers and hash digests, including MD2, MD4, Blowfish, DES, IDEA, RC2, RC4, SEED, that most applications no longer need and which needs to loaded by the application by setting global variable GSSLEAY_LOAD_LEGACY to true before loading OpenSSL.

3 - OpenSSL 3.0 does not offer any specific new features of benefit to ICS at present, although HTTP/3 support is planned for 3.1 or later, so the main ICS distribution retains OpenSSL 1.1.1i which is fully supported until September 2023. OpenSSL 3.0 may be downloaded from the download page. There are two global variables to restrict which OpenSSL version is loaded, GSSLEAY_DLL_IgnoreNew set true will ignore 3.0, while GSSLEAY_DLL_IgnoreOld will ignore 1.1.1, if both sets of DLLs are available in the same directory. The main SSL samples all set these globals, which can be changed for  testing one version or the other, or set by the application, but must be before OpenSSL is initialised.

4 - The main implication for ICS with OpenSSL 3.0 is for SSL/TLS certificate private keys saved with password protection, which is required for PKCS12 certificates for importing into the Windows certificate store. The new PKCS12 default password encryption AES256 is not recognised until Windows Server 2016 v1709  and Windows 10 v1709, so Server 2012, Windows 10 RTM and earlier won't load AES passworded keys, only 3DES, for which the legacy.dll must be loaded.

5 - The TX509Base class has various improvements. The ValidateCertChain method reports CA roots for multiple certificate verification paths with two or more intermediate certificates, rather than only the last. The CertMainInfo method provides a single line with the main certificate information.

6 - There are two new classes to write and read SSL/TLS certificates to and from the Windows Certificate Store, including private keys. This is primarily so Let's Encrypt certificates can be installed automatically for use with the IIS web server.  TMsX509List descends from TX509List adding a method LoadFromStore to load the list from a Windows certificate store by store name TMsCertStore and location MsCertLocation. For My/Personal store, attempts to load private keys if they are allowed to be exported unencrypted. TMsCertTools descends from TSslCertTools adding methods SaveToStorePfx and LoadFromMyStore to access Windows certificate stores. Note access to the Local Machine Store for web server certificates requires administrator rights.

7 - Various improvements for the OverbyteIcsPemTool sample.  It includes new buttons to list the  contents of Windows certificate and private key stores and allow old items to be deleted.  This may be useful for cleaning up old certificates and private keys from the Windows stores. Added ResavePrivateKey and Resave Private Key menu option which prompt for a PFX or PEM file containing an encrypted  private key with a new cipher, renaming old file to .oldpem/pfx.  Specifically for files saved with old ciphers than OpenSSL 3.0 does not support as standard if required for older versions of Windows.  Displaying certificates and bundles is no longer a new modal window, but updates the existing log window. Improved import certificates from Windows certificate store to use TMsX509List instead of Windows API calls, and to access all Windows store locations instead of just user, specifically the Local Machine store where server certificates are located.

8 - For the TX509Certs component, the default cipher for encrypting PFX/P12 files is now PrivKeyEncAES256 with 3.0 unless the legacy DLL is loaded when still PrivKeyEncTripleDES so older versions of Windows can load them. Changed extraction of download PEM bundle so that main certificate does not need to be first in file, log them all, and ignore any self signed root certificates. If testing dns-01 challenge fails, rotate to next public server and three retries (previously only happened on timeout). When saving files with private keys, log encryption type used. Added more certificate output formats, OutFmtPwPem and OutFmtPwP12 specify whether to password PEM and P12/PFX private keys. Note Windows always needs passworded P12/PRX files, while Apache web server only accepts PEM files without a password. Allow automatic installation of new certificates to the Windows Certificate Store so they can be used by IIS web sites, by setting output format to OutFmtWinStore.  Note application must have administrator rights to do this.

9 - Fixed two problems in the FTP client, support option ftpFixPasvLanIP for PUT/APPE uploads as well as  downloads, and support IPv6 for PUT/APPE uploads as well as downloads.

10 - Fixed a problem in TIcsMailQueue with sequential number generation to avoid file locking errors and unicode BOM corrupting file, generate large random number for errors instead of reverting to 1.  Don't save BOM withunicode compilers.

11 - In the Application Web Server TSslHttpAppSrv, added an optional LastModified parameter to the AnswerStream, AnswerPage, and AnswerString methods to avoid adding a custom header line with the date.  Added NO_CACHE_EX and NO_STORE_EX literals. Added PUT and DELETE verb handlers, similar to GET and POST.

12 - For the HTTP client TSslHttpCli, fixed a relocation problem where the Location: header included a path with a space, encode the space. Fixed another relocation problem where HEAD sometimes stalled. Remove # fragment or anchor from URL in relocation, only used by browsers and not by servers.

13 - In the TIcsBlackList component, Internally use BlockedFlag instead of setting attempts to 9999 once the actual maximum failed attempts is reached, so we can keep counting attempts.

14 - Added a new SSL sample, OverbyteIcsDDWebService.dpr which is very similar to OverbyteIcsSslMultiWebServ.dpr, but designed as a Windows service, although it will also run as a GUI for debugging.  It requires DDService service framework to be installed from https://www.magsys.co.uk/delphi/ddservice. asp. It also includes a REST server with simple lookup responses from a SQL
database, which optionally requires DISQLite3 5.36.5 or later to be installed from http://www.yunqa.de. Note this sample in not in the project groups due to these pre-requisites.

15 - Moved TRestParams from the OverbyteIcsSslHttpRest unit to OverbyteIcsUrl to ease circular references. Added a new method AddItemNULL to add a null, in Json this will be unquoted. Added a new TRestParamsSrv component which provides methods for creating REST server Json responses from a SQL database resultset, one or more rows, also error responses. Note this is only compiled if DATABASE is defined in OverbyteIcsDefs.inc to avoid bringing in database units that are not available on all Delphi editions.  There is a REST server sample OverbyteIcsDDWebService.dpr that illustrates SQL lookups.

16 - In the proxy component TIcsHttpProxy, don't send an HTTP request header until after HTTP body has been processed in case the body length changes. HTTP Forward Proxy using HTTP works again, broken in V8.65. Using HTTP Forward Proxy, convert absolute URL to path only since some servers can not process an absolute URL and sulk.

17 - In the Jose unit, rewrote the functions converting private keys to and from Json Web Keys with new OpenSSL 3.0 provider functions. Use AnsiStrings and functions when dealing with binary data to avoid possible issues with string conversions and nulls.  Json now created with TRestParams.

18 - Added two new sample project groups, OtherDemos64 and SslDemos64 which include Win64 versions of all the main active samples with 64 added to the project name, so they can be regularly built alongside the Win32 versions without changing platforms and overwriting executables.

 

September 12, 2021

ICS (Internet Component Suite) updated for Delphi 11

 I have the pleasure to announce that ICS is available for Delphi 11.

You can download the update from download page.

Enjoy!


August 2, 2021

Hide user data using impersonation



This article is based on a question seen on StackOverflow: File permission to a specific application.
(https://stackoverflow.com/questions/68457656/file-permission-to-a-specific-application).

In that question a developer ask how he can hide a data file so that the user cannot see it and yet have his application access the file.

A developer noted in a comment “If you don't want the configuration file to be readable you should encrypt the data and decrypt it as part of your program “. Another one said: “Windows security is user-based, not application-based. If the file is accessible to a given user, and that same user runs both your app and Notepad, then both apps will have access to the file “.

Sure, encryption is a valid solution to the problem. There are a lot of encryption/decryption libraries available for Delphi, including free one such as Delphi Encryption Compendium available for free on GitHub (https://github.com/MHumm/DelphiEncryptionCompendium).

I wanted to develop an alternate and probably easier method to access a data file from an application and yet prohibit the user access the file with another application and even prevent the user to see the file exists!

The key to this solution is to make the application logon as another user and store the file in that other user profile.

Sound complex? Maybe but actually, it is very simple. There are only two system calls required:
  • LogonUser to provide credential for that user authentication (Username, domain and password) and get a handle.
  • ImpersonateLoggedOnUser to ask Windows to momentarily forget about the current user and act as the newly logged on user.
Later, to return to the normal situation, that is real logged on user, two other calls are required:
  • RevertToSelf
  • CloseHandle.
I have encapsulated the above sequences in a simple class with two methods: Logon and Logoff.

    

type

    TImpersonateUser = class(TComponent)

    protected

        FUserToken : THandle;

        FErrorCode : DWORD;

    public

        destructor Destroy; override;

        function  Logon(const UserName : String;

                        const Domain   : String;

                        const Password : String) : Boolean;

        procedure Logoff();

        property ErrorCode : DWORD read FErrorCode;

    end;

 


The implementation is straightforward:


destructor TImpersonateUser.Destroy;

begin

    if FUserToken <> 0 then begin

        CloseHandle(FUserToken);

        FUserToken := 0;

    end;

    inherited Destroy;

end;


procedure TImpersonateUser.Logoff;

begin

    if FUserToken <> 0 then begin

        RevertToSelf();   // Revert to our user

        CloseHandle(FUserToken);

        FUserToken := 0;

    end;

end;


function TImpersonateUser.Logon(

    const UserName : String;

    const Domain   : String;

    const Password : String): Boolean;

var

    LoggedOn : Boolean;

begin

    Result := FALSE;

    if FUserToken <> 0 then

        Logoff();


    if UserName = '' then begin // Must at least provide a user name

        FErrorCode := ERROR_BAD_ARGUMENTS;

        Exit;

    end;


    if Domain <> '' then

        LoggedOn := LogonUser(PChar(UserName),

                              PChar(Domain),

                              PChar(Password),

                              LOGON32_LOGON_INTERACTIVE,

                              LOGON32_PROVIDER_DEFAULT,

                              FUserToken)

    else

        LoggedOn := LogonUser(PChar(UserName),

                              PChar(Domain),

                              PChar(Password),

                              LOGON32_LOGON_NEW_CREDENTIALS,

                              LOGON32_PROVIDER_WINNT50,

                              FUserToken);

    if not LoggedOn then begin

        FErrorCode := GetLastError();

        Exit;

    end;


    if not ImpersonateLoggedOnUser(FUserToken) then begin

        FErrorCode := GetLastError();

        Exit;

    end;


    FErrorCode := ERROR_SUCCESS;

    Result     := TRUE;

end;

 

The sequences of operations in TImpersonateUser are limited to the thread issuing the calls. You may have – for example – the main thread acting as the current user and simultaneously have another thread acting as another user account of your choice. Of course, you need the credentials for that user account.

The use case for the developer who posted the question I mentioned in the beginning is to create a new Windows account for the sole purpose of storing the configuration files for all other users. The configuration files could be stored in a subdirectory of the Documents folder. One subdirectory for each other user using the application.

To further show how simple it is, I created a new TStream derived class that can directly access a file in another user account. I named this class TImpersonateFileStream. It derives from TFileStream, overriding the constructor to first impersonate the user (logon), then call the inherited TFileStream constructor and then immediately revert back (logoff) to the current Windows User. The stream remains opened with the access right of the given user event after logoff because the permissions are defined by the file handle when opened and persists during the live time of the file handle.

With TImpersonateFileStream you code exactly like with a TFileStream beside the 3 additional arguments of the constructor (Usercode, domain and password). If logon fails, you’ll get an exception.

The interface part is like this:

type

    TImpersonateFileStream = class(TFileStream)

    protected

        FImpersonate   : TImpersonateUser;

    public

        constructor Create(const AFileName  : String;

                           const AMode      : Word;

                           const AUserName  : String;

                           const ADomain    : String;

                           const APassword  : String); overload;

        destructor Destroy; override;

    end;

 

and the implementation is very simple:


constructor TImpersonateFileStream.Create(

    const AFileName  : String;

    const AMode      : Word;

    const AUserName  : String;

    const ADomain    : String;

    const APassword  : String);

begin

    // If no user name given, behave like a TFileStream (No cross account)

    if AUserName = '' then begin

        inherited Create(AFileName, AMode);

        Exit;

    end;


    // A username is given, try to logon the user before opening the file

    // and logoff once the file is opened (The file will be accessed as

    // the user used to logon, even after logoff).

    FImpersonate := TImpersonateUser.Create(nil);

    if not FImpersonate.Logon(AUserName, ADomain, APassword) then

         raise EImpersonateFileStream.CreateFmt('Logon error %d. %s.',

                             [FImpersonate.ErrorCode,

                              SysErrorMessage(FImpersonate.ErrorCode)]);

    try

        inherited Create(AFileName, AMode);

    finally

        FImpersonate.Logoff;

    end;

end;


destructor TImpersonateFileStream.Destroy;

begin

    FreeAndNil(FImpersonate);

    inherited Destroy;

end;

 

I made a demo with edit box to specify the user account credentials and the file, and a few buttons to call various TStream methods.

It is easy to write code similar to TImpersonateFileStream.Create, to do other operation like DeleteFile or RenameFile. Just put that code between logon and logoff as is TImpersonateFileStream.Create.

Full source code for the article, including demo, can be found on GitHub at https://github.com/fpiette/Delphi-ImpersonateUser

If you need help with this code, please use StackOverflow.com to ask for your question, be sure to use the tag #delphi so that I receive a notification. I’ll try to answer your question. Before asking, please review “How to Ask” https://stackoverflow.com/questions/how-to-ask