/*
  ICS PEM Certificate Tool - C++Builder translation of the Delphi demo
  OverbyteIcsPemTool1.pas (demos-delphi-vcl), ICS V9.8.

  Lists, displays, converts and creates SSL/TLS certificates, private keys and
  certificate requests using the TSslCertTools, TX509Base and TX509List components,
  checks certificate chains and OCSP status, and tests the certificates of remote
  hosts with TIcsIpStrmLog. Results are written to the log window (frmPemTool2).

  How this C++ version differs from the Delphi original, apart from syntax:
  - The Windows Certificate Store tools (Delphi {$IFDEF MSCRYPT_Tools}) are built only
    when the ICS C++ library has OverbyteIcsMsSslUtils.hpp (ICS_HAVE_MSCRYPT, set in the
    header). The standard ICS build undefines MSCRYPT_Tools for C++Builder and has no such
    header; then this is the Delphi demo's own {$ELSE} path: the store box is hidden and
    the handlers bound to its controls (and Check Bundle Windows, Install ICS Root) do
    nothing.
  - The store tools' chain engine and certificate list are form members (Delphi: a unit
    global and a field).
  - Components and certificate lists are created with no Owner and held in
    std::unique_ptr (Delphi: Create(Self) plus Free/FreeAndNil).
  - The unit globals (ColumnToSort, VerifyDir, StartTickCount, the certificate lists,
    ClosingFlag) are members of the form.
  - The ini settings are saved and restored from tables of controls, with the key
    derived from the control name, instead of one hand-written line per control.
  - Delphi's PathAddBackSlash is replaced by DirWithDelimiter (below) and its copy of
    DirectoryExists by the RTL's; IsDirEmpty and EmptyDirectory, used only by the Windows
    store export, use TDirectory::GetFiles.
  - Delphi demo bugs are fixed here; each fix has a one-line comment.
*/

#include <vcl.h>
#pragma hdrstop

#include <commctrl.h>
#include <memory>
#include <vector>
#include <System.IOUtils.hpp>

#include "OverbyteIcsPemTool1.h"
#include "OverbyteIcsPemTool2.h"
#include "OverbyteIcsPemTool3.h"
#include "OverbyteIcsUtils.hpp"
#include "OverbyteIcsTicks64.hpp"
#include "OverbyteIcsIniFiles.hpp"
#include "OverbyteIcsWSocket.hpp"
#include "OverbyteIcsLIBEAY.hpp"

#pragma package(smart_init)
#pragma resource "*.dfm"

TfrmPemTool1 *frmPemTool1;

namespace {

const wchar_t *const SectionMainWindow    = L"MainWindow";
const wchar_t *const SectionDisplayWindow = L"DisplayWindow";
const wchar_t *const SectionData          = L"Data";
const wchar_t *const KeyTop               = L"Top";
const wchar_t *const KeyLeft              = L"Left";
const wchar_t *const KeyWidth             = L"Width";
const wchar_t *const KeyHeight            = L"Height";
const wchar_t *const KeyVerifyDir         = L"VerifyDir";

// Delphi's PathAddBackSlash raises a range check error for an empty directory, while the
// RTL's IncludeTrailingPathDelimiter turns "" into "\" (the drive root); this raises instead.
String DirWithDelimiter(const String &Dir)
{
    if (Dir.IsEmpty())
        throw Exception("Directory name is empty");
    return IncludeTrailingPathDelimiter(Dir);
}

// Disables a button while a long operation runs and re-enables it however the scope
// ends (Delphi: try/finally).
class TButtonBusy
{
public:
    explicit TButtonBusy(TButton *Btn) : FBtn(Btn) { FBtn->Enabled = false; }
    ~TButtonBusy() { FBtn->Enabled = true; }
    TButtonBusy(const TButtonBusy &) = delete;
    TButtonBusy &operator=(const TButtonBusy &) = delete;
private:
    TButton *FBtn;
};

// BeginUpdate/EndUpdate pair for list view items, ended on every exit path.
class TItemsUpdate
{
public:
    explicit TItemsUpdate(TListItems *Items) : FItems(Items) { FItems->BeginUpdate(); }
    ~TItemsUpdate() { FItems->EndUpdate(); }
    TItemsUpdate(const TItemsUpdate &) = delete;
    TItemsUpdate &operator=(const TItemsUpdate &) = delete;
private:
    TListItems *FItems;
};

/* Lists the major certificate entries separately; this also documents how to read
   the different properties of TX509Base. Multiple items are normally separated by
   CRLF, but are unwrapped here for display. Rarely does a certificate have all these
   entries, particularly the personal names, which are really for email certificates. */
String ListCertDetail(TX509Base *Cert)
{
    if (!Cert)
        return "No certificate loaded";

    String Result = String("ISSUED TO (Subject)\r\n") +
        "Common Name (CN): " + IcsUnwrapNames(Cert->SubjectCName) + "\r\n" +
        "Subject Alt Names: " + Cert->SubAltAllDisp + "\r\n" +
        "Organisation (O): " + IcsUnwrapNames(Cert->SubjectOName) + "\r\n" +
        "Organisational Unit (OU): " + IcsUnwrapNames(Cert->SubjectOUName) + "\r\n" +
        "Country (C): " + Cert->SubjectCOName + "\r\n" +
        "State/Province(ST): " + Cert->SubjectSTName + "\r\n" +
        "Locality (L): " + Cert->SubjectLName + "\r\n" +
        "Title (T): " + Cert->GetNameEntryByNid(true, NID_title) + "\r\n" +
        "Initials (I): " + Cert->GetNameEntryByNid(true, NID_initials) + "\r\n" +
        "Given Name (G): " + Cert->GetNameEntryByNid(true, NID_givenName) + "\r\n" +
        "Surname (S): " + Cert->GetNameEntryByNid(true, NID_surname) + "\r\n" +
        "Description (D): " + Cert->GetNameEntryByNid(true, NID_description) + "\r\n" +
        "Email (Email): " + Cert->SubjectEmailName + "\r\n" +
        "Raw Public Key: \r\n" +
        IcsFormatHexStr(String(IcsBase64EncodeTB(Cert->X509PubKeyTB)), 0, 64) + "\r\n" +
        "\r\n";
    if (Cert->SelfSigned)
        Result += "SELF SIGNED\r\n";
    else
        Result += String("ISSUED BY\r\n") +
            "Common Name (CN): " + IcsUnwrapNames(Cert->IssuerCName) + "\r\n" +
            "Organisation (O): " + IcsUnwrapNames(Cert->IssuerOName) + "\r\n" +
            "Organisational Unit (OU): " + IcsUnwrapNames(Cert->IssuerOUName) + "\r\n" +
            "Country (C): " + Cert->IssuerCOName + "\r\n" +
            "State/Province(ST): " + Cert->IssuerSTName + "\r\n" +
            "Locality (L): " + Cert->IssuerLName + "\r\n" +
            "Email (Email): " + Cert->IssuerEmailName + "\r\n";
    Result += String("\r\nGENERAL\r\n") +
        "Serial Number: " + Cert->SerialNumHex + "\r\n" +
        "Issued on (UTC): " + DateTimeToStr(Cert->ValidNotBefore) + "\r\n" +
        "Expires on (UTC): " + DateTimeToStr(Cert->ValidNotAfter) + "\r\n" +
        "Basic Constraints: " + IcsUnwrapNames(Cert->BasicConstraints) + "\r\n" +
        "Key Usage: " + IcsUnwrapNames(Cert->KeyUsage) + "\r\n" +
        "Extended Key Usage: " + IcsUnwrapNames(Cert->ExKeyUsage) + "\r\n" +
        "Authority Info, OCSP: " + Cert->UrlOcsp + "\r\n" +
        "Authority Info, Issuer Cert: " + Cert->UrlIssuer + "\r\n" +
        "Certificate Policies: " + IcsUnwrapNames(Cert->CertPolicies) + "\r\n" +
        "CRL Distribution Points: " + IcsUnwrapNames(Cert->CRLDistribution) + "\r\n" +
        "Authority Key Identifier: " + IcsUnwrapNames(Cert->AuthorityKeyId) + "\r\n" +
        "Subject Key Identifier: " + IcsUnwrapNames(Cert->SubjectKeyId) + "\r\n" +
        "Signature Algorithm: " + Cert->SignatureAlgorithm + "\r\n" +
        "Fingerprint (sha1): " + IcsLowerCase(Cert->Sha1Hex) + "\r\n" +
        "Fingerprint (sha56): " + IcsLowerCase(Cert->Sha256Hex) + "\r\n";
    const TExtension Ext = Cert->GetExtensionByName("acmeIdentifier");
    if (!Ext.Value.IsEmpty())
        Result += "ACME Identifier: " + Ext.Value + "\r\n";
    if (Cert->ExtendedValidation)
        Result += "Extended Validation (EV) SSL Server Certificate\r\n";
    Result += "Key Info: " + Cert->KeyInfo + "\r\n";
    return Result;
}

TCertReadOpt GetCertReadOpt(bool Value)
{
    return Value ? croTry : croNo;
}

/* If more than one CA certificate with the same name hash value exists, the
   extension must be different (e.g. 9d66eef0.0, 9d66eef0.1 etc). */
String FindPemFileName(const String &FileName)
{
    const String FExt = ExtractFileExt(FileName);
    const String FFileName = FileName.SubString(1, FileName.Length() - FExt.Length());
    int I = StrToInt(FExt.SubString(2, MaxInt));
    String Result = FFileName + "." + IntToStr(I);
    while (FileExists(Result)) {
        I++;
        Result = FFileName + "." + IntToStr(I);
    }
    return Result;
}

#ifdef ICS_HAVE_MSCRYPT
// True if the directory holds no files (subdirectories are ignored).
bool IsDirEmpty(const String &Path)
{
    return System::Ioutils::TDirectory::GetFiles(DirWithDelimiter(Path)).Length == 0;
}

// Deletes every file in the directory, leaving subdirectories alone.
void EmptyDirectory(const String &Path)
{
    const TStringDynArray files = System::Ioutils::TDirectory::GetFiles(DirWithDelimiter(Path));
    for (int I = 0; I < files.Length; I++)
        DeleteFile(files[I]);
}

// Frees an OpenSSL BIO when the owning unique_ptr goes out of scope; PBIO is a void *.
struct TBioFree
{
    void operator()(PBIO Bio) const { BIO_free(Bio); }
};
using TBioPtr = std::unique_ptr<void, TBioFree>;
#endif

/* Resave a PEM or PFX file containing an encrypted private key
   -BEGIN ENCRYPTED PRIVATE KEY- with a new cipher, renaming the old file to
   .oldpem/.oldpfx. Specifically for files saved with old ciphers that OpenSSL 3.0
   does not support as standard; use with OpenSSL 1.1.1 before upgrading. */
bool ResavePrivateKey(const String &FName, const String &PW, TSslPrivKeyCipher KeyCipher, String &ErrMsg)
{
    ErrMsg = "";
    if (FName.IsEmpty() || !FileExists(FName)) {
        ErrMsg = "File name empty or file doesn't exist: " + FName;
        return false;
    }
    std::unique_ptr<TX509Base> Cert(new TX509Base(nullptr, nullptr));
    String NewFName;
    try {
        Cert->LoadFromFileEx(FName, croTry, croTry, PW, ErrMsg);
        if (!ErrMsg.IsEmpty())
            return false;
        if (!Cert->IsPKeyLoaded) {
            Cert->ClearAll();
            Cert->PrivateKeyLoadFromPemFile(FName, PW);
            if (!Cert->IsPKeyLoaded) {
                ErrMsg = "No private key found in file: " + FName;
                return false;
            }
        }
        NewFName = ChangeFileExt(FName, ".old" + ExtractFileExt(FName).SubString(2, 99));
        IcsRenameFile(FName, NewFName, true, true);

        if (!Cert->IsCertLoaded)   // private key only
            Cert->PrivateKeySaveToPemFile(FName, PW, KeyCipher);
        else
            Cert->SaveToFile(FName, true, true, Cert->IsInterLoaded, PW, KeyCipher);
        // Delphi clears NewFName before building this message, so it never shows the old file name.
        ErrMsg = "Resaved OK, old file now: " + NewFName + IcsCRLF + Cert->CertInfo();
        return true;
    }
    catch (Exception &E) {
        ErrMsg = Trim(E.Message);
    }
    // failure, try and restore the old file we renamed
    if (!NewFName.IsEmpty() && !FileExists(FName))
        IcsRenameFile(NewFName, FName, true, true);
    return false;
}

} // namespace

__fastcall TfrmPemTool1::TfrmPemTool1(TComponent *Owner)
    : TForm(Owner)
{
}

void __fastcall TfrmPemTool1::AppOnException(TObject *Sender, Exception *E)
{
    if (MessageDlg(E->ClassName() + ": " + E->Message + "\r\nExit PemTool now?",
                   mtError, TMsgDlgButtons() << mbYes << mbNo, 0) == mrYes)
        Application->Terminate();
}

void TfrmPemTool1::DispError(const String &Err)
{
    Status->Caption = StringReplace(Err, "\r\n", " ", TReplaceFlags() << rfReplaceAll);
    frmPemTool2->Memo1->Lines->Add(Status->Caption);
    System::Sysutils::Beep();
}

void __fastcall TfrmPemTool1::ToolsOKeyProgress(TObject *Sender)
{
    Application->ProcessMessages();
    if (FStartTickCount == 0)
        return;
    Status->Caption = "Generating prime numbers: " + IntToStr(IcsElapsedSecs64(FStartTickCount)) + " secs";
}

void TfrmPemTool1::AddLog(const String &S)
{
    if (FClosing)
        return;
    frmPemTool2->Memo1->Lines->Add(S);
}

// Appends raw text to the log window (Delphi: Memo1.Lines.Text := Memo1.Lines.Text + ...).
void TfrmPemTool1::AppendLogText(const String &S)
{
    frmPemTool2->Memo1->Lines->Text = frmPemTool2->Memo1->Lines->Text + S;
}

void __fastcall TfrmPemTool1::LogEvent(TObject *Sender, TLogOption LogOption, const String Msg)
{
    AddLog(Msg);
}

void TfrmPemTool1::ShowLogWindow()
{
    frmPemTool2->Show();
}

void __fastcall TfrmPemTool1::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    ReportMemoryLeaksOnShutdown = (DebugHook != 0);
    Application->OnException = AppOnException;
    FProgDir = ExtractFilePath(ParamStr(0));
    FIniFileName = GetIcsIniFileName();
    MsStoreType->ItemIndex = 0;
    // OpenSSL is loaded in WMAppStartup, not here, so errors are reported properly.
    OpenDlg->Filter = SslCertFileOpenExts;

    // replace the component items with the SSL literals
    CertSignHash->Items->Clear();
    for (int I = 0; I <= DigestListLitsLast; I++)
        CertSignHash->Items->Add(DigestListLits[I]);
    // Delphi selects item 0 of both lists (SHA1, and key type None, which cannot generate a key),
    // discarding the .dfm's SHA256 and RSA 2,048; this keeps the .dfm's choices.
    CertSignHash->ItemIndex = 2;   // DigestListLits[2] is SHA256
    KeyType->Items->Clear();
    for (int KT = PrivKeyNone; KT <= SslPrivKeyTypeLastCert; KT++)
        KeyType->Items->Add(SslPrivKeyTypeLits[KT]);
    KeyType->ItemIndex = PrivKeyRsa2048;
    KeyEncrypt->Items->Clear();
    for (int KC = PrivKeyEncNone; KC <= PrivKeyEncAES256; KC++)
        KeyEncrypt->Items->Add(SslPrivKeyCipherLits[KC]);
    KeyEncrypt->ItemIndex = 0;

#ifdef ICS_HAVE_MSCRYPT
    MsStoreType->Items->Clear();
    for (int CS = MsStoreMy; CS <= MsStoreSPC; CS++)
        MsStoreType->Items->Add(MsCertStoreTitles[CS]);
    MsStoreType->ItemIndex = 0;
    MsLocationType->Items->Clear();
    for (int CL = MsLocCurUser; CL <= MsLocUsers; CL++)
        MsLocationType->Items->Add(MsCertLocTitles[CL]);
    MsLocationType->ItemIndex = 0;
    MSKeyStoreType->Items->Clear();
    for (int KP = MsKeySoftware; KP <= MsKeyTPM; KP++)
        MSKeyStoreType->Items->Add(MsKeyProviderTitles[KP]);
    MSKeyStoreType->ItemIndex = 0;
#else
    BoxStoreTools->Visible = false;   // no Windows Certificate Store tools (see top of file)

    // The Import PEM destination (Label2, DestDirEdit) lives inside the hidden store box, which
    // leaves Delphi's {$ELSE} path with no way to set it; move both into the visible Misc box.
    // Positions are set after the resize because btnImportPemFile has no anchors, so the VCL
    // would otherwise re-centre it as the box grows.
    BoxMisc->SetBounds(BoxMisc->Left, BoxMisc->Top - 56, BoxMisc->Width, BoxMisc->Height + 56);
    Label2->Parent = BoxMisc;
    Label2->SetBounds(15, 18, Label2->Width, Label2->Height);
    DestDirEdit->Parent = BoxMisc;
    DestDirEdit->SetBounds(15, 36, BoxMisc->Width - 30, DestDirEdit->Height);
    btnImportPemFile->SetBounds(btnImportPemFile->Left, 70, btnImportPemFile->Width, btnImportPemFile->Height);
#endif
    DestDirEdit->Text = "";   // Delphi keeps the .dfm placeholder "DestDirEdit", a relative folder

    // TX509Base::SaveToP7BFile passes OpenSSL the address of its pointers, not the objects
    // (Source\OverbyteIcsSslBase.pas:3284-3312), and crashes; disabled until ICS is fixed.
    doSavePkcs7Cert->Enabled = false;

    if (IcsIsProgAdmin()) {
        LabelAdminRightsIns->Caption = "Local Machine store has administrator rights";
        LabelAdminRightsSrch->Caption = "Exporting private key has administrator rights";
        LabelAdminRightsRoot->Caption = "Local Machine store has admin rights";
    }
    else {
        LabelAdminRightsIns->Caption = "Local Machine store needs administrator rights";
        LabelAdminRightsSrch->Caption = "Exporting private key needs administrator rights";
        LabelAdminRightsRoot->Caption = "Local Machine store needs admin rights";
    }
}

// Bound in the .dfm. Frees the Windows store chain engine, which is only built with ICS_HAVE_MSCRYPT.
void __fastcall TfrmPemTool1::FormDestroy(TObject *Sender)
{
#ifdef ICS_HAVE_MSCRYPT
    FMsCertChainEngine.reset();
#endif
}

// Ini keys are "<control name>_Text", "_Checked", "_ItemIndex" or "_CommaText" in [Data],
// the same keys the Delphi demo writes.
void TfrmPemTool1::LoadSettings()
{
    std::unique_ptr<TIcsIniFile> IniFile(new TIcsIniFile(FIniFileName, true));
    Width = IniFile->ReadInteger(SectionMainWindow, KeyWidth, Width);
    Height = IniFile->ReadInteger(SectionMainWindow, KeyHeight, Height);
    Top = IniFile->ReadInteger(SectionMainWindow, KeyTop, (Screen->Height - Height) / 2);
    Left = IniFile->ReadInteger(SectionMainWindow, KeyLeft, (Screen->Width - Width) / 2);
    frmPemTool2->Width = IniFile->ReadInteger(SectionDisplayWindow, KeyWidth, frmPemTool2->Width);
    frmPemTool2->Height = IniFile->ReadInteger(SectionDisplayWindow, KeyHeight, frmPemTool2->Height);
    frmPemTool2->Top = IniFile->ReadInteger(SectionDisplayWindow, KeyTop, (Screen->Height - frmPemTool2->Height) / 2);
    frmPemTool2->Left = IniFile->ReadInteger(SectionDisplayWindow, KeyLeft, (Screen->Width - frmPemTool2->Width) / 2);
    FVerifyDir = IniFile->ReadString(SectionData, KeyVerifyDir, FProgDir);

    // An edit's default is its current .dfm text, except for these three.
    CurrentCertDirEdit->Text = GSSL_CERTS_DIR;
    QuickCAFile->Text = GSSL_INTER_FILE;
    QuickCertPath->Text = GSSL_CERTS_DIR;
    const std::vector<TEdit*> edits = {
        CertCommonName, CertCountry, CertDays, CertDescr, CertEMail, CertLocality,
        CertOrganization, CertOrganizationalUnit, CertPassword, CertState, CurrentCertDirEdit,
        DestDirEdit, LoadCertFile, LoadCertPW, LoadDirectory, LoadInterCerts, LoadPrivatetKey,
        LoadRequestFile, SaveCertDer, SaveCertPem, SaveCertPW, SaveDirectory, SavePkcs12File,
        SavePkcs7File, SavePrvFileFile, SavePubKeyFile, SaveReqCertFile, LoadCaBundleFile,
        MsInstallFile, IntersCAFile, TestHostOne, DownCertsPath, DownIntersPath, MsSearchCert,
        QuickCAFile, QuickCertPath, QuickPassword, CertStreet, CertTitle, CertInitals,
        CertGivenName, CertSurName, BundleHostName };
    for (TEdit *edit : edits)
        edit->Text = IniFile->ReadString(SectionData, edit->Name + "_Text", edit->Text);
    CAFilesDir->Text = IniFile->ReadString(SectionData, "CAFilesDir_Text", CAFilesDir->Text);

    // A missing check box key means unchecked, except CertPwPkcs12, which defaults to checked.
    const std::vector<TCheckBox*> checks = {
        CertAddComment, CertExtClient, CertExtCodeSign, CertExtEmail, CertExtServer,
        CertUsageCRLSign, CertUsageCertSign, CertUsageDataEn, CertUsageDigSign,
        CertUsageKeyAgree, CertUsageKeyEn, CertUsageNonRepud, CheckBoxComment,
        CheckBoxEmptyDestDir, CheckBoxOverwriteExisting, CheckBoxWarnDestNotEmpty,
        CheckBoxWriteToBundle, LoadCertInters, LoadCertPrivKey, NewCertCopyExt, SaveAutoReplace,
        SaveInterCerts, SavePrivateKey, MSBriefList, CertPwPemKeys, CertPwPkcs12, CertCheckOCSP,
        CertsRootCA };
    for (TCheckBox *check : checks) {
        const String def = (check == CertPwPkcs12) ? "True" : "False";
        check->Checked = (IniFile->ReadString(SectionData, check->Name + "_Checked", def) == "True");
    }

    const std::vector<TRadioGroup*> radios = { CertSignHash, KeyEncrypt, KeyType, MsStoreType, MsLocationType };
    for (TRadioGroup *radio : radios)
        radio->ItemIndex = IniFile->ReadInteger(SectionData, radio->Name + "_ItemIndex", radio->ItemIndex);

    CertAltDomains->Lines->CommaText = IniFile->ReadString(SectionData, "CertAltDomains_CommaText", "");
    CertAltIPs->Lines->CommaText = IniFile->ReadString(SectionData, "CertAltIPs_CommaText", "");
    TestHostList->Lines->CommaText = IniFile->ReadString(SectionData, "TestHostList_Lines", TestHostList->Lines->CommaText);
}

void TfrmPemTool1::SaveSettings()
{
    std::unique_ptr<TIcsIniFile> IniFile(new TIcsIniFile(FIniFileName, true));
    IniFile->WriteInteger(SectionMainWindow, KeyTop, Top);
    IniFile->WriteInteger(SectionMainWindow, KeyLeft, Left);
    IniFile->WriteInteger(SectionMainWindow, KeyWidth, Width);
    IniFile->WriteInteger(SectionMainWindow, KeyHeight, Height);
    // Delphi also sets the main form's Width from the saved log window width here, after saving it.
    IniFile->WriteInteger(SectionDisplayWindow, KeyTop, frmPemTool2->Top);
    IniFile->WriteInteger(SectionDisplayWindow, KeyLeft, frmPemTool2->Left);
    IniFile->WriteInteger(SectionDisplayWindow, KeyWidth, frmPemTool2->Width);
    IniFile->WriteInteger(SectionDisplayWindow, KeyHeight, frmPemTool2->Height);
    IniFile->WriteString(SectionData, KeyVerifyDir, FVerifyDir);

    const std::vector<TEdit*> edits = {
        CertCommonName, CertCountry, CertDays, CertDescr, CertEMail, CertLocality,
        CertOrganization, CertOrganizationalUnit, CertPassword, CertState, CurrentCertDirEdit,
        DestDirEdit, LoadCertFile, LoadCertPW, LoadDirectory, LoadInterCerts, LoadPrivatetKey,
        LoadRequestFile, SaveCertDer, SaveCertPem, SaveCertPW, SaveDirectory, SavePkcs12File,
        SavePkcs7File, SavePrvFileFile, SavePubKeyFile, SaveReqCertFile, LoadCaBundleFile,
        MsInstallFile, IntersCAFile, TestHostOne, DownCertsPath, DownIntersPath, MsSearchCert,
        QuickCAFile, QuickCertPath, QuickPassword, CertStreet, CertTitle, CertInitals,
        CertGivenName, CertSurName, BundleHostName };
    for (TEdit *edit : edits)
        IniFile->WriteString(SectionData, edit->Name + "_Text", edit->Text);
    IniFile->WriteString(SectionData, "CAFilesDir_Text", CAFilesDir->Text);

    const std::vector<TCheckBox*> checks = {
        CertAddComment, CertExtClient, CertExtCodeSign, CertExtEmail, CertExtServer,
        CertUsageCRLSign, CertUsageCertSign, CertUsageDataEn, CertUsageDigSign,
        CertUsageKeyAgree, CertUsageKeyEn, CertUsageNonRepud, CheckBoxComment,
        CheckBoxEmptyDestDir, CheckBoxOverwriteExisting, CheckBoxWarnDestNotEmpty,
        CheckBoxWriteToBundle, LoadCertInters, LoadCertPrivKey, NewCertCopyExt, SaveAutoReplace,
        SaveInterCerts, SavePrivateKey, MSBriefList, CertPwPemKeys, CertPwPkcs12, CertCheckOCSP,
        CertsRootCA };
    for (TCheckBox *check : checks)
        IniFile->WriteString(SectionData, check->Name + "_Checked", check->Checked ? "True" : "False");

    const std::vector<TRadioGroup*> radios = { CertSignHash, KeyEncrypt, KeyType, MsStoreType, MsLocationType };
    for (TRadioGroup *radio : radios)
        IniFile->WriteInteger(SectionData, radio->Name + "_ItemIndex", radio->ItemIndex);

    IniFile->WriteString(SectionData, "CertAltDomains_CommaText", CertAltDomains->Lines->CommaText);
    IniFile->WriteString(SectionData, "CertAltIPs_CommaText", CertAltIPs->Lines->CommaText);
    IniFile->WriteString(SectionData, "TestHostList_Lines", TestHostList->Lines->CommaText);
    IniFile->UpdateFile();
}

void __fastcall TfrmPemTool1::FormShow(TObject *Sender)
{
    if (!FInitialized) {
        FInitialized = true;
        LoadSettings();
        PostMessage(Handle, WM_APPSTARTUP, 0, 0);
    }
    if (CertSignHash->ItemIndex < 0)
        CertSignHash->ItemIndex = 0;
    if (KeyType->ItemIndex < 0)
        KeyType->ItemIndex = 0;
    if (KeyEncrypt->ItemIndex < 0)
        KeyEncrypt->ItemIndex = 0;
    if (MsStoreType->ItemIndex < 0)
        MsStoreType->ItemIndex = 0;
    if (MsLocationType->ItemIndex < 0)
        MsLocationType->ItemIndex = 0;
    if (MSKeyStoreType->ItemIndex < 0)
        MSKeyStoreType->ItemIndex = 0;
}

void __fastcall TfrmPemTool1::WMAppStartup(TMessage &Msg)
{
    ShowLogWindow();
    // Delphi leaves every tab usable after OpenSSL fails, so later clicks use the components
    // that are never created below. Here everything except File > Exit is disabled instead.
    // The lambda captures `this` to reach the form's controls; it runs only on the two failure paths.
    auto disableAfterSslFailure = [this]() {
        PageControl1->Enabled = false;
        MMExtras->Enabled = false;
        MMResaveKey->Enabled = false;
    };
    try {
        IcsLoadSsl();                       // also done by Initialise, but reports errors as an exception
        IcsSslRootCAStore->Initialise();    // loads OpenSSL and the internal root bundle if not loaded
    }
    catch (Exception &E) {
        AddLog("Fatal Error, Failed to Load OpenSSL: " + E.Message);
        disableAfterSslFailure();
        return;
    }
    if (!GSSL_LOAD_ERRS.IsEmpty()) {
        AddLog("Fatal Error, Failed to Load OpenSSL: " + GSSL_LOAD_ERRS);
        disableAfterSslFailure();
        return;
    }

    AddLog(String(IcsVerLitRelDate) + ", SSL/TLS " + IcsReportOpenSSLVer(true) + IcsCRLF +
           "Compiler: " + IcsBuiltWithEx());
    AddLog("OpenSSL version: " + OpenSslFullVer() + " " + OpenSslBuiltOn() + " " + OpenSslPlatForm());
    FSslCertTools.reset(new TSslCertTools(nullptr));
    FSslCertTools->OnKeyProgress = ToolsOKeyProgress;
    FSslCAX509.reset(new TX509Base(nullptr, nullptr));
    PageControl1->ActivePageIndex = 0;
    LvCerts->Perform(CM_RECREATEWND, static_cast<WPARAM>(0), static_cast<LPARAM>(0));  // fix column buttons not displayed
    FOcspHttp.reset(new TOcspHttp(nullptr));
    FOcspHttp->OnOcspProg = LogEvent;
    FOcspHttp->OcspHttpProxy = "";
    FInterCAList.reset(new TX509List(nullptr, true));
    FInterCAList->X509Class = __classid(TX509Base);
    FDownCertsList.reset(new TX509List(nullptr, true));
    FDownCertsList->X509Class = __classid(TX509Base);
    FDownIntersList.reset(new TX509List(nullptr, true));
    FDownIntersList->X509Class = __classid(TX509Base);
    FillListView();
}

void __fastcall TfrmPemTool1::FormClose(TObject *Sender, TCloseAction &Action)
{
    FClosing = true;   // stop logging now
    FOcspHttp.reset();
    SaveSettings();
#ifdef ICS_HAVE_MSCRYPT
    FMsX509List.reset();
#endif
    FSslCertTools.reset();
    FInterCAList.reset();
    FDownCertsList.reset();
    FDownIntersList.reset();
}

String TfrmPemTool1::ListOcspStatus(TX509Base *Cert)
{
    if (!Cert)
        return "No certificate loaded";
    if (Cert->UrlOcsp.IsEmpty()) {
        const String Result = "OCSP not available";
        AddLog(Result);
        return Result;
    }
    AddLog("Checking OCSP status for: " + Cert->SubjectCName);
    std::unique_ptr<TX509List> Inters(new TX509List(nullptr, true));
    Inters->X509Class = __classid(TX509Base);

    // need the intermediate bundle to build OCSP requests with the issuing certificate
    if (FInterCAList->Count == 0)
        LoadInterCA();
    if (IcsSslRootCAStore->Count == 0)
        LoadRootCA();

    // look for the issuer in the intermediate or root bundle
    if (Cert->BasicConstraints.Pos("CA=TRUE") > 0) {
        const int I = IcsSslRootCAStore->IndexOfSubj(Cert->IssuerOneLine);
        if (I >= 0)
            Inters->Add(IcsSslRootCAStore->Items[I]->X509);
    }
    else if (!Cert->SelfSigned) {
        const int I = FInterCAList->IndexOfSubj(Cert->IssuerOneLine);
        if (I >= 0)
            Inters->Add(FInterCAList->Items[I]->X509);
    }

    // try and download the issuer
    if (Inters->Count == 0 && !Cert->UrlIssuer.IsEmpty()) {
        FOcspHttp->OcspInters = Inters.get();
        FOcspHttp->DebugLevel = DebugConn;
        if (FOcspHttp->IssuerHttpRequest(Cert->UrlIssuer, 10))   // waits for the response
            AddLog("Downloaded Issuer certificate OK: " + FOcspHttp->OcspInters->Items[0]->SubjectOneLine);
    }
    if (Inters->Count == 0) {
        AddLog("Could not find Issuer certificate: " + Cert->IssuerOneLine);
        return String();
    }

    // finally get the OCSP status, which TOcspHttp logs through OnOcspProg
    FOcspHttp->ClearOcsp();
    FOcspHttp->DebugLevel = DebugSsl;
    FOcspHttp->OcspCert = Cert;
    FOcspHttp->OcspInters = Inters.get();
    FOcspHttp->CheckOcspRevoked(IcsSslRootCAStore->X509Store, 10);   // waits for the response
    return String();
}

void TfrmPemTool1::ShowOneCert(const String &FileName)
{
    if (FileName.IsEmpty() || !FileExists(FileName))
        throw Exception("FileName empty or file doesn't exist");
    ShowLogWindow();
    std::unique_ptr<TX509Base> Cert(new TX509Base(nullptr, nullptr));
    try {
        String Errs;
        Cert->LoadFromFileEx(FileName, croTry, croTry, CertPassword->Text, Errs);
        if (!Errs.IsEmpty())
            AddLog(Errs);
        if (!Cert->IsCertLoaded)
            AddLog("No certificate found in file " + FileName);
        else {
            AddLog("Certificate file " + FileName);
            AddLog(ListCertDetail(Cert.get()) + "\r\n");
            if (CertCheckOCSP->Checked)
                AddLog(ListOcspStatus(Cert.get()));
            AppendLogText("Raw Cert\r\n" + Cert->GetRawText() + "\r\n");
            if (Cert->IsPKeyLoaded) {
                const String match = Cert->CheckCertAndPKey()
                    ? "!! Private key available for certificate public key: "
                    : "!! Private key does not match certificate public key: ";
                AppendLogText(match + Cert->KeyInfo + "\r\n" + Cert->GetPKeyRawText(true) + "\r\n");
            }
        }
        if (Cert->IsInterLoaded)
            AddLog("!! Intermediate certificates: " + Cert->ListInters() + "\r\n");
    }
    catch (Exception &E) {
        AddLog(E.Message);
    }
}

void TfrmPemTool1::ShowBundleCerts(const String &FileName)
{
    if (FileName.IsEmpty() || !FileExists(FileName))
        throw Exception("FileName empty or file doesn't exist");
    ShowLogWindow();
    std::unique_ptr<TX509List> Certs(new TX509List(nullptr, true));
    Certs->X509Class = __classid(TX509Base);
    String Errs;
    const int Total = Certs->LoadAllFromFileEx(FileName, Errs);
    if (!Errs.IsEmpty())
        AddLog("Errors reading bundle file: " + Errs);
    if (Total <= 0) {
        AddLog("No PEM certificates found in file");
        return;
    }
    AddLog("Certificate file " + FileName);
    AddLog("Number of PEM certificates found in file: " + IntToStr(Total));
    for (int I = 1; I <= Total; I++) {
        AddLog("\r\nCertificate #" + IntToStr(I) + "\r\n" + ListCertDetail(Certs->Items[I - 1]));
        AppendLogText("Raw #" + IntToStr(I) + "\r\n" + Certs->Items[I - 1]->GetRawText());
    }
}

// Runs the open dialog for a certificate file and returns the file chosen, or an empty string.
String TfrmPemTool1::PickCertFile(const String &Filter)
{
    if (OpenDlg->InitialDir.IsEmpty())
        OpenDlg->InitialDir = DestDirEdit->Text;
    OpenDlg->Filter = Filter;
    String FileName;
    if (OpenDlg->Execute())
        FileName = OpenDlg->FileName;
    if (FileName.IsEmpty() || !FileExists(FileName))
        return String();
    OpenDlg->InitialDir = ExtractFileDir(FileName);
    return FileName;
}

void __fastcall TfrmPemTool1::btnShowBundleFileClick(TObject *Sender)
{
    const String FileName = PickCertFile("PEM Certs *.pem|*.pem|All Files *.*|*.*");
    if (!FileName.IsEmpty())
        ShowBundleCerts(FileName);
}

void __fastcall TfrmPemTool1::btnShowOneFileClick(TObject *Sender)
{
    const String FileName = PickCertFile(SslCertFileOpenExts);
    if (!FileName.IsEmpty())
        ShowOneCert(FileName);
}

void TfrmPemTool1::AddListView(TX509Base *X, const String &FileName)
{
    TListItem *ListItem = LvCerts->Items->Add();
    ListItem->Caption = X->SubjectCName;
    if (ListItem->Caption.IsEmpty())
        ListItem->Caption = X->SubjectOName;
    String S = X->SubjectOUName;
    if (S.IsEmpty())
        S = X->SubjectOName;
    ListItem->SubItems->Add(S);
    S = X->IssuerCName;
    if (S.IsEmpty())
        S = X->IssuerOUName;
    if (S.IsEmpty())
        S = X->IssuerOName;
    ListItem->SubItems->Add(S);
    if (static_cast<double>(X->ValidNotAfter) > 10)   // a bundle might not have an expiry
        ListItem->SubItems->Add(DateToStr(X->ValidNotAfter));
    else
        ListItem->SubItems->Add("");
    ListItem->SubItems->Add(ExtractFileName(FileName));
}

void TfrmPemTool1::FillListView()
{
    std::unique_ptr<TX509Base> X(new TX509Base(nullptr, nullptr));
    TItemsUpdate update(LvCerts->Items);
    LvCerts->Items->Clear();
    FCurrentCertDir = Trim(CurrentCertDirEdit->Text);
    if (!DirectoryExists(FCurrentCertDir))
        return;
    // Delphi uses FindFirst/FindNext and skips names containing '?', which only occur on non-Unicode compilers.
    const TStringDynArray files = System::Ioutils::TDirectory::GetFiles(DirWithDelimiter(FCurrentCertDir));
    for (int I = 0; I < files.Length; I++) {
        try {
            String Errs;
            X->LoadFromFileEx(files[I], croNo, croNo, CertPassword->Text, Errs);
            if (Errs.IsEmpty())   // ignore files that are not really certificates
                AddListView(X.get(), files[I]);
        }
        catch (Exception &) {
        }
    }
}

// Returns the selected list item and its full file name, or nullptr after telling the user.
TListItem *TfrmPemTool1::SelectedCert(String &FileName)
{
    TListItem *ListItem = LvCerts->Selected;
    if (!ListItem) {
        MessageDlg("No item selected", mtError, TMsgDlgButtons() << mbOK, 0);
        return nullptr;
    }
    FileName = ListItem->SubItems->Strings[3];
    if (!FileName.IsEmpty())
        FileName = DirWithDelimiter(FCurrentCertDir) + FileName;
    return ListItem;
}

void __fastcall TfrmPemTool1::LvCertsDblClick(TObject *Sender)
{
    String FileName;
    if (SelectedCert(FileName) && FileExists(FileName))
        ShowOneCert(FileName);
}

void __fastcall TfrmPemTool1::LvCertsColumnClick(TObject *Sender, TListColumn *Column)
{
    Screen->Cursor = crHourGlass;
    try {
        TItemsUpdate update(LvCerts->Items);
        if (FColumnToSort == Column->Index)
            FLVSortFlag = !FLVSortFlag;
        for (int I = 0; I < LvCerts->Columns->Count; I++) {
            if (I != Column->Index)
                LvCerts->Columns->Items[I]->ImageIndex = -1;
            else
                LvCerts->Columns->Items[I]->ImageIndex = FLVSortFlag ? 1 : 2;
        }
        FColumnToSort = Column->Index;
        LvCerts->AlphaSort();
        if (LvCerts->Selected)
            LvCerts->Selected->MakeVisible(true);
    }
    __finally {
        Screen->Cursor = crDefault;
    }
}

void __fastcall TfrmPemTool1::LvCertsCompare(TObject *Sender, TListItem *Item1, TListItem *Item2, int Data, int &Compare)
{
    // a descending sort is an ascending sort with the items swapped
    TListItem *A = FLVSortFlag ? Item2 : Item1;
    TListItem *B = FLVSortFlag ? Item1 : Item2;
    if (FColumnToSort == 0) {
        Compare = CompareText(A->Caption, B->Caption);
        return;
    }
    const int Idx = FColumnToSort - 1;
    // Delphi compares dates for SubItems[1], the Issuer, so StrToDate raises; the dates are SubItems[2].
    if (Idx == 2) {
        TDateTime DateA = 0, DateB = 0;   // a bundle with no expiry has an empty date
        TryStrToDate(A->SubItems->Strings[Idx], DateA);
        TryStrToDate(B->SubItems->Strings[Idx], DateB);
        Compare = (DateA > DateB) ? 1 : (DateA < DateB) ? -1 : 0;
        return;
    }
    Compare = CompareText(A->SubItems->Strings[Idx], B->SubItems->Strings[Idx]);
}

// Displays the sort image on the right of the column caption (comctl32.dll 4.70+).
void __fastcall TfrmPemTool1::LvCertsCustomDraw(TCustomListView *Sender, const TRect &ARect, bool &DefaultDraw)
{
    for (int I = 0; I < LvCerts->Columns->Count; I++) {
        TListColumn *Col = LvCerts->Columns->Items[I];
        if (Col->ImageIndex == -1)
            continue;
        // Delphi first calls ListView_GetColumn on the header handle with a zero mask, which reads nothing.
        LVCOLUMN LvColumn = {};
        LvColumn.iImage = Col->ImageIndex;
        LvColumn.mask = LVCF_IMAGE | LVCF_FMT;
        LvColumn.fmt = LVCFMT_IMAGE | LVCFMT_BITMAP_ON_RIGHT;
        switch (Col->Alignment) {
            case taLeftJustify:  LvColumn.fmt |= LVCFMT_LEFT;   break;
            case taCenter:       LvColumn.fmt |= LVCFMT_CENTER; break;
            case taRightJustify: LvColumn.fmt |= LVCFMT_RIGHT;  break;
        }
        ListView_SetColumn(LvCerts->Handle, I, &LvColumn);
    }
}

String TfrmPemTool1::BuildLoadName(const String &FName)
{
    if (FName.IsEmpty()) {
        DispError("Must specify load file name");
        return String();
    }
    if (LoadDirectory->Text.IsEmpty()) {
        DispError("Must specify load directory");
        return String();
    }
    const String Result = DirWithDelimiter(LoadDirectory->Text) + FName;
    if (FileExists(Result))
        return Result;
    DispError("Load file not found - " + Result);
    return String();
}

String TfrmPemTool1::BuildSaveName(const String &FName)
{
    if (FName.IsEmpty()) {
        DispError("Must specify save file name");
        return String();
    }
    if (SaveDirectory->Text.IsEmpty()) {
        DispError("Must specify save directory");
        return String();
    }
    if (!ForceDirectories(SaveDirectory->Text)) {
        DispError("Failed to create save directory - " + SaveDirectory->Text);
        return String();
    }
    const String Result = DirWithDelimiter(SaveDirectory->Text) + FName;
    if (!FileExists(Result))
        return Result;
    if (SaveAutoReplace->Checked) {
        if (DeleteFile(Result))
            return Result;
        DispError("Failed to delete old save file - " + Result);
    }
    else
        DispError("File already exists and replace not specified - " + Result);
    return String();
}

// The cipher for a password protected private key: the selected one, or AES256 if None is selected.
TSslPrivKeyCipher TfrmPemTool1::SelectedKeyCipher()
{
    const TSslPrivKeyCipher Cipher = static_cast<TSslPrivKeyCipher>(KeyEncrypt->ItemIndex);
    return (Cipher == PrivKeyEncNone) ? PrivKeyEncAES256 : Cipher;
}

// Removes blank lines from the alternate domains and makes sure the common name is one of them.
void TfrmPemTool1::CleanAltDomains(const String &CName)
{
    TStrings *Lines = CertAltDomains->Lines;
    for (int I = Lines->Count - 1; I >= 0; I--) {
        Lines->Strings[I] = Trim(Lines->Strings[I]);
        if (Lines->Strings[I].IsEmpty())
            Lines->Delete(I);
    }
    if (Lines->IndexOf(CName) < 0)
        Lines->Add(CName);
}

void TfrmPemTool1::SetCertProps()
{
    CertCommonName->Text = Trim(CertCommonName->Text);
    TSslCertTools *T = FSslCertTools.get();
    T->Country = CertCountry->Text;
    T->State = CertState->Text;
    T->Locality = CertLocality->Text;
    T->Organization = CertOrganization->Text;
    T->OrgUnit = CertOrganizationalUnit->Text;
    T->Descr = CertDescr->Text;
    T->Email = CertEMail->Text;
    T->CommonName = CertCommonName->Text;
    T->Street = CertStreet->Text;
    T->NameTitle = CertTitle->Text;
    T->NameInitials = CertInitals->Text;
    T->GivenName = CertGivenName->Text;
    T->SurName = CertSurName->Text;

    CleanAltDomains(CertCommonName->Text);   // the alt domains must contain the common name
    T->AltDNSList->Assign(CertAltDomains->Lines);
    T->AltIpList->Assign(CertAltIPs->Lines);
    // optional, not set by the demo: AltEmailList, AltIssuer, CRLDistPoint, AuthInfoAcc
    T->BasicIsCA = CertIsCA->Checked || CertsRootCA->Checked;
    T->BasicPathLen = CertsRootCA->Checked ? -1 : 0;   // -1 means pathlen is ignored
    T->KeyCertSign = CertUsageCertSign->Checked;
    T->KeyCRLSign = CertUsageCRLSign->Checked;
    T->KeyDigiSign = CertUsageDigSign->Checked;
    T->KeyDataEnc = CertUsageDataEn->Checked;
    T->KeyKeyEnc = CertUsageKeyEn->Checked;
    T->KeyKeyAgree = CertUsageKeyAgree->Checked;
    T->KeyNonRepud = CertUsageNonRepud->Checked;
    T->KeyExtClient = CertExtClient->Checked;
    T->KeyExtServer = CertExtServer->Checked;
    T->KeyExtEmail = CertExtEmail->Checked;
    T->KeyExtCode = CertExtCodeSign->Checked;
    T->ExpireDays = Overbyteicsutils::atoi(CertDays->Text);   // ICS atoi(String), not the CRT one
    T->AddComments = CertAddComment->Checked;
    T->CertDigest = DigestDispList[CertSignHash->ItemIndex];
    T->SerialNum = 0;   // forces a random serial number
}

void TfrmPemTool1::SelectLoadFile(TEdit *FileEdit)
{
    OpenDlg->InitialDir = LoadDirectory->Text;
    // Delphi raises a range check error here while Load Directory is still empty; this opens the dialog instead.
    OpenDlg->FileName = LoadDirectory->Text.IsEmpty() ? FileEdit->Text
                                                      : DirWithDelimiter(LoadDirectory->Text) + FileEdit->Text;
    if (OpenDlg->Execute()) {
        FileEdit->Text = ExtractFileName(OpenDlg->FileName);
        LoadDirectory->Text = ExtractFilePath(OpenDlg->FileName);
    }
}

bool TfrmPemTool1::SelectDir(TEdit *DirEdit)
{
    OpenDirDiag->InitialDir = DirEdit->Text;
    if (!OpenDirDiag->Execute())
        return false;
    DirEdit->Text = ExtractFilePath(OpenDirDiag->FileName);
    return true;
}

void __fastcall TfrmPemTool1::SelCAFileClick(TObject *Sender)
{
    SelectLoadFile(LoadCaBundleFile);
}

void __fastcall TfrmPemTool1::SelCertFileClick(TObject *Sender)
{
    SelectLoadFile(LoadCertFile);
}

void __fastcall TfrmPemTool1::SelPrvKeyFileClick(TObject *Sender)
{
    SelectLoadFile(LoadPrivatetKey);
}

void __fastcall TfrmPemTool1::SelReqFileClick(TObject *Sender)
{
    SelectLoadFile(LoadRequestFile);
}

void __fastcall TfrmPemTool1::SelIntersFileClick(TObject *Sender)
{
    SelectLoadFile(LoadInterCerts);
}

void __fastcall TfrmPemTool1::SelCurrDirClick(TObject *Sender)
{
    SelectDir(CurrentCertDirEdit);
    FillListView();
}

void __fastcall TfrmPemTool1::SelLoadDirClick(TObject *Sender)
{
    SelectDir(LoadDirectory);
}

void __fastcall TfrmPemTool1::SelSaveDirClick(TObject *Sender)
{
    SelectDir(SaveDirectory);
}

void __fastcall TfrmPemTool1::SelImpDirClick(TObject *Sender)
{
    SelectDir(DestDirEdit);
    DestDirEditChange(this);
}

void __fastcall TfrmPemTool1::SelIntersCAClick(TObject *Sender)
{
    OpenDlg->InitialDir = LoadDirectory->Text;
    OpenDlg->FileName = IntersCAFile->Text;
    if (OpenDlg->FileName.IsEmpty())
        return;
    if (OpenDlg->Execute())
        IntersCAFile->Text = OpenDlg->FileName;
}

void __fastcall TfrmPemTool1::CAFilesDirClick(TObject *Sender)
{
    OpenDlg->InitialDir = LoadDirectory->Text;
    OpenDlg->FileName = CAFilesDir->Text;
    if (OpenDlg->Execute())
        CAFilesDir->Text = OpenDlg->FileName;
}

void __fastcall TfrmPemTool1::CurrentCertDirEditChange(TObject *Sender)
{
    FCurrentCertDir = Trim(CurrentCertDirEdit->Text);
}

// The next three are bound in the .dfm; the Delphi handlers are empty too.
void __fastcall TfrmPemTool1::PageControl1Change(TObject *Sender)
{
}

void __fastcall TfrmPemTool1::DestDirEditChange(TObject *Sender)
{
}

void __fastcall TfrmPemTool1::IntersCAFileChange(TObject *Sender)
{
}

void __fastcall TfrmPemTool1::doClearCertsClick(TObject *Sender)
{
    FSslCertTools->ClearAll();
    FSslCAX509->ClearAll();
    DispCert();
    DispPKey();
    DispReq();
    DispInter();
    DispCACert();
    KeyPairLines->Lines->Clear();
    CertLinesNew->Lines->Clear();
    CertAltDomains->Lines->Clear();
    CertAltIPs->Lines->Clear();
    CertCommonName->Text = "";
    frmPemTool2->Memo1->Lines->Clear();
}

void __fastcall TfrmPemTool1::doCreateBundleClick(TObject *Sender)
{
    doClearCertsClick(this);
    const String certfname = BuildLoadName(LoadCertFile->Text);
    if (certfname.IsEmpty())
        return;
    const String pkeyfname = BuildLoadName(LoadPrivatetKey->Text);
    if (pkeyfname.IsEmpty())
        return;
    const String interfname = BuildLoadName(LoadInterCerts->Text);   // optional
    const String savefname = BuildSaveName(SaveCertPem->Text);
    if (savefname.IsEmpty())
        return;
    const TSslPrivKeyCipher PrivKeyType = CertPwPemKeys->Checked ? SelectedKeyCipher() : PrivKeyEncNone;
    try {
        FSslCertTools->CreateCertBundle(certfname, pkeyfname, interfname, LoadCertPW->Text,
                                        savefname, SaveCertPW->Text, PrivKeyType);
        DispError("Saved certificate bundle OK  with " + String(SslPrivKeyCipherLits[PrivKeyType]) +
                  " key cipher: " + savefname);
        DispCert();
        DispPKey();
        DispInter();
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void TfrmPemTool1::LoadRootCA()
{
    bool Internal = false;
    String Errs;
    if (CAFilesDir->Text.IsEmpty() || CAFilesDir->Text == "Internal") {
        if (!IcsSslRootCAStore->InitFlag)
            IcsSslRootCAStore->Initialise();
        Internal = true;
        IcsIpClient->LogSslRootFile = "";
    }
    else {
        if (!FileExists(CAFilesDir->Text)) {
            DispError("CA root bundle file not found: " + CAFilesDir->Text);
            return;
        }
        IcsSslRootCAStore->Clear();
        IcsSslRootCAStore->LoadAllFromFileEx(CAFilesDir->Text, Errs);
        IcsIpClient->LogSslRootFile = CAFilesDir->Text;
    }
    if (!Errs.IsEmpty())
        DispError(Errs);
    if (IcsSslRootCAStore->Count > 0) {
        if (Internal)
            DispError("Loaded " + IntToStr(IcsSslRootCAStore->Count) +
                      " CA root certificates from internal bundle: " + IcsSslRootCAStore->CAStoreSource);
        else
            DispError("Loaded " + IntToStr(IcsSslRootCAStore->Count) +
                      " CA root certificates from: " + CAFilesDir->Text);
        if (!IcsSslRootCAStore->SetX509Store())
            DispError("Failed to Build X509 Store");
    }
    else
        DispError("No CA root bundle certificates found in: " + CAFilesDir->Text);
}

void TfrmPemTool1::LoadInterCA()
{
    FInterCAList->Clear();
    if (!FileExists(IntersCAFile->Text)) {
        AddLog("Intermediate CA bundle file not found: " + IntersCAFile->Text);
        return;
    }
    String Errs;
    FInterCAList->LoadAllFromFileEx(IntersCAFile->Text, Errs);
    if (!Errs.IsEmpty())
        DispError(Errs);
    if (FInterCAList->Count == 0)
        AddLog("Failed to Load Intermediate CA bundle file: " + IntersCAFile->Text);
}

void __fastcall TfrmPemTool1::doCheckBundleSelfClick(TObject *Sender)
{
    if (!FSslCertTools->IsCertLoaded) {
        DispError("Must load or create a certificate first");
        return;
    }
    if (!FSslCertTools->IsPKeyLoaded) {
        DispError("Must load or create a private key first");
        return;
    }
    if (!FSslCertTools->CheckCertAndPKey()) {
        DispError("Mismatch certificate and private key");
        return;
    }

    AddLog("Validating Server Certificate Bundle");
    AddLog("CheckHost Result=" + FSslCertTools->CheckHost(BundleHostName->Text, 0));
    AddLog("CheckIP Result=" + IcsGetTrueFalse(FSslCertTools->CheckIPaddr(BundleHostName->Text, 0)));

    if (IcsSslRootCAStore->Count == 0)
        LoadRootCA();
    String CertStr, ErrStr;
    const TChainResult ValRes = FSslCertTools->ValidateCertChain(BundleHostName->Text, IcsSslRootCAStore,
                                                                 CertStr, ErrStr, 7);
    if (ValRes == chainOK)
        ErrStr = "Chain Validated OK";
    else if (ValRes == chainWarn)
        ErrStr = "Chain Warning - " + ErrStr;
    else
        ErrStr = "Chain Failed - " + ErrStr;
    DispError(ErrStr);
    AddLog(CertStr + IcsCRLF);
}

void __fastcall TfrmPemTool1::doListRootsClick(TObject *Sender)
{
    ShowLogWindow();
    LoadRootCA();
    AddLog("Number of root certificates found: " + IntToStr(IcsSslRootCAStore->Count));
    if (IcsSslRootCAStore->Count > 0)
        AddLog(IcsSslRootCAStore->ListCerts());
}

void __fastcall TfrmPemTool1::doListIntersClick(TObject *Sender)
{
    ShowLogWindow();
    LoadInterCA();
    AddLog("Intermediate CA bundle certificate file " + IntersCAFile->Text);
    AddLog("Number of inter certificates found: " + IntToStr(FInterCAList->Count));
    if (FInterCAList->Count > 0)
        AddLog(FInterCAList->ListCerts());
}

// Checks the bundle with the Windows certificate chain engine; does nothing without ICS_HAVE_MSCRYPT.
void __fastcall TfrmPemTool1::doCheckBundleWinClick(TObject *Sender)
{
#ifdef ICS_HAVE_MSCRYPT
    if (!FSslCertTools->IsCertLoaded) {
        DispError("Must load or create a certificate first");
        return;
    }
    if (SaveInterCerts->Checked && !FSslCertTools->IsInterLoaded) {
        DispError("Must load intermediate certificates first");
        return;
    }
    if (FSslCertTools->IsPKeyLoaded && !FSslCertTools->CheckCertAndPKey()) {
        DispError("Mismatch certificate and private key");
        return;
    }

    std::unique_ptr<TX509List> CertChain(new TX509List(nullptr, true));
    FSslCertTools->GetIntersList(CertChain.get());
    if (!FMsCertChainEngine)
        FMsCertChainEngine.reset(new TMsCertChainEngine());
    FMsCertChainEngine->VerifyOptions = TMsVerifyOptions();   // no revocation checks, they are very slow
    System::LongWord ChainVerifyResult = 0;
    FMsCertChainEngine->VerifyCert(FSslCertTools.get(), CertChain.get(), ChainVerifyResult, true);
    DispError("Bundle check result - " + MsChainVerifyErrorToStr(ChainVerifyResult));
#endif
}

void __fastcall TfrmPemTool1::doCreateCACertClick(TObject *Sender)
{
    SetCertProps();
    TButtonBusy busy(doCreateCACert);
    try {
        if (!FSslCAX509->IsCertLoaded) {
            DispError("Must specify CA ertificate first");
            return;
        }
        FSslCertTools->X509CA = FSslCAX509->X509;
        FSslCertTools->PrivKeyCA = FSslCAX509->PrivateKey;
        DispCACert();
        FSslCertTools->DoSignCertReq(NewCertCopyExt->Checked);
        DispError("Created certificate from request signed by CA certificate OK");
        DispCert();
        CertLinesNew->Lines->Text = FSslCertTools->SaveCertToText(true);
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doCreateReqCertClick(TObject *Sender)
{
    SetCertProps();
    TButtonBusy busy(doCreateReqCert);
    try {
        FSslCertTools->X509Req = nullptr;
        DispCert();
        CertLinesNew->Lines->Clear();
        FSslCertTools->DoCertReqOld();
        DispError("Created certificate request from existing certificate OK");
        DispReq();
        CertLinesNew->Lines->Text = FSslCertTools->SaveReqToText(true);
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doCreateReqPropsClick(TObject *Sender)
{
    SetCertProps();
    TButtonBusy busy(doCreateReqProps);
    try {
        FSslCertTools->X509Req = nullptr;
        DispCert();
        CertLinesNew->Lines->Clear();
        FSslCertTools->DoCertReqProps();
        DispError("Created certificate request from properties OK");
        DispReq();
        CertLinesNew->Lines->Text = FSslCertTools->SaveReqToText(true);
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doCreateSelfCertClick(TObject *Sender)
{
    SetCertProps();
    TButtonBusy busy(doCreateSelfCert);
    try {
        FSslCertTools->X509 = nullptr;
        DispCert();
        CertLinesNew->Lines->Clear();
        FSslCertTools->DoSelfSignCert();
        DispError("Created self signed certificate OK");
        DispCert();
        CertLinesNew->Lines->Text = FSslCertTools->SaveCertToText(true);
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doGenKeyClick(TObject *Sender)
{
    FSslCertTools->PrivKeyType = static_cast<TSslPrivKeyType>(KeyType->ItemIndex);
    TButtonBusy busy(doGenKey);
    const TSslPrivKeyCipher PrivKeyType = CertPwPemKeys->Checked ? SelectedKeyCipher() : PrivKeyEncNone;
    try {
        FSslCertTools->PrivateKey = nullptr;
        KeyPairLines->Lines->Clear();
        DispError("Generating private and public key pair, please wait");
        // Delphi stores the 32-bit GetTickCount here but measures with the 64-bit ICS ticks.
        FStartTickCount = IcsGetTickCount64();
        FSslCertTools->DoKeyPair();
        KeyPairLines->Lines->Text = FSslCertTools->SavePKeyToText(SaveCertPW->Text, PrivKeyType);
        DispError("Generated private and public key pair OK with " +
                  String(SslPrivKeyCipherLits[PrivKeyType]) + " key cipher");
        DispPKey();
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void TfrmPemTool1::DispCert()
{
    const String info = "Certificate: ";
    if (!FSslCertTools->IsCertLoaded) {
        LabelStateCert->Caption = info + "None";
        return;
    }
    ShowLogWindow();
    LabelStateCert->Caption = info + FSslCertTools->CertInfo(false);
    AddLog(ListCertDetail(FSslCertTools.get()));
    AppendLogText("Raw Certificate\r\n" + FSslCertTools->GetRawText());
    AddLog("");
}

void TfrmPemTool1::DispPKey()
{
    LabelStatePrivKey->Caption = "Private Key: ";
    if (!FSslCertTools->IsPKeyLoaded) {
        LabelStatePrivKey->Caption = LabelStatePrivKey->Caption + "None";
        return;
    }
    ShowLogWindow();
    LabelStatePrivKey->Caption = LabelStatePrivKey->Caption + "\r\n" + FSslCertTools->PrivateKeyInfo;
    AppendLogText("Raw Private Key: " + FSslCertTools->PrivateKeyInfo + "\r\n" + FSslCertTools->GetPKeyRawText());
    AddLog("");
}

void TfrmPemTool1::DispReq()
{
    LabelStateReq->Caption = "Certificate Request: ";
    if (!FSslCertTools->IsReqLoaded) {
        LabelStateReq->Caption = LabelStateReq->Caption + "None";
        return;
    }
    ShowLogWindow();
    LabelStateReq->Caption = LabelStateReq->Caption + FSslCertTools->ReqCertInfo;
    AppendLogText(LabelStateReq->Caption);
    AppendLogText("Raw Certificate Request\r\n" + FSslCertTools->GetRequestRawText());
    AddLog("");
}

void TfrmPemTool1::DispInter()
{
    const String info = "Intermediate Certificates: ";
    if (!FSslCertTools->IsInterLoaded) {
        LabelInters->Caption = info + "None";
        return;
    }
    ShowLogWindow();
    LabelInters->Caption = info + FSslCertTools->ListInters();
    AppendLogText(LabelInters->Caption);
    AddLog("");
}

void TfrmPemTool1::DispCACert()
{
    LabelStateCACert->Caption = "CA Certificate: ";
    if (!FSslCAX509->IsCertLoaded)
        LabelStateCACert->Caption = "None";
    else   // assume the cert is still loaded before being copied to the CA
        LabelStateCACert->Caption = LabelStateCACert->Caption + FSslCAX509->CertInfo(true);
}

void __fastcall TfrmPemTool1::doLoadBase64Click(TObject *Sender)
{
    if (CertLinesOld->Text.Pos("-BEGIN CERTIFICATE-") == 0) {
        DispError("Must paste base64 certificate into control");
        return;
    }
    try {
        FSslCertTools->LoadFromText(CertLinesOld->Text, true, LoadCertPW->Text);
        DispCert();
        DispPKey();
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doLoadCABundleClick(TObject *Sender)
{
    const String fname = BuildLoadName(LoadCaBundleFile->Text);
    if (fname.IsEmpty())
        return;
    try {
        FSslCAX509->LoadFromFile(fname, croTry, croNo, LoadCertPW->Text);
        if (FSslCAX509->BasicConstraints.Pos("CA=TRUE") == 0) {
            DispError("Certificate does not allow CA signing");
            FSslCAX509->ClearAll();
            return;
        }
        if (!FSslCAX509->CheckCertAndPKey()) {
            DispError("Need matching CA certificate amd private key");
            FSslCAX509->ClearAll();
            return;
        }
        DispError("Loaded CA cerfificate OK - " + fname);
        DispCACert();
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doLoadCertClick(TObject *Sender)
{
    const String fname = BuildLoadName(LoadCertFile->Text);
    if (fname.IsEmpty())
        return;
    try {
        String Errs;
        FSslCertTools->LoadFromFileEx(fname, GetCertReadOpt(LoadCertPrivKey->Checked),
                                      GetCertReadOpt(LoadCertInters->Checked), LoadCertPW->Text, Errs);
        if (!Errs.IsEmpty())
            DispError("Failed to Loaded cerfificate: " + Errs + " - " + fname);
        else
            DispError("Loaded cerfificate OK - " + fname);
        DispCert();
        if (LoadCertPrivKey->Checked) {
            DispError("Loaded cerfificate and key OK - " + fname);
            DispPKey();
        }
        if (LoadCertInters->Checked)
            DispInter();
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doLoadIntersClick(TObject *Sender)
{
    const String fname = BuildLoadName(LoadInterCerts->Text);
    if (fname.IsEmpty())
        return;
    try {
        FSslCertTools->LoadIntersFromPemFile(fname);
        DispError("Loaded intermediates OK - " + fname);
        DispInter();
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doLoadPrvKeyClick(TObject *Sender)
{
    const String fname = BuildLoadName(LoadPrivatetKey->Text);
    if (fname.IsEmpty())
        return;
    try {
        FSslCertTools->PrivateKeyLoadFromPemFile(fname, LoadCertPW->Text);
        DispError("Loaded private key OK - " + fname);
        DispPKey();
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doLoadReqClick(TObject *Sender)
{
    const String fname = BuildLoadName(LoadRequestFile->Text);
    if (fname.IsEmpty())
        return;
    try {
        FSslCertTools->LoadReqFromFile(fname);
        DispError("Loaded request OK - " + fname);
        DispReq();
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doSaveCertDerClick(TObject *Sender)
{
    if (!FSslCertTools->IsCertLoaded) {
        DispError("Must load or create a certificate first");
        return;
    }
    const String fname = BuildSaveName(SaveCertDer->Text);
    if (fname.IsEmpty())
        return;
    try {
        FSslCertTools->SaveToDERFile(fname);
        DispError("Saved certificate OK - " + fname);
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doSaveCertPemClick(TObject *Sender)
{
    if (!FSslCertTools->IsCertLoaded) {
        DispError("Must load or create a certificate first");
        return;
    }
    TSslPrivKeyCipher PrivKeyType = PrivKeyEncNone;
    if (SavePrivateKey->Checked) {
        if (!FSslCertTools->IsPKeyLoaded) {
            DispError("Must load or create a private key first");
            return;
        }
        if (!FSslCertTools->CheckCertAndPKey()) {
            DispError("Mismatch certificate and private key");
            return;
        }
        if (CertPwPemKeys->Checked)
            PrivKeyType = SelectedKeyCipher();
    }
    if (SaveInterCerts->Checked && !FSslCertTools->IsInterLoaded) {
        DispError("Must load intermediate certificates first");
        return;
    }
    const String fname = BuildSaveName(SaveCertPem->Text);
    if (fname.IsEmpty())
        return;
    try {
        FSslCertTools->SaveToPemFile(fname, SavePrivateKey->Checked, CertAddComment->Checked,
                                     SaveInterCerts->Checked, SaveCertPW->Text, PrivKeyType);
        if (!SavePrivateKey->Checked)
            DispError("Saved certificate OK - " + fname);
        else
            DispError("Saved certificate OK with " + String(SslPrivKeyCipherLits[PrivKeyType]) +
                      " key cipher: " + fname);
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doSavePkcs12Click(TObject *Sender)
{
    if (!FSslCertTools->IsCertLoaded) {
        DispError("Must load or create a certificate first");
        return;
    }
    TSslPrivKeyCipher PrivKeyType = PrivKeyEncNone;
    if (SavePrivateKey->Checked) {   // the key is optional
        if (!FSslCertTools->IsPKeyLoaded) {
            DispError("Must load or create a private key first");
            return;
        }
        if (!FSslCertTools->CheckCertAndPKey()) {
            DispError("Mismatch certificate and private key");
            return;
        }
        if (CertPwPkcs12->Checked)
            PrivKeyType = SelectedKeyCipher();
    }
    if (SaveInterCerts->Checked && !FSslCertTools->IsInterLoaded) {
        DispError("Must load intermediate certificates first");
        return;
    }
    const String fname = BuildSaveName(SavePkcs12File->Text);
    if (fname.IsEmpty())
        return;
    try {
        FSslCertTools->SaveToP12File(fname, SaveCertPW->Text, SaveInterCerts->Checked, PrivKeyType,
                                     SavePrivateKey->Checked);
        DispError("Saved certificate OK with " + String(SslPrivKeyCipherLits[PrivKeyType]) +
                  " key cipher: " + fname);
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doSavePkcs7CertClick(TObject *Sender)
{
    if (!FSslCertTools->IsCertLoaded) {
        DispError("Must load or create a certificate first");
        return;
    }
    if (SaveInterCerts->Checked && !FSslCertTools->IsInterLoaded) {
        DispError("Must load intermediate certificates first");
        return;
    }
    const String fname = BuildSaveName(SavePkcs7File->Text);
    if (fname.IsEmpty())
        return;
    try {
        FSslCertTools->SaveToP7BFile(fname, SaveInterCerts->Checked);
        DispError("Saved certificate OK - " + fname);
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doSavePrivKeyClick(TObject *Sender)
{
    if (SavePrivateKey->Checked && !FSslCertTools->IsPKeyLoaded) {
        DispError("Must load or create a private key first");
        return;
    }
    const String fname = BuildSaveName(SavePrvFileFile->Text);
    if (fname.IsEmpty())
        return;
    const TSslPrivKeyCipher PrivKeyType = CertPwPemKeys->Checked ? SelectedKeyCipher() : PrivKeyEncNone;
    try {
        FSslCertTools->PrivateKeySaveToPemFile(fname, SaveCertPW->Text, PrivKeyType);
        DispError("Saved perivate key OK with " + String(SslPrivKeyCipherLits[PrivKeyType]) +
                  " key cipher: " + fname);
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doSavePubKeyClick(TObject *Sender)
{
    if (SavePrivateKey->Checked && !FSslCertTools->IsPKeyLoaded) {
        DispError("Must load or create a private key first");
        return;
    }
    const String fname = BuildSaveName(SavePubKeyFile->Text);
    if (fname.IsEmpty())
        return;
    try {
        FSslCertTools->PublicKeySaveToPemFile(fname);
        DispError("Saved public key OK - " + fname);
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::doSaveReqCertClick(TObject *Sender)
{
    if (!FSslCertTools->IsReqLoaded) {
        DispError("Must load or create a certificate request first");
        return;
    }
    const String fname = BuildSaveName(SaveReqCertFile->Text);
    if (fname.IsEmpty())
        return;
    try {
        FSslCertTools->SaveReqToFile(fname, CertAddComment->Checked);
        DispError("Saved certificate request OK - " + fname);
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
}

void __fastcall TfrmPemTool1::btnRefreshClick(TObject *Sender)
{
    FillListView();
}

void __fastcall TfrmPemTool1::btnDeleteCertClick(TObject *Sender)
{
    String FileName;
    TListItem *ListItem = SelectedCert(FileName);
    if (!ListItem)
        return;
    if (FileExists(FileName) &&
        MessageDlg("Delete Certificate '" + ListItem->Caption + "',\r\nfile '" + FileName + "' now?",
                   mtWarning, TMsgDlgButtons() << mbYes << mbNo, 0) != mrYes)
        return;
    if (DeleteFile(FileName))
        FillListView();
}

// Checks the digital signature on EXE and DLL files.
void __fastcall TfrmPemTool1::btnCheckSignedClick(TObject *Sender)
{
    OpenDlg->InitialDir = FVerifyDir;
    OpenDlg->Filter = "Executable Files|*.exe;*.dll;*.ocx|All Files *.*|*.*";
    OpenDlg->Title = "Select executable file to check Digital Signature";
    if (!OpenDlg->Execute())
        return;
    const String FileName = OpenDlg->FileName;
    FVerifyDir = ExtractFileDir(FileName);
    if (FileName.IsEmpty() || !FileExists(FileName))
        return;
    String Info;
    const int RetCode = IcsVerifyTrust(FileName, false, true, Info);
    MessageDlg("Checked Digital Signature for " + FileName + "\r\n\r\n" +
               "Result: " + Info + "\r\n" +
               "RetCode=" + IntToHex(RetCode, 8),
               mtInformation, TMsgDlgButtons() << mbOK, 0);
}

void __fastcall TfrmPemTool1::btnCopyCertClick(TObject *Sender)
{
    String FileName;
    TListItem *ListItem = SelectedCert(FileName);
    if (!ListItem || !FileExists(FileName))
        return;
    String NewFileName = FileName;
    if (!InputQuery(ListItem->Caption, "Copy to: ", NewFileName) || CompareText(NewFileName, FileName) == 0)
        return;
    if (::CopyFile(FileName.c_str(), NewFileName.c_str(), TRUE))
        FillListView();
    else
        MessageDlg("'" + FileName + "' copy failed!", mtError, TMsgDlgButtons() << mbOK, 0);
}

void __fastcall TfrmPemTool1::btnImportPemFileClick(TObject *Sender)
{
    OpenDlg->InitialDir = DestDirEdit->Text;
    OpenDlg->Filter = SslCertFileOpenExts;
    String FileName;
    if (OpenDlg->Execute())
        FileName = OpenDlg->FileName;
    if (FileName.IsEmpty() || !FileExists(FileName))
        throw Exception("FileName empty or file doesn't exist");

    std::unique_ptr<TX509Base> X(new TX509Base(nullptr, nullptr));
    X->LoadFromFile(FileName, croNo, croNo, CertPassword->Text);
    const unsigned Subject_Hash = X509_subject_name_hash(X->X509);
    FileName = DirWithDelimiter(Trim(DestDirEdit->Text)) +
               IntToHex(static_cast<__int64>(Subject_Hash), 8) + ".0";
    if (FileExists(FileName)) {
        const int Answer = MessageDlg(
            "A certificate with the same subject already exists in folder '" + Trim(DestDirEdit->Text) +
            "' . Change file extension?\r\n"
            "Click 'Yes' to change file extension.\r\n"
            "Click 'No' to overwrite existing file.\r\n"
            "Click 'Cancel' to abort.",
            mtWarning, TMsgDlgButtons() << mbYes << mbNo << mbCancel, 0);
        if (Answer == mrYes)
            FileName = FindPemFileName(FileName);
        else if (Answer != mrNo)   // Delphi aborts on No as well, although the prompt says No overwrites
            return;
    }
    X->SaveToPemFile(FileName);
    ShowMessage("Certificate has been stored to '" + FileName + "'");
}

void __fastcall TfrmPemTool1::MMFileExitClick(TObject *Sender)
{
    Close();
}

void __fastcall TfrmPemTool1::MMExtrasCreateSelfSignedCertClick(TObject *Sender)
{
    frmPemTool3->ToDo = tdCreateSelfSignedCert;
    frmPemTool3->ShowModal();
}

void __fastcall TfrmPemTool1::MMExtrasCreateCertRequestClick(TObject *Sender)
{
    frmPemTool3->ToDo = tdCreateCertRequest;
    frmPemTool3->ShowModal();
}

void __fastcall TfrmPemTool1::MMResaveKeyClick(TObject *Sender)
{
    const String FileName = PickCertFile(SslCertFileOpenExts);
    if (FileName.IsEmpty())
        return;
    ShowLogWindow();
    const TSslPrivKeyCipher Cipher = static_cast<TSslPrivKeyCipher>(KeyEncrypt->ItemIndex);
    AddLog("Resaving file with " + String(SslPrivKeyCipherLits[Cipher]) + " key cipher: " + FileName);
    String ErrMsg;
    ResavePrivateKey(FileName, SaveCertPW->Text, Cipher, ErrMsg);
    AddLog(ErrMsg);
}

// The Windows Certificate Store handlers below are bound to controls in BoxStoreTools (and
// Install ICS Root in the Quick box). Without ICS_HAVE_MSCRYPT the box is hidden and they do
// nothing, as in the Delphi {$ELSE} path.

// Exports every certificate in the selected store as <subject hash>.0 PEM files, and
// optionally into one CA bundle file.
void __fastcall TfrmPemTool1::btnImportClick(TObject *Sender)
{
#ifdef ICS_HAVE_MSCRYPT
    const String Path = Trim(DestDirEdit->Text);
    // Delphi passes an empty or uncreatable destination on to ForceDirectories and the export.
    if (Path.IsEmpty()) {
        DispError("Must specify destination directory");
        return;
    }
    if (!ForceDirectories(Path)) {
        DispError("Failed to create destination directory - " + Path);
        return;
    }
    if (CheckBoxWarnDestNotEmpty->Checked && !IsDirEmpty(Path) &&
        MessageDlg("Directory '" + Path + "' is not empty. Continue?",
                   mtWarning, TMsgDlgButtons() << mbYes << mbNo, 0) != mrYes)
        return;
    if (CheckBoxEmptyDestDir->Checked) {
        if (MessageDlg("Any file in destination '" + Path + "' will be deleted. Continue?",
                       mtWarning, TMsgDlgButtons() << mbYes << mbNo, 0) != mrYes)
            return;
        EmptyDirectory(Path);
    }

    const TMsCertStoreType StoreType = static_cast<TMsCertStoreType>(MsStoreType->ItemIndex);
    const String StoreTitle = MsStoreType->Items->Strings[MsStoreType->ItemIndex];
    std::unique_ptr<TMsX509List> MsX509List(new TMsX509List(nullptr, true));
    const int Total = MsX509List->LoadFromStore(StoreType,
                                                static_cast<TMsCertLocation>(MsLocationType->ItemIndex), true);
    // LoadFromStore returns 0 both for an open failure and for an empty store; only a failure
    // sets MsLastError. Delphi reports an empty store as "Could not open".
    if (Total == 0 && !MsX509List->MsLastError.IsEmpty()) {
        ShowMessage("Could not open the '" + StoreTitle + "' system store: " + MsX509List->MsLastError);
        return;
    }
    if (Total == 0) {
        ShowMessage("The '" + StoreTitle + "' system store is empty.");
        return;
    }
    ShowMessage("Opened the '" + StoreTitle + "' system store, " + IntToStr(Total) + " certificates.");

    // Declared after MsX509List, so the bundle file is closed before the list is freed.
    TBioPtr BundleBio;
    String BundleFilename;
    if (CheckBoxWriteToBundle->Checked) {
        const String BundlePath = DirWithDelimiter(Path) + "Bundled certs";
        ForceDirectories(BundlePath);
        BundleFilename = DirWithDelimiter(BundlePath) + MsCertStoreNames[static_cast<int>(StoreType)] +
                         "CertsBundle.pem";
        // IcsSslOpenFileBio passes the name as UTF-8 (Delphi: AnsiString) and raises if the file cannot be opened.
        BundleBio.reset(IcsSslOpenFileBio(BundleFilename, bomWrite));
    }

    int Count = 0;
    for (int I = 0; I < Total; I++) {
        TX509Base *Cert = MsX509List->Items[I];
        String FileName = DirWithDelimiter(Path) +
                          IntToHex(static_cast<__int64>(X509_subject_name_hash(Cert->X509)), 8) + ".0";
        if (!CheckBoxOverwriteExisting->Checked && FileExists(FileName))
            FileName = FindPemFileName(FileName);
        Cert->SaveToPemFile(FileName, false, CheckBoxComment->Checked);
        Count++;
        if (BundleBio) {
            Cert->WriteCertToBio(BundleBio.get(), CheckBoxComment->Checked, BundleFilename);
            Cert->WriteStrBio(BundleBio.get(), "\n\n");   // blank lines between certificates
        }
    }
    ShowMessage(IntToStr(Count) + " Certificates exported.");
#endif
}

// Lists the certificates and private keys of the selected store and location.
void __fastcall TfrmPemTool1::doMsDisplayClick(TObject *Sender)
{
#ifdef ICS_HAVE_MSCRYPT
    if (!FMsX509List)
        FMsX509List.reset(new TMsX509List(nullptr, true));
    FMsX509List->Clear();
    FMsListStoreType = static_cast<TMsCertStoreType>(MsStoreType->ItemIndex);
    FMsListLocation = static_cast<TMsCertLocation>(MsLocationType->ItemIndex);
    const int Total = FMsX509List->LoadFromStore(FMsListStoreType, FMsListLocation, true);
    ShowLogWindow();
    AddLog("Microsoft Certificate Store: " + MsStoreType->Items->Strings[MsStoreType->ItemIndex] +
           " - " + MsLocationType->Items->Strings[MsLocationType->ItemIndex]);
    CertStoreList->Items->Clear();
    if (!FMsX509List->MsLastError.IsEmpty())
        AddLog("Errors reading store: " + FMsX509List->MsLastError);
    if (Total <= 0)
        AddLog("No certificates found in store");
    else {
        AddLog("Number of certificates found in store: " + IntToStr(Total));
        for (int I = 0; I < Total; I++) {
            TX509Base *Cert = FMsX509List->Items[I];
            const String Item = Cert->CertMainInfo();
            CertStoreList->Items->Add(Item);
            String Info = "Certificate #" + IntToStr(I + 1) + " ";
            Info += MSBriefList->Checked ? Item : "\r\n" + ListCertDetail(Cert);
            Info += "\r\nFriendly Name: " + Cert->CertName;
            if (!Cert->Comments.IsEmpty())
                Info += ", Key Name: " + Cert->KeyName + " - " + Cert->Comments;
            AddLog(Info);
            if (Cert->IsPKeyLoaded) {
                if (Cert->CheckCertAndPKey())
                    AddLog("!! Private key available for certificate: " + Cert->KeyInfo);
                else
                    AddLog("!! Private key available but does not match certificate");
            }
        }
    }
    AddLog("");
#endif
}

// Loads the selected store certificate (and its private key) into Create Certs, so it can be
// saved in other formats.
void __fastcall TfrmPemTool1::doLoadtoCreateClick(TObject *Sender)
{
#ifdef ICS_HAVE_MSCRYPT
    const int I = CertStoreList->ItemIndex;
    if (I < 0) {
        DispError("No certificate store item selected");
        return;
    }
    try {
        TX509Base *Cert = FMsX509List->Items[I];
        FSslCertTools->ClearAll();
        FSslCertTools->X509 = Cert->X509;
        FSslCertTools->PrivateKey = Cert->PrivateKey;
        FSslCertTools->Comments = Cert->Comments;
        if (FSslCertTools->IsCertLoaded) {
            DispError("Loaded cerfificate OK - " + FSslCertTools->Comments);
            DispCert();
            if (LoadCertPrivKey->Checked) {
                DispError("Loaded cerfificate and key OK - " + FSslCertTools->Comments);
                DispPKey();
            }
            PageControl1->ActivePage = TabCreateCerts;
        }
        else
            DispError("Failed to loaded cerfificate from store");
    }
    catch (Exception &E) {
        DispError(E.Message);
    }
#endif
}

// Deletes the selected certificate from the store; its private key is kept, since other
// certificates may use it.
void __fastcall TfrmPemTool1::doCertStoreDelClick(TObject *Sender)
{
#ifdef ICS_HAVE_MSCRYPT
    ShowLogWindow();
    const int I = CertStoreList->ItemIndex;
    if (I < 0) {
        AddLog("No certificate store item selected");
        return;
    }
    // Delphi deletes from the store selected now, which may not be the store that was listed.
    if (MsStoreType->ItemIndex != static_cast<int>(FMsListStoreType) ||
        MsLocationType->ItemIndex != static_cast<int>(FMsListLocation)) {
        AddLog("Store type or location changed since the store was displayed, display it again first");
        return;
    }
    try {
        TX509Base *Cert = FMsX509List->Items[I];
        const String CName = Cert->CertName;
        if (FMsX509List->DeleteFromStore(Cert->Sha1Digest, FMsListStoreType, FMsListLocation, false)) {
            AddLog("Deleted cerfificate from store OK - " + CName);
            AddLog("");
            doMsDisplayClick(this);   // refresh the store list
        }
        if (!FMsX509List->MsLastError.IsEmpty())
            AddLog("Failed to delete cerfificate from store - " + FMsX509List->MsLastError);
    }
    catch (Exception &E) {
        AddLog(E.Message);
    }
#endif
}

// Installs a certificate file, with its private key and intermediates if present, into the
// selected store.
void __fastcall TfrmPemTool1::doMsInstallFileClick(TObject *Sender)
{
#ifdef ICS_HAVE_MSCRYPT
    ShowLogWindow();
    const TMsCertLocation Location = static_cast<TMsCertLocation>(MsLocationType->ItemIndex);
    if (!IcsIsProgAdmin() && Location == MsLocMachine) {
        AddLog("Program needs administrator rights to access local machine store");
        return;
    }
    if (!FileExists(MsInstallFile->Text)) {
        AddLog("Certificates file not found: " + MsInstallFile->Text);
        return;
    }
    std::unique_ptr<TMsCertTools> MsCertTools(new TMsCertTools(nullptr));
    try {
        String Errs;
        MsCertTools->LoadFromFileEx(MsInstallFile->Text, croTry, croTry, CertPassword->Text, Errs);
        if (!Errs.IsEmpty())
            AddLog(Errs);   // Delphi discards the load errors
        MsCertTools->SaveToStorePfx(Location, MsCertTools->IsPKeyLoaded, MsCertTools->IsInterLoaded,
                                    static_cast<TMsCertStoreType>(MsStoreType->ItemIndex));
        AddLog("Saved certificate to Windows Store OK");
        AddLog("");
        doMsDisplayClick(this);   // refresh the store list
    }
    catch (Exception &E) {
        AddLog(E.Message);
    }
#endif
}

// Lists the private key names in the selected key provider and location.
void __fastcall TfrmPemTool1::doListPkeysClick(TObject *Sender)
{
#ifdef ICS_HAVE_MSCRYPT
    ShowLogWindow();
    PkeyList->Items->Clear();
    FStorePkeyInfos.Length = 0;
    const TMsCertLocation Location = static_cast<TMsCertLocation>(MsLocationType->ItemIndex);
    // Delphi offers all five locations, but ICS ListPKeys/DeletePKey only tell Local Machine
    // apart; the others silently list the current user's keys.
    if (Location != MsLocCurUser && Location != MsLocMachine) {
        AddLog("The private key store can only be listed for Current User or Local Machine");
        return;
    }
    if (!IcsIsProgAdmin() && Location == MsLocMachine) {
        AddLog("Program needs administrator rights to access local machine keys");
        return;
    }
    if (!FMsX509List)
        FMsX509List.reset(new TMsX509List(nullptr, true));
    FStorePkeyInfos.Length = 1;   // ListPKeys returns 0 without looking if the array is empty
    FMsKeyListProvider = static_cast<TMsKeyProvider>(MSKeyStoreType->ItemIndex);
    FMsKeyListLocation = Location;
    FMsX509List->MsKeyProvider = MsKeyProviderNames[static_cast<int>(FMsKeyListProvider)];
    const int Total = FMsX509List->ListPKeys(Location, FStorePkeyInfos);
    AddLog("Microsoft " + MSKeyStoreType->Items->Strings[MSKeyStoreType->ItemIndex] +
           ": " + MsLocationType->Items->Strings[MsLocationType->ItemIndex]);
    if (!FMsX509List->MsLastError.IsEmpty())
        AddLog("Errors reading key store: " + FMsX509List->MsLastError);
    if (Total <= 0)
        AddLog("No private keys found in store");
    else {
        AddLog("Number of private keys found in store: " + IntToStr(Total));
        for (int I = 0; I < Total; I++) {
            const String Info = FStorePkeyInfos[I].KeyName + " - " + FStorePkeyInfos[I].KeyAlg;
            PkeyList->Items->Add(Info);
            AddLog(Info);
        }
    }
    AddLog("");
#endif
}

// Deletes the selected private key. The .dfm leaves this button disabled.
void __fastcall TfrmPemTool1::doKeyStoreDelClick(TObject *Sender)
{
#ifdef ICS_HAVE_MSCRYPT
    ShowLogWindow();
    const int I = PkeyList->ItemIndex;
    if (I < 0) {
        AddLog("No private key store item selected");
        return;
    }
    // Delphi deletes with the location selected now, which may not be the one that was listed.
    // The listed location is always Current User or Local Machine (see doListPkeysClick).
    if (MSKeyStoreType->ItemIndex != static_cast<int>(FMsKeyListProvider) ||
        MsLocationType->ItemIndex != static_cast<int>(FMsKeyListLocation)) {
        AddLog("Key provider or location changed since the keys were listed, list them again first");
        return;
    }
    try {
        const String CName = FStorePkeyInfos[I].KeyName;
        FMsX509List->MsKeyProvider = MsKeyProviderNames[static_cast<int>(FMsKeyListProvider)];
        if (FMsX509List->DeletePKey(CName, FMsKeyListLocation)) {
            AddLog("Deleted private key from store OK - " + CName);
            AddLog("");
            doListPkeysClick(this);   // refresh the key list
        }
        if (!FMsX509List->MsLastError.IsEmpty())
            AddLog("Failed to delete private key from store - " + FMsX509List->MsLastError);
    }
    catch (Exception &E) {
        AddLog(E.Message);
    }
#endif
}

// Searches the store for the best matching certificate and loads it, with its key and
// intermediates, into Create Certs; used to test IcsHosts loading certificates from the store.
void __fastcall TfrmPemTool1::doSearchMsStoreClick(TObject *Sender)
{
#ifdef ICS_HAVE_MSCRYPT
    ShowLogWindow();
    const TMsCertLocation MsCertLoc = static_cast<TMsCertLocation>(MsLocationType->ItemIndex);
    // Keys imported or created as exportable can be read from the current user's store
    // without administrator rights.
    const bool LoadPrivateCerts = (MsCertLoc == MsLocCurUser) || IcsIsProgAdmin();
    if (!LoadPrivateCerts) {
        AddLog("Program needs administrator rights to export private keys from the selected store");
        return;
    }
    MsSearchCert->Text = Trim(MsSearchCert->Text);
    if (MsSearchCert->Text.IsEmpty()) {
        AddLog("Must enter certificate name to search");
        return;
    }
    {
        std::unique_ptr<TMsCertTools> MsCertTools(new TMsCertTools(nullptr));
        try {
            const String ErrStr = MsCertTools->LoadOneFromStore(MsCertLoc, MsSearchCert->Text, LoadPrivateCerts);
            if (!ErrStr.IsEmpty()) {
                AddLog(ErrStr);
                return;
            }
            FSslCertTools->ClearAll();
            FSslCertTools->Assign(MsCertTools.get());
        }
        catch (Exception &E) {
            AddLog(E.Message);
            return;   // Delphi carries on and reports the previously loaded certificate as found
        }
    }
    try {
        if (FSslCertTools->CheckCertAndPKey()) {
            if (IcsSslRootCAStore->Count == 0)
                LoadRootCA();
            String CertStr, ErrStr;
            const TChainResult ValRes = FSslCertTools->ValidateCertChain("", IcsSslRootCAStore, CertStr, ErrStr);
            if (ValRes == chainOK)
                ErrStr = "Chain Validated OK";
            else if (ValRes == chainWarn)
                ErrStr = "Chain Warning - " + ErrStr;
            else
                ErrStr = "Chain Failed - " + ErrStr;
            DispError("Loaded cerfificate and key " + ErrStr + " - " + MsSearchCert->Text);
            AddLog(FSslCertTools->CertInfo(true));
            DispCert();
            DispPKey();
            DispInter();
            AddLog(ErrStr);
            PageControl1->ActivePage = TabCreateCerts;
        }
        else
            DispError("Failed to loaded cerfificate from store");
    }
    catch (Exception &E) {
        AddLog(E.Message);
    }
#endif
}

// Installs the ICS root certificate into the Windows store.
void __fastcall TfrmPemTool1::doInstallIcsRootClick(TObject *Sender)
{
#ifdef ICS_HAVE_MSCRYPT
    ShowLogWindow();
    try {
        String Errs;
        IcsInstallIcsRoot(Errs);
        AddLog(Errs);
    }
    catch (Exception &E) {
        AddLog(E.Message);
    }
#endif
}

void __fastcall TfrmPemTool1::SelInstallFileClick(TObject *Sender)
{
    OpenDirDiag->InitialDir = ExtractFilePath(MsInstallFile->Text);
    if (OpenDirDiag->Execute())
        MsInstallFile->Text = OpenDirDiag->FileName;
}

/* Test Host Certificates: TIcsIpStrmLog makes an SSL/TLS connection to each host in
   turn. When the handshake is done the chain is added to the downloaded lists, the
   state change posts a disconnect, and the disconnect posts the next host. */
void __fastcall TfrmPemTool1::IcsIpClientLogChangeEvent(TObject *Sender, int Socnr, TStrmLogState LogState)
{
    switch (LogState) {
        case logstateNone:      // disconnect done, try another host
            PostMessage(Handle, WM_IPLOGCONNECT, 0, 0);
            break;
        case logstateOK:        // immediate disconnect
        case logstateStopping:
            PostMessage(Handle, WM_IPLOGDISCONNECT, 0, 0);
            break;
        default:
            break;
    }
}

void __fastcall TfrmPemTool1::WMIpLogConnect(TMessage &Msg)
{
    FTestHostCur++;
    if (FTestHostCur >= FTestHostTot || FTestHostCur >= TestHostList->Lines->Count) {
        AddLog("Finished Testing Hosts");
        AddLog("");
        doTestOneCert->Enabled = true;
        doTestListCerts->Enabled = true;
        if (DownloadCerts->Items->Count > 1)
            DownloadCerts->ItemIndex = 0;
        if (DownloadInters->Items->Count > 1)
            DownloadInters->ItemIndex = 0;
        return;
    }
    TestHostCert(TestHostList->Lines->Strings[FTestHostCur]);
}

void __fastcall TfrmPemTool1::WMIpLogDisconnect(TMessage &Msg)
{
    IcsIpClient->StopLogging();
}

// Builds the lists of server and intermediate certificates so they can be viewed and saved.
void __fastcall TfrmPemTool1::IcsIpClientLogHandshakeDone(TObject *Sender, Word ErrCode, TX509Base *PeerCert, bool &Disconnect)
{
    if (ErrCode != 0)
        return;
    try {
        TSslWSocket *Socket = dynamic_cast<TSslWSocket*>(Sender);
        if (!Socket || !Socket->SslCertChain)
            return;
        TX509List *Chain = Socket->SslCertChain;
        // the chain may be in random order, so check each one for the server certificate
        for (int I = 0; I < Chain->Count; I++) {
            TX509Base *Cert = Chain->Items[I];
            if (Cert->SelfSigned)
                continue;   // skip trusted roots
            if (!Cert->SubAltAllDisp.IsEmpty()) {
                if (FDownCertsList->IndexOfSubj(Cert->SubjectOneLine) >= 0)
                    return;
                FDownCertsList->Add(Cert->X509);
                DownloadCerts->Items->Add(Cert->CertMainInfo());
            }
            else if (FDownIntersList->IndexOfSubj(Cert->SubjectOneLine) < 0) {   // intermediate
                FDownIntersList->Add(Cert->X509);
                DownloadInters->Items->Add(Cert->CertMainInfo());
            }
        }
    }
    catch (Exception &) {
    }
}

void __fastcall TfrmPemTool1::IcsIpClientLogProgEvent(TObject *Sender, int Socnr, TLogOption LogOption, const String Msg)
{
    AddLog(Msg);
}

void __fastcall TfrmPemTool1::IcsIpClientLogRecvEvent(TObject *Sender, int Socnr, const String Line)
{
    // ignore received data, there should be none
}

void TfrmPemTool1::TestHostCert(const String &HostPort)
{
    if (HostPort.Pos(".") == 0 || HostPort.Pos("*") == 1 || HostPort.Pos(";") == 1) {
        PostMessage(Handle, WM_IPLOGCONNECT, 0, 0);
        return;
    }
    doTestOneCert->Enabled = false;
    doTestListCerts->Enabled = false;
    IcsIpClient->SocFamily = sfAny;
    IcsIpClient->ForceSsl = true;
    IcsIpClient->RetryAttempts = -1;   // no retries
    IcsIpClient->LogSslVerMethod = logSslVerBundle;
    IcsIpClient->LogSslRevocation = true;
    IcsIpClient->LogSslReportChain = true;
    IcsIpClient->LogSslCliSecurity = sslCliSecHigh;

    String Host = LowerCase(Trim(HostPort));
    if (Host.Pos("https://") == 1)
        Host = Host.SubString(9, 999);
    const int I = Host.Pos(":");
    if (I > 1) {
        IcsIpClient->RemoteHost = Host.SubString(1, I - 1);
        IcsIpClient->RemoteIpPort = Host.SubString(I + 1, 9);
    }
    else {
        IcsIpClient->RemoteHost = Host;
        IcsIpClient->RemoteIpPort = "443";
    }
    IcsIpClient->LogTitle = IcsIpClient->RemoteHost + ":" + IcsIpClient->RemoteIpPort;
    AddLog("Start Testing Host: " + IcsIpClient->LogTitle);
    IcsIpClient->StartLogging();
}

void __fastcall TfrmPemTool1::doTestAbortClick(TObject *Sender)
{
    if (doTestListCerts->Enabled)
        return;
    FTestHostCur = FTestHostTot + 1;
    IcsIpClient->StopLogging();
}

void __fastcall TfrmPemTool1::doTestListCertsClick(TObject *Sender)
{
    ShowLogWindow();
    FTestHostTot = TestHostList->Lines->Count;
    if (FTestHostTot == 0)
        return;
    FTestHostCur = 0;
    AddLog("Start Testing Host List");
    TestHostCert(TestHostList->Lines->Strings[0]);
}

void __fastcall TfrmPemTool1::doTestOneCertClick(TObject *Sender)
{
    ShowLogWindow();
    FTestHostTot = 1;
    FTestHostCur = 0;
    AddLog("Start Testing One Host");
    TestHostCert(TestHostOne->Text);
}

void __fastcall TfrmPemTool1::DownloadCertsDblClick(TObject *Sender)
{
    const int I = DownloadCerts->ItemIndex;
    if (I < 0 || I >= FDownCertsList->Count)
        return;
    AddLog(ListCertDetail(FDownCertsList->Items[I]) + "\r\n");
    AddLog(ListOcspStatus(FDownCertsList->Items[I]));
}

void __fastcall TfrmPemTool1::DownloadIntersDblClick(TObject *Sender)
{
    const int I = DownloadInters->ItemIndex;
    if (I < 0 || I >= FDownIntersList->Count)
        return;
    AddLog(ListCertDetail(FDownIntersList->Items[I]) + "\r\n");
    AddLog(ListOcspStatus(FDownIntersList->Items[I]));
}

void __fastcall TfrmPemTool1::SelDownHostPathClick(TObject *Sender)
{
    OpenDlg->InitialDir = DownCertsPath->Text;
    if (OpenDlg->Execute())
        DownCertsPath->Text = ExtractFilePath(OpenDlg->FileName);
}

void __fastcall TfrmPemTool1::SelDownIntersClick(TObject *Sender)
{
    OpenDlg->InitialDir = DownIntersPath->Text;
    if (OpenDlg->Execute())
        DownIntersPath->Text = ExtractFilePath(OpenDlg->FileName);
}

void __fastcall TfrmPemTool1::doAddInterBundleClick(TObject *Sender)
{
    if (FDownIntersList->Count == 0)
        return;
    const String FName = IntersCAFile->Text;
    if (FName.IsEmpty())
        return;
    if (FInterCAList->Count == 0)
        LoadInterCA();
    const Word Mode = FileExists(FName) ? static_cast<Word>(fmOpenReadWrite) : static_cast<Word>(fmCreate);
    std::unique_ptr<TFileStream> BundleFile;   // closed on every exit path (Delphi: no try/finally)
    try {
        BundleFile.reset(new TFileStream(FName, Mode));
    }
    catch (Exception &E) {
        AddLog("Failed to Open Bundle Files: " + FName + " - " + E.Message);
        return;
    }
    BundleFile->Seek(static_cast<__int64>(0), soEnd);
    int Tot = 0;
    for (int I = 0; I < FDownIntersList->Count; I++) {
        if (!DownloadInters->Checked[I])
            continue;
        TX509Base *Cert = FDownIntersList->Items[I];
        if (FInterCAList->FindDigest(Cert->Sha1Digest))
            continue;
        FInterCAList->Add(Cert->X509);
        const RawByteString utftext = StringToUtf8(Cert->SaveCertToText(true) + IcsCRLF + IcsCRLF);
        BundleFile->Write(utftext.c_str(), utftext.Length());
        AddLog("Added Certificate: " + Cert->SubjectCName + " to Bundle");
        DownloadInters->Checked[I] = false;
        Tot++;
    }
    BundleFile.reset();   // close the file before reporting
    if (Tot > 0)
        AddLog("Finshed Saving " + IntToStr(Tot) + " Certificates to File: " + FName);
}

// Saves each ticked downloaded certificate as <sha256>.pem in Path.
void TfrmPemTool1::SaveDownloadedCerts(TX509List *List, TCheckListBox *Box, const String &Path)
{
    if (List->Count == 0)
        return;
    if (!IcsDirExists(Path)) {
        AddLog("Download Path Not Found: " + Path);
        return;
    }
    int Tot = 0;
    for (int I = 0; I < List->Count; I++) {
        if (!Box->Checked[I])
            continue;
        TX509Base *Cert = List->Items[I];
        const String FName = DirWithDelimiter(Path) + IcsLowerCase(Cert->Sha256Hex) + ".pem";
        try {
            if (FileExists(FName))
                DeleteFile(FName);
            Cert->SaveToPemFile(FName, false, true);
            AddLog("Saved Certificate: " + Cert->SubjectCName + " as " + FName);
            Box->Checked[I] = false;
            Tot++;
        }
        catch (Exception &) {
            AddLog("Failed to Save Certificate: " + Cert->SubjectCName + " as " + FName);
        }
    }
    AddLog("Finshed Saving " + IntToStr(Tot) + " Certificate Files");
}

void __fastcall TfrmPemTool1::doSaveIntersClick(TObject *Sender)
{
    SaveDownloadedCerts(FDownIntersList.get(), DownloadInters, DownIntersPath->Text);
}

void __fastcall TfrmPemTool1::doHostSaveClick(TObject *Sender)
{
    SaveDownloadedCerts(FDownCertsList.get(), DownloadCerts, DownCertsPath->Text);
}

// Quick certificates: one call to CreateSelfSignCertEx makes a PEM bundle with key and
// intermediate, self signed or signed by the CA bundle in QuickCAFile.
void TfrmPemTool1::CreateQuickCert(bool CASigned)
{
    Status->Caption = "";
    ShowLogWindow();
    if (!IcsDirExists(QuickCertPath->Text)) {
        AddLog("Save Path Not Found: " + QuickCertPath->Text);
        return;
    }
    // Delphi appends the file name without a path delimiter, so a path without a trailing '\' is wrong.
    const String NewFName = DirWithDelimiter(QuickCertPath->Text) +
                            IcsBuildCertFName(CertCommonName->Text) + ".pem";
    try {
        CleanAltDomains(CertCommonName->Text);
        const TSslPrivKeyType KeyKind = static_cast<TSslPrivKeyType>(KeyType->ItemIndex);
        if (CASigned)
            CreateSelfSignCertEx(NewFName, CertCommonName->Text, CertAltDomains->Lines, KeyKind,
                                 QuickPassword->Text, String(), QuickCAFile->Text, GetCertHash(QuickCAFile->Text));
        else
            CreateSelfSignCertEx(NewFName, CertCommonName->Text, CertAltDomains->Lines, KeyKind,
                                 QuickPassword->Text, String(), String());
        ShowOneCert(NewFName);
        Status->Caption = "Created certificate OK: " + NewFName;
    }
    catch (Exception &E) {
        AddLog(E.Message);
    }
}

void __fastcall TfrmPemTool1::doQuickCASignClick(TObject *Sender)
{
    CreateQuickCert(true);
}

void __fastcall TfrmPemTool1::doQuickSelfSignClick(TObject *Sender)
{
    CreateQuickCert(false);
}
