/*
  ICS SSL Json Object Signing (Jose) Demos - C++Builder translation of the Delphi demo
  OverbyteIcsJoseTst1.pas (demos-delphi-vcl), ICS V9.8.

  Four tabs:
  - JSON/XML: parses pasted Json or XML into a grid; a double click opens a nested object
    or array in the Json Object window (OverbyteIcsJoseTst2).
  - Jose: private keys (new, or from a PEM file), HMAC and asymmetric signing digests,
    Json Web Keys and Json Web Signatures, RSA public key encryption.
  - Transcode: Base64, Base64Url, hex and URL encoding, hash digests.
  - Cryptography: the TIcsSymCrypto symmetric cipher component on strings, streams and files.

  How this C++ version differs from the Delphi original, apart from syntax:
  - The private key, verify key and TIcsSymCrypto are held in std::unique_ptr (Delphi creates
    them in FormCreate and frees them in FormDestroy). Settings are saved in the destructor;
    the .dfm OnDestroy binding is removed, because in C++ that event runs after the form's
    members are destroyed.
  - Delphi unit globals (ini file name, first-show flag, array rows, key-from-file flag) are
    form members.
  - Delphi demo defects are corrected where they happen, each with a one-line comment.
*/

#include <vcl.h>
#pragma hdrstop

#include "OverbyteIcsJoseTst1.h"
#include "OverbyteIcsJoseTst2.h"

#include <System.TypInfo.hpp>
#include <memory>

#include "OverbyteIcsUtils.hpp"
#include "OverbyteIcsUrl.hpp"
#include "OverbyteIcsSha1.hpp"
#include "OverbyteIcsSSLEAY.hpp"
#include "OverbyteIcsLIBEAY.hpp"
#include "OverbyteIcsSuperObject.hpp"
#include "OverbyteIcsSuperXMLParser.hpp"
#include "OverbyteIcsIniFiles.hpp"

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

TJsonDemoForm *JsonDemoForm;

namespace {

const String SectionMainWindow = "MainWindow";
const String KeyTop = "Top";
const String KeyLeft = "Left";
const String KeyWidth = "Width";
const String KeyHeight = "Height";
const String SectionData = "Data";

const String TrueText = "True";
const String FalseText = "False";

// The largest file Encrypt File reads into memory to compare with its decrypted copy.
const __int64 MaxFileCompareSize = 256 * 1024 * 1024;

// Base64Url text to lower-case hex.
String B64Url2Hex(const String &S)
{
    return LowerCase(IcsTBToHex(IcsBase64UrlDecodeTB(S)));
}

// The UTF-8 bytes of S in an AnsiString, byte for byte; the ICS RSA functions take AnsiString.
AnsiString Utf8Bytes(const String &S)
{
    const RawByteString Utf8 = StringToUtf8(S);
    return AnsiString(Utf8.c_str(), Utf8.Length());
}

// Decodes UTF-8 bytes held in an AnsiString, without a code page conversion first.
String FromUtf8Bytes(const AnsiString &S)
{
    return Utf8ToStringW(RawByteString(S.c_str(), S.Length()));
}

}  // namespace

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

__fastcall TJsonDemoForm::~TJsonDemoForm()
{
    // Delphi saves in OnDestroy; here the controls still exist and the members are not yet freed.
    try {
        SaveSettings();
    }
    catch (...) {
        // a destructor must not throw; the settings are simply not saved
    }
}

void __fastcall TJsonDemoForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    const String ProgDir = ExtractFilePath(ParamStr(0));
    FIniFileName = GetIcsIniFileName(true);
    GSSL_DLL_DIR = ProgDir;             // only from our directory
    GSSL_SignTest_Check = true;         // check digitally signed
    GSSL_SignTest_Certificate = true;   // check digital certificate
    GSSLEAY_LOAD_LEGACY = true;         // OpenSSL 3 legacy provider for old algorithms
    IcsSslRootCAStore->Initialise();
    LibeayLoadProviders(true, false);   // legacy provider needed
    FPrivateKey.reset(new TSslCertTools(nullptr));
    FVerifyPKey.reset(new TX509Base(nullptr, nullptr));
    NewprivKey->Items->Clear();
    CipherICsTypes->Items->Clear();
    for (int I = 0; I < static_cast<int>(SslPrivKeyTypeLits.size()); I++)
        NewprivKey->Items->Add(SslPrivKeyTypeLits[I]);
    for (int I = 0; I < static_cast<int>(JoseAlgDescLits.size()); I++)
        TestJWSAlg->Items->Add(JoseAlgDescLits[I]);
    for (int I = 0; I <= static_cast<int>(Cipher_des_ede3_ofb); I++)
        CipherICsTypes->Items->Add(GetEnumName(__delphirtti(TEvpCipher), I));

    FSymCrypto.reset(new TIcsSymCrypto());
    GetCiphers();
}

void TJsonDemoForm::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);
    auto WriteBool = [&IniFile](const String &Key, bool Value) {
        IniFile->WriteString(SectionData, Key, Value ? TrueText : FalseText);
    };
    IniFile->WriteString(SectionData, "Base64Text_Text", Base64Text->Text);
    IniFile->WriteString(SectionData, "CodeTextLines_Lines", CodeTextLines->Lines->CommaText);
    WriteBool("CompactXML_Checked", CompactXML->Checked);
    IniFile->WriteString(SectionData, "HexText_Text", HexText->Text);
    IniFile->WriteString(SectionData, "JoseTextLines_Lines", JoseTextLines->Lines->CommaText);
    IniFile->WriteString(SectionData, "JsonInput_Lines", JsonInput->Lines->CommaText);
    WriteBool("JwkPrivate_Checked", JwkPrivate->Checked);
    WriteBool("LogJson_Checked", LogJson->Checked);
    IniFile->WriteInteger(SectionData, "NewPrivKey_ItemIndex", NewprivKey->ItemIndex);
    IniFile->WriteString(SectionData, "RawJWK_Lines", RawJWK->Lines->CommaText);
    WriteBool("ShowRawKey_Checked", ShowRawKey->Checked);
    IniFile->WriteString(SectionData, "TestHmacKey_Text", TestHmacKey->Text);
    IniFile->WriteInteger(SectionData, "TestJWSAlg_ItemIndex", TestJWSAlg->ItemIndex);
    IniFile->WriteString(SectionData, "TestPrivKeyFile_Text", TestPrivKeyFile->Text);
    IniFile->WriteString(SectionData, "URLText_Text", URLText->Text);
    WriteBool("SignECDSAIEEE_Checked", SignECDSAIEEE->Checked);
    IniFile->WriteString(SectionData, "CipherName_Text", CipherName->Text);
    IniFile->WriteString(SectionData, "CipherPwSalt_Text", CipherPwSalt->Text);
    IniFile->WriteString(SectionData, "CipherRawKey_Text", CipherRawKey->Text);
    IniFile->WriteString(SectionData, "CipherRawIV_Text", CipherRawIV->Text);
    IniFile->WriteString(SectionData, "CipherPassword_Text", CipherPassword->Text);
    IniFile->WriteInteger(SectionData, "EncKeyType_ItemIndex", EncKeyType->ItemIndex);
    IniFile->WriteString(SectionData, "CipherFileName_Text", CipherFileName->Text);
    IniFile->WriteString(SectionData, "CipherAEAD_Text", CipherAEAD->Text);
    IniFile->WriteInteger(SectionData, "CipherRandomSalt_State", static_cast<int>(CipherRandomSalt->State));
    WriteBool("CipherRandomSalt_Checked", CipherRandomSalt->Checked);
    IniFile->UpdateFile();
}

void __fastcall TJsonDemoForm::FormShow(TObject *Sender)
{
    if (!FInitialized) {
        FInitialized = true;
        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);

        auto ReadBool = [&IniFile](const String &Key) {
            return IniFile->ReadString(SectionData, Key, FalseText) == TrueText;
        };
        Base64Text->Text = IniFile->ReadString(SectionData, "Base64Text_Text", Base64Text->Text);
        CodeTextLines->Lines->CommaText = IniFile->ReadString(SectionData, "CodeTextLines_Lines",
                                                              CodeTextLines->Lines->CommaText);
        CompactXML->Checked = ReadBool("CompactXML_Checked");
        HexText->Text = IniFile->ReadString(SectionData, "HexText_Text", HexText->Text);
        JoseTextLines->Lines->CommaText = IniFile->ReadString(SectionData, "JoseTextLines_Lines",
                                                              JoseTextLines->Lines->CommaText);
        JsonInput->Lines->CommaText = IniFile->ReadString(SectionData, "JsonInput_Lines",
                                                          JsonInput->Lines->CommaText);
        LogJson->Checked = ReadBool("LogJson_Checked");
        JwkPrivate->Checked = ReadBool("JwkPrivate_Checked");
        NewprivKey->ItemIndex = IniFile->ReadInteger(SectionData, "NewPrivKey_ItemIndex", 0);
        RawJWK->Lines->CommaText = IniFile->ReadString(SectionData, "RawJWK_Lines", "");
        ShowRawKey->Checked = ReadBool("ShowRawKey_Checked");
        TestHmacKey->Text = IniFile->ReadString(SectionData, "TestHmacKey_Text", TestHmacKey->Text);
        TestJWSAlg->ItemIndex = IniFile->ReadInteger(SectionData, "TestJWSAlg_ItemIndex", 0);
        TestPrivKeyFile->Text = IniFile->ReadString(SectionData, "TestPrivKeyFile_Text", TestPrivKeyFile->Text);
        URLText->Text = IniFile->ReadString(SectionData, "URLText_Text", URLText->Text);
        SignECDSAIEEE->Checked = ReadBool("SignECDSAIEEE_Checked");
        CipherName->Text = IniFile->ReadString(SectionData, "CipherName_Text", CipherName->Text);
        CipherPwSalt->Text = IniFile->ReadString(SectionData, "CipherPwSalt_Text", CipherPwSalt->Text);
        CipherRawKey->Text = IniFile->ReadString(SectionData, "CipherRawKey_Text", CipherRawKey->Text);
        CipherRawIV->Text = IniFile->ReadString(SectionData, "CipherRawIV_Text", CipherRawIV->Text);
        CipherPassword->Text = IniFile->ReadString(SectionData, "CipherPassword_Text", CipherPassword->Text);
        EncKeyType->ItemIndex = IniFile->ReadInteger(SectionData, "EncKeyType_ItemIndex", EncKeyType->ItemIndex);
        CipherFileName->Text = IniFile->ReadString(SectionData, "CipherFileName_Text", CipherFileName->Text);
        CipherAEAD->Text = IniFile->ReadString(SectionData, "CipherAEAD_Text", CipherAEAD->Text);
        CipherRandomSalt->Checked = ReadBool("CipherRandomSalt_Checked");
    }
    LogWin->Lines->Add("SSL/TLS " + IcsReportOpenSSLVer(true));
    LogWin->Lines->Add("Built With " + IcsBuiltWithEx());
}

// Adds a line to the log; multi-line text is appended as a block.
void TJsonDemoForm::AddLog(const String &S)
{
    if (S.Pos(String(IcsLF)) > 0)
        LogWin->Lines->Text = LogWin->Lines->Text + S + IcsCRLF;
    else
        LogWin->Lines->Add(S);
}

void __fastcall TJsonDemoForm::doClearClick(TObject *Sender)
{
    LogWin->Lines->Clear();
}

void TJsonDemoForm::ClearPrivateKey()
{
    FPrivateKey->ClearAll();
    LabelPKey->Caption = "Private Key: ";
}

bool TJsonDemoForm::LoadPKeyFile(const String &FileName)
{
    if (!FileExists(FileName)) {
        AddLog("Can not find private key: " + FileName);
        return false;
    }
    try {
        ClearPrivateKey();
        FPrivateKey->PrivateKeyLoadFromPemFile(FileName, "");
    }
    catch (Exception &E) {
        AddLog("Failed to load private key file: " + FileName + " - " + E.Message);
        return false;
    }
    if (!FPrivateKey->IsPKeyLoaded) {
        AddLog("Failed to load private key: " + FileName);
        return false;
    }
    AddLog("Loaded private key file OK: " + FileName + " - " + FPrivateKey->PrivateKeyInfo);
    LabelPKey->Caption = "Private Key: " + FPrivateKey->PrivateKeyInfo;
    FPkeyFromFile = true;
    return true;
}

// A new key type was selected: clear the old key.
void __fastcall TJsonDemoForm::NewprivKeyClick(TObject *Sender)
{
    ClearPrivateKey();
}

// A new signing algorithm was selected: clear the old key, ready for a matching one.
void __fastcall TJsonDemoForm::TestJWSAlgoldChange(TObject *Sender)
{
    ClearPrivateKey();
}

void __fastcall TJsonDemoForm::TestPrivKeyFileChange(TObject *Sender)
{
    if (!FPkeyFromFile)
        FPrivateKey->ClearAll();
}

void __fastcall TJsonDemoForm::SelFileClick(TObject *Sender)
{
    OpenDlg->InitialDir = ExtractFilePath(TestPrivKeyFile->Text);
    OpenDlg->FileName = TestPrivKeyFile->Text;
    // Delphi exits here when the file name box is empty, so the browse button did nothing.
    if (OpenDlg->Execute()) {
        TestPrivKeyFile->Text = OpenDlg->FileName;
        FPrivateKey->ClearAll();
    }
}

void __fastcall TJsonDemoForm::doNewPrivKeyClick(TObject *Sender)
{
    FPkeyFromFile = false;
    const int KeyIndex = NewprivKey->ItemIndex;
    // Delphi casts ItemIndex -1 to a key type before its list lookup raises.
    if (KeyIndex < 0) {
        AddLog("Need to select a private key type, first");
        return;
    }
    doNewPrivKey->Enabled = false;
    try {
        ClearPrivateKey();
        FPrivateKey->PrivKeyType = static_cast<TSslPrivKeyType>(KeyIndex);   // after clearing the key
        AddLog("Generating private and public key pair, please wait: " + NewprivKey->Items->Strings[KeyIndex]);
        FPrivateKey->DoKeyPair();
        // The DER key, so its length can be reported.
        const TBytes DerPkeyTB = IcsBase64UrlDecodeTB(FPrivateKey->SavePKeyToText());
        const TBytes RawPrivTB = IcsEvpGetParamTB(FPrivateKey->PrivateKey, OSSL_PKEY_PARAM_PRIV_KEY);
        const TBytes RawPubTB = IcsEvpGetParamTB(FPrivateKey->PrivateKey, OSSL_PKEY_PARAM_PUB_KEY);
        AddLog("Generated private and public key pair OK: " + FPrivateKey->PrivateKeyInfo);
        AddLog("Private Key Can Sign Messages: " + IcsGetTrueFalse(IcsPkeyCansign(FPrivateKey->PrivateKey)));
        AddLog("TSslPrivKeyType: " +
               String(SslPrivKeyTypeLits[static_cast<int>(IcsGetPkeyType(FPrivateKey->PrivateKey))]));
        AddLog("Lengths: DER: " + IntToStr(DerPkeyTB.Length) + ", Raw Private Key: " + IntToStr(RawPrivTB.Length) +
               ", Raw Public Key: " + IntToStr(RawPubTB.Length));
        if (ShowRawKey->Checked)
            AddLog(FPrivateKey->GetPKeyRawText(false));
        AddLog("");
        LabelPKey->Caption = "Private Key: " + FPrivateKey->PrivateKeyInfo;
        doJWKCreate->Enabled = true;
    }
    catch (Exception &E) {
        AddLog(E.Message);
    }
    doNewPrivKey->Enabled = true;
}

void __fastcall TJsonDemoForm::doLoadKeyFileClick(TObject *Sender)
{
    if (LoadPKeyFile(TestPrivKeyFile->Text))
        doJWKCreate->Enabled = true;
}

// Reports the private key's parameters (OpenSSL 3.0 and later).
void __fastcall TJsonDemoForm::doKeyParamsClick(TObject *Sender)
{
    if (!FPrivateKey->PrivateKey) {
        AddLog("Need to create new private key or load old key file, first");
        return;
    }
    AddLog("IcsPkeyParamGettable: " + String(IcsCRLF) + IcsPkeyParamGettable(FPrivateKey->PrivateKey));
    try {
        AddLog(IcsPkeyParamsGetAll(FPrivateKey->PrivateKey));
    }
    catch (Exception &E) {
        AddLog("Error printing parameters: " + E.Message);
    }
}

void __fastcall TJsonDemoForm::JoseTextLinesDblClick(TObject *Sender)
{
    // Delphi binds this to three memos but always clears JoseTextLines; clear the one clicked.
    if (TMemo *Memo = dynamic_cast<TMemo *>(Sender))
        Memo->Lines->Clear();
}

void __fastcall TJsonDemoForm::CodeTextLinesDblClick(TObject *Sender)
{
    CodeTextLines->Lines->Clear();
}

void __fastcall TJsonDemoForm::doEncodeURLClick(TObject *Sender)
{
    AddLog(UrlEncode(Trim(CodeTextLines->Lines->Text)));
}

void __fastcall TJsonDemoForm::doDecodeURLClick(TObject *Sender)
{
    AddLog(UrlDecode(Trim(CodeTextLines->Lines->Text)));
    AddLog("");
}

void __fastcall TJsonDemoForm::doHashDigestClick(TObject *Sender)
{
    try {
        const AnsiString AnsiData = AnsiString(Trim(CodeTextLines->Lines->Text));
        const TBytes DataTB = IcsStringToTBytes(Trim(CodeTextLines->Lines->Text));
        const SHA1DigestString Sha1Digest = SHA1ofStr(AnsiData);
        // Delphi reports the length of the still-empty Digest here, always 0.
        AddLog("SHA1ofStr (len " + IntToStr(Sha1Digest.Length()) + ") Hex: " + IcsBufferToHex(Sha1Digest));

        AnsiString Digest = IcsHashDigest(AnsiData, Digest_sha1);
        AddLog("IcsHashDigest Sha1 (len " + IntToStr(Digest.Length()) + ") Hex: " + IcsBufferToHex(Digest));

        Digest = IcsHashDigest(AnsiData, Digest_sha256);
        AddLog("IcsHashDigest Sha256 (len " + IntToStr(Digest.Length()) + ") Hex: " + IcsBufferToHex(Digest));
        AddLog("IcsHashDigest Sha256 Base4Url: " + IcsBase64UrlEncode(String(Digest)));

        TBytes DigestTB = IcsHashDigestTB(DataTB, Digest_sha512);
        AddLog("IcsHashDigestTB Sha512 (len " + IntToStr(DigestTB.Length) + ") Hex: " + IcsTBToHex(DigestTB));
        AddLog("IcsHashDigestTB Sha512 Base4Url: " + IcsBase64UrlEncodeTB(DigestTB));

        Digest = IcsHashDigest(AnsiData, Digest_sha3_256);
        AddLog("IcsHashDigest Sha3-256 (len " + IntToStr(Digest.Length()) + ") Hex: " + IcsBufferToHex(Digest));
        AddLog("IcsHashDigest Sha3-256 Base4Url: " + IcsBase64UrlEncode(String(Digest)));

        DigestTB = IcsHashDigestTB(DataTB, Digest_sha3_512);
        AddLog("IcsHashDigestTB Sha3-512 (len " + IntToStr(DigestTB.Length) + ") Hex: " + IcsTBToHex(DigestTB));
        AddLog("IcsHashDigestTB Sha3-512 Base4Url: " + IcsBase64UrlEncodeTB(DigestTB));

        Digest = IcsHashDigest(AnsiData, Digest_shake128);
        AddLog("IcsHashDigest Shake128 (len " + IntToStr(Digest.Length()) + ") Hex: " + IcsBufferToHex(Digest));
        AddLog("IcsHashDigest Shake128 Base4Url: " + IcsBase64UrlEncode(String(Digest)));

        Digest = IcsHashDigest(AnsiData, Digest_shake256);
        AddLog("IcsHashDigest Shake256 (len " + IntToStr(Digest.Length()) + ") Hex: " + IcsBufferToHex(Digest));
        AddLog("IcsHashDigest Shake256 Base4Url: " + IcsBase64UrlEncode(String(Digest)));
    }
    catch (Exception &E) {
        AddLog("Hash digest exception - " + E.Message);
    }
    AddLog("");
}

void __fastcall TJsonDemoForm::doHexDecClick(TObject *Sender)
{
    const TBytes Bin = IcsHexToTB(AnsiString(Trim(HexText->Text)));
    AddLog("IcsHexToTB Decoded, and Encoded again: " + IcsTBToHex(Bin));
    AddLog("");
}

void __fastcall TJsonDemoForm::doHexEncodeClick(TObject *Sender)
{
    AddLog(IcsLowerCase(IcsBufferToHex(AnsiString(Trim(CodeTextLines->Lines->Text)))));
    AddLog("");
}

void __fastcall TJsonDemoForm::doTestUrlEncClick(TObject *Sender)
{
    const String Data = Trim(CodeTextLines->Lines->Text);
    AddLog("Data: " + Data);
    const AnsiString EncodedA = IcsBase64EncodeA(AnsiString(Data));
    Base64Text->Text = String(EncodedA);
    AddLog("IcsBase64Encode: " + String(EncodedA));
    const String Encoded = IcsBase64UrlEncode(Data);
    AddLog("IcsBase64UrlEncode: " + Encoded);
    const String Decoded = IcsBase64UrlDecode(Encoded);
    AddLog("IcsBase64UrlDecode: " + Decoded);
    if (Decoded != Data)
        AddLog("Encode/Decode Failed Comparison, InLen=" + IntToStr(Data.Length()) + ", DecLen=" +
               IntToStr(Decoded.Length()));
    AddLog("");
}

void __fastcall TJsonDemoForm::doB64URLEnClick(TObject *Sender)
{
    AddLog(IcsBase64UrlEncode(Trim(CodeTextLines->Lines->Text)));
    AddLog("");
}

void __fastcall TJsonDemoForm::doB64URLDecClick(TObject *Sender)
{
    AddLog(IcsBase64UrlDecode(Trim(Base64Text->Text)));
    AddLog("");
}

void __fastcall TJsonDemoForm::doBase64DecClick(TObject *Sender)
{
    const AnsiString ADecode = IcsBase64Decode(Trim(Base64Text->Text));
    AddLog("Base64: " + String(ADecode));
    AddLog("Base64(hex): " + IcsBufferToHex(ADecode));
    AddLog("");
}

void __fastcall TJsonDemoForm::doBase64EncClick(TObject *Sender)
{
    const AnsiString Data = AnsiString(Trim(CodeTextLines->Lines->Text));
    AnsiString Encoded = IcsBase64EncodeA(Data);
    Base64Text->Text = String(Encoded);
    AddLog("Base64: " + String(Encoded));
    const AnsiString ADecode = IcsBase64DecodeA(Encoded);
    AddLog("Decode Base64: " + String(ADecode));
    if (ADecode != Data)
        AddLog("Encode/Decode Failed Comparison, InLen=" + IntToStr(Data.Length()) + ", DecLen=" +
               IntToStr(ADecode.Length()));
    AddLog("");

    // The same again with the TBytes functions.
    const TBytes DataTB = IcsStringAToTBytes(Data);
    Encoded = IcsBase64EncodeTB(DataTB);
    AddLog("Base64TB: " + String(Encoded));
    const TBytes DecTB = IcsBase64DecodeTB(Encoded);
    AddLog("Decode Base64TB: " + IcsTBytesToString(DecTB));
    if (!IcsTBytesCompare(DecTB, DataTB))
        AddLog("Encode/DecodeTB Failed Comparison, InLen=" + IntToStr(Data.Length()) + ", DecLen=" +
               IntToStr(DecTB.Length));
    AddLog("");
}

void __fastcall TJsonDemoForm::doSignHmacClick(TObject *Sender)
{
    AddLog("Testing Various HMAC Digests with Different SHA keys");
    const AnsiString AnsiData = AnsiString(Trim(JoseTextLines->Lines->Text));
    const AnsiString AnsiSecret = AnsiString(TestHmacKey->Text);
    const TBytes DataTB = IcsStringToTBytes(Trim(JoseTextLines->Lines->Text));
    const TBytes SecretTB = IcsStringToTBytes(TestHmacKey->Text);
    AddLog("Test Data: " + String(AnsiData) + ", HMAC Key: " + String(AnsiSecret));
    try {
        AnsiString Digest = HMAC_SHA1_EX(AnsiData, AnsiSecret);
        AddLog("HMAC_SHA1_EX (len " + IntToStr(Digest.Length()) + ") Base64: " + String(IcsBase64EncodeA(Digest)));

        Digest = IcsHMACDigest(AnsiData, AnsiSecret, Digest_sha1);
        AddLog("IcsHMACDigest Sha1 (len " + IntToStr(Digest.Length()) + ") Base64: " + String(IcsBase64EncodeA(Digest)));

        Digest = IcsHMACDigest(AnsiData, AnsiSecret, Digest_sha256);
        AddLog("IcsHMACDigest Sha256 (len " + IntToStr(Digest.Length()) + ") Base64: " + String(IcsBase64EncodeA(Digest)));

        TBytes DigestTB = IcsHMACDigestTB(DataTB, SecretTB, Digest_sha512);
        AddLog("IcsHMACDigestTB Sha512 (len " + IntToStr(DigestTB.Length) + ") Base64: " +
               String(IcsBase64EncodeTB(DigestTB)));

        Digest = IcsHMACDigestEx(AnsiData, AnsiSecret, Digest_sha1);
        AddLog("IcsHMACDigestEx Sha1 (len " + IntToStr(Digest.Length()) + ") Base64: " + String(IcsBase64EncodeA(Digest)));

        DigestTB = IcsHMACDigestExTB(DataTB, SecretTB, Digest_sha256);
        // Delphi reports the length of the previous AnsiString digest here.
        AddLog("IcsHMACDigestExTB Sha256 (len " + IntToStr(DigestTB.Length) + ") Base64: " +
               String(IcsBase64EncodeTB(DigestTB)));

        Digest = IcsHMACDigestEx(AnsiData, AnsiSecret, Digest_sha512);
        AddLog("IcsHMACDigestEx Sha512 (len " + IntToStr(Digest.Length()) + ") Base64: " + String(IcsBase64EncodeA(Digest)));

        const bool Flag = IcsHMACDigestVerify(AnsiData, AnsiSecret, Digest, Digest_sha512);
        AddLog("IcsHMACDigestVerify (len " + IntToStr(Digest.Length()) + ") Sha512: " +
               (Flag ? "Passed Verify" : "Failed Verify"));
    }
    catch (Exception &E) {
        AddLog("Hash digest exception - " + E.Message);
    }
    AddLog("");
}

// Signs the data with the private key (made to match the selected algorithm when needed) and
// verifies the signature with the public key.
void __fastcall TJsonDemoForm::doTestSignClick(TObject *Sender)
{
    const TBytes DataTB = IcsStringToTBytes(Trim(JoseTextLines->Lines->Text));
    if (TestJWSAlg->ItemIndex <= 0) {
        AddLog("Need to select Key and Signing Hash Algorithm, First");
        return;
    }
    if (TestJWSAlg->ItemIndex >= static_cast<int>(JoseAlgDescLits.size())) {
        AddLog("Unknown Key and Signing Hash Algorithm");
        return;
    }
    const TJoseAlg JoseAlg = static_cast<TJoseAlg>(TestJWSAlg->ItemIndex);
    if (JoseAlg < jsigRsa256) {
        AddLog("Asymmetic MAC Hash Needs Private Key Hash Algorithm");
        return;
    }

    const TEvpDigest SignDigest = IcsJoseFindHash(JoseAlg);
    const TSslPrivKeyType AlgKeyType = JoseAlgPribvKeyTypes[static_cast<int>(JoseAlg)];
    try {
        // Check whether the loaded private key matches the algorithm.
        TSslPrivKeyType SignPkeyType = PrivKeyNone;
        String SignPKeyName;
        if (FPrivateKey->PrivateKey) {
            SignPkeyType = IcsGetPkeyType(FPrivateKey->PrivateKey);
            SignPKeyName = String(SslPKeyTypNames[static_cast<int>(SignPkeyType)]);
            if (SignPkeyType > PrivKeyNone && SignPkeyType != AlgKeyType) {
                AddLog("Old private key does not match signing Hash, cleared: " + SignPKeyName);
                SignPkeyType = PrivKeyNone;
            }
        }
        if (SignPkeyType == PrivKeyNone) {
            FPrivateKey->ClearAll();
            SignPkeyType = AlgKeyType;
            SignPKeyName = String(SslPKeyTypNames[static_cast<int>(SignPkeyType)]);
            FPrivateKey->PrivKeyType = SignPkeyType;
            LabelPKey->Caption = "Private Key: ";
            AddLog("Generating private and public key pair according to Signing Hash, please wait");
            FPrivateKey->DoKeyPair();
            AddLog("Generated private and public key pair OK: " + FPrivateKey->PrivateKeyInfo);
            LabelPKey->Caption = "Private Key: " + FPrivateKey->PrivateKeyInfo;
        }

        // Sign the data with the private key and an asymmetric keyed message authentication code.
        AddLog("Asymmetic MAC Hash Signing: " + String(JoseAlgDescLits[static_cast<int>(JoseAlg)]) + IcsCRLF +
               "Data: " + JoseTextLines->Lines->Text + IcsCRLF +
               "Private Key: " + FPrivateKey->PrivateKeyInfo + IcsCRLF +
               "Digest Type: " + String(EvpDigestLits[static_cast<int>(SignDigest)]));
        const TBytes SigTB = IcsAsymSignDigestTB(DataTB, FPrivateKey->PrivateKey, SignDigest, SignECDSAIEEE->Checked);
        if (SignPKeyName == "EC") {
            if (SignECDSAIEEE->Checked)
                AddLog("ECDA Digest IEEE P1363 Format");
            else
                AddLog("ECDA Digest ASN.1 Format");
        }
        AddLog("Digest Length (binary): " + IntToStr(SigTB.Length) + " bytes, Expected Lenght: " +
               IntToStr(SslPubKeyLen[static_cast<int>(IcsGetPkeyType(FPrivateKey->PrivateKey))]));
        AddLog("IcsAsymSignDigestTB IcsBase64UrlEncodeTB: " + IcsBase64UrlEncodeTB(SigTB));
        AddLog("IcsAsymSignDigestTB Hex: " + IcsLowerCase(IcsTBToHex(SigTB)));

        // The public key alone, from the private key, through its PEM text.
        FVerifyPKey->PublicKeyLoadFromText(FPrivateKey->PublicKeySaveToText());
        AddLog("Public key: " + FVerifyPKey->GetKeyDesc(FVerifyPKey->PrivateKey));

        // Verify the data with the public key.
        const bool Flag = IcsAsymVerifyDigestTB(DataTB, SigTB, FVerifyPKey->PrivateKey, SignDigest,
                                                SignECDSAIEEE->Checked);
        AddLog(String("IcsAsymVerifyDigest: ") + (Flag ? "Passed Verify" : "Failed Verify"));
    }
    catch (Exception &E) {
        AddLog("Signing exception - " + E.Message);
    }
    AddLog("");
}

// Builds a Json Web Key from the private key (or the HMAC secret), then gets the key back from
// the JWK and checks it matches.
void __fastcall TJsonDemoForm::doJWKCreateClick(TObject *Sender)
{
    RawJWK->Lines->Clear();
    try {
        const String Mykid = TimeToStr(Now());
        if (TestJWSAlg->ItemIndex <= 0) {
            AddLog("Need to select Key and Signing Hash Algorithm, First");
            return;
        }
        AddLog("Creating Json Web Key using Algorithm:: " + TestJWSAlg->Items->Strings[TestJWSAlg->ItemIndex]);
        const TJoseAlg JoseAlg = static_cast<TJoseAlg>(TestJWSAlg->ItemIndex);
        const bool UsesKey = JoseAlg >= jsigRsa256;
        if (UsesKey && !FPrivateKey->PrivateKey)
            doNewPrivKeyClick(this);

        // Create the Json Web Key.
        const String Myalg = IcsJoseFindAlg(JoseAlg, FPrivateKey->PrivateKey);
        String JwkPub;
        if (UsesKey) {
            AddLog("Private Key: " + FPrivateKey->PrivateKeyInfo);
            if (ShowRawKey->Checked)
                AddLog(FPrivateKey->GetPKeyRawText());
            JwkPub = IcsJoseJWKPubKey(FPrivateKey->PrivateKey, Myalg, Mykid, JoseLituse_sig, JwkPrivate->Checked);
            RawJWK->Lines->Text = JwkPub;
            AddLog("Ics Jose JWK Pkey Raw: " + JwkPub);
            _di_ISuperObject JwkJson = SO(JwkPub);
            const String Kty = JwkJson->S[JoseParamk_kty];
            if (Kty == JoseLitkty_RSA) {
                AddLog("Hex N: " + B64Url2Hex(JwkJson->S[String(JoseParamkr_n)]));
                AddLog("Hex E: " + B64Url2Hex(JwkJson->S[String(JoseParamkr_e)]));
                if (JwkPrivate->Checked)
                    AddLog("Hex D: " + B64Url2Hex(JwkJson->S[String(JoseParamkr_d)]));
            }
            if (Kty == JoseLitkty_EC) {
                AddLog("Base64 X: " + JwkJson->S[String(JoseParamke_x)]);
                AddLog("Hex X: " + B64Url2Hex(JwkJson->S[String(JoseParamke_x)]));
                AddLog("Hex Y: " + B64Url2Hex(JwkJson->S[String(JoseParamke_y)]));
                if (JwkPrivate->Checked)
                    AddLog("Hex D: " + B64Url2Hex(JwkJson->S[String(JoseParamke_d)]));
            }
            if (Kty == JoseLitkty_OKP) {
                AddLog("Hex Public: " + B64Url2Hex(JwkJson->S[String(JoseParamko_pub)]));
                if (JwkPrivate->Checked)
                    AddLog("Hex Private: " + B64Url2Hex(JwkJson->S[String(JoseParamko_priv)]));
            }
            if (Kty == JoseLitkty_AKP) {
                AddLog("Hex Public: " + B64Url2Hex(JwkJson->S[JoseParamka_pub]));
                if (JwkPrivate->Checked)
                    AddLog("Hex Private: " + B64Url2Hex(JwkJson->S[JoseParamka_priv]));
            }
        }
        else {
            AddLog("HMAC secret: " + TestHmacKey->Text);
            JwkPub = IcsJoseJWKHmac(TestHmacKey->Text, Myalg, Mykid, JoseLituse_sig);
            RawJWK->Lines->Text = JwkPub;
            AddLog("Ics Jose JWK HMAC Raw: " + JwkPub);
        }
        AddLog("");

        // Get the key back from the Json Web Key.
        if (UsesKey) {
            AddLog("Getting Private Key from JWK");
            String NewKeyid;
            FVerifyPKey->PrivateKey = IcsJoseJWKGetPKey(JwkPub, NewKeyid);
            if (FVerifyPKey->PrivateKey) {
                AddLog("Private key from JWK: " + FVerifyPKey->PrivateKeyInfo);
                if (ShowRawKey->Checked)
                    AddLog(FVerifyPKey->GetPKeyRawText(!JwkPrivate->Checked));
                try {
                    if (FPrivateKey->ComparePkey(FVerifyPKey.get()))
                        AddLog("JWK key matches original private key");
                    else
                        AddLog("Error, JWK key does not match private key");
                }
                catch (Exception &E) {
                    AddLog("Failed to compare keys - " + E.Message);
                }
            }
            else
                AddLog("No JWK public key found");
        }
        else {
            AddLog("Getting HMAC secret from JWK");
            const String NewSecret = IcsJoseJWKGetHmac(JwkPub);
            if (NewSecret == TestHmacKey->Text)
                AddLog("JWK HMAC secret matches original HMAC secret, result " + NewSecret);
            else
                AddLog("Error, JWK HMAC secret does not match HMAC secret, result " + NewSecret);
        }
    }
    catch (Exception &E) {
        AddLog("Json Web Key exception - " + E.Message);
    }
    AddLog("");
}

// Gets the public key or HMAC secret from an existing Json Web Key (as Json).
void __fastcall TJsonDemoForm::doJKWReadClick(TObject *Sender)
{
    const String RawText = RawJWK->Lines->Text;
    AddLog("Ics Jose JWK Pkey Raw: " + RawText);
    try {
        _di_ISuperObject JwkJson = SO(RawText);
        const TJoseAlg JoseAlg = IcsJoseFindAlgType(JwkJson->S[JoseParamk_alg]);
        const String KeyType = JwkJson->S[JoseParamk_kty];
        if (JoseAlg >= jsigRsa256 || KeyType != JoseLitkty_oct) {
            AddLog("Getting public key from JWK");
            String NewKeyid;
            FVerifyPKey->PrivateKey = IcsJoseJWKGetPKey(RawText, NewKeyid);
            if (FVerifyPKey->PrivateKey) {
                if (ShowRawKey->Checked)
                    AddLog(FVerifyPKey->GetPKeyRawText(true));
                AddLog("JWK public key found: " + FVerifyPKey->PrivateKeyInfo);
            }
            else
                AddLog("No JWK public key found");
        }
        else {
            AddLog("Getting HMAC secret from JWK");
            const String NewSecret = IcsJoseJWKGetHmac(RawText);
            if (!NewSecret.IsEmpty())
                AddLog("JWK HMAC secret found: " + NewSecret);
            else
                AddLog("Error, JWK HMAC secret not found");
        }
    }
    catch (Exception &E) {
        AddLog("Json Web Key exception - " + E.Message);
    }
    AddLog("");
}

// Creates a Json Web Key, signs the data with it as Json Web Signatures (one carrying the JWK,
// one the key id), then checks both signatures.
void __fastcall TJsonDemoForm::doJWSCreateClick(TObject *Sender)
{
    const String Data = Trim(JoseTextLines->Lines->Text);
    // Delphi reads Items[ItemIndex] before this test (raising with no selection) and exits silently.
    if (TestJWSAlg->ItemIndex < 1) {
        AddLog("Need to select Key and Signing Hash Algorithm, First");
        return;
    }
    AddLog("Create Json Web Key, sign data with JWK, check signature");
    AddLog("Building Json Web Key using Algorithm: " + TestJWSAlg->Items->Strings[TestJWSAlg->ItemIndex]);
    AddLog("Data: " + Data);
    const String Mykid = "1234";   // ideally a random number

    // Checks one signature; a signature that carries only the key id is checked again with the
    // public key saved from the first one. Captures the form and Mykid by reference.
    auto VerifyJWS = [this, &Mykid](const String &MyJWS) {
        String NewKeyid = Mykid;
        String Payload;
        void *NewPubKey = FVerifyPKey->PrivateKey;
        AddLog("CheckJWS: " + MyJWS);
        TJoseVerify VerifyRes = IcsJoseCheckJWS(MyJWS, "", TestHmacKey->Text, NewPubKey, NewKeyid, Payload,
                                                SignECDSAIEEE->Checked);
        AddLog("Verify Result: " + JoseVerifyLits[static_cast<int>(VerifyRes)]);
        if (VerifyRes == JVerifyOkNewPubKey) {
            FVerifyPKey->PrivateKey = NewPubKey;   // keep the new public key
            AddLog("Saved JWK: " + Trim(FVerifyPKey->PublicKeySaveToText()));
        }
        if (VerifyRes == JVerifyNeedPubKey && NewKeyid == Mykid) {
            AddLog("Repeating with saved public key for KID: " + NewKeyid);
            NewPubKey = FVerifyPKey->PrivateKey;
            VerifyRes = IcsJoseCheckJWS(MyJWS, "", "", NewPubKey, NewKeyid, Payload, SignECDSAIEEE->Checked);
            AddLog("Verify Result: " + JoseVerifyLits[static_cast<int>(VerifyRes)]);
        }
        if (!Payload.IsEmpty())
            AddLog(Payload);
        AddLog("");
    };

    try {
        const TJoseAlg JoseAlg = static_cast<TJoseAlg>(TestJWSAlg->ItemIndex);
        const bool UsesKey = JoseAlg >= jsigRsa256;
        if (UsesKey) {
            if (!FPrivateKey->PrivateKey) {
                AddLog("Need to create new private key or load old key file, first");
                return;
            }
            if (!IcsPkeyCansign(FPrivateKey->PrivateKey)) {
                AddLog("Private key can not sign messages: " + IcsPkeyTypeName(FPrivateKey->PrivateKey));
                return;
            }
            // Keep the public key, to verify without the JWK.
            FVerifyPKey->PublicKeyLoadFromText(FPrivateKey->PublicKeySaveToText());
            AddLog("Public Key: " + FPrivateKey->PublicKeySaveToText());
            AddLog("");
        }

        // Build the Json Web Key.
        const String Myalg = IcsJoseFindAlg(JoseAlg, FPrivateKey->PrivateKey);
        String KwkPub;
        _di_ISuperObject RespJson;
        if (UsesKey) {
            if (ShowRawKey->Checked)
                AddLog(FPrivateKey->GetPKeyRawText());
            KwkPub = IcsJoseJWKPubKey(FPrivateKey->PrivateKey, Myalg, Mykid, JoseLituse_sig);
            AddLog("IcsJoseJWKPkey Raw: " + KwkPub);
            AddLog("");
        }
        else {
            AddLog("HMAC secret: " + TestHmacKey->Text);
            KwkPub = IcsJoseJWKHmac(TestHmacKey->Text, Myalg, Mykid, JoseLituse_sig);
            RespJson = SO(KwkPub);
            AddLog("IcsJoseJWK HMac: " + RespJson->AsJSon(true, false));
        }
        AddLog("");

        // The Json header, unsigned.
        const String Header = String(IcsJoseHeader(Myalg, "jws", KwkPub, "", "Nonce"));
        AddLog("IcsJoseHeader Raw: " + Header);
        AddLog("");
        RespJson = SO(Header);
        AddLog("IcsJoseHeader: " + RespJson->AsJSon(true, false));
        AddLog("");

        // Json Web Signatures in Json format, carrying the JWK or the key id.
        AddLog("Building Json Web Signatures, sending JWK or KID");
        const String JsonJWSKey = IcsJoseJWSJson(JoseAlg, Data, TestHmacKey->Text, FPrivateKey->PrivateKey, "jws",
                                                 KwkPub, "", "103", "", SignECDSAIEEE->Checked);
        RawJWK->Text = JsonJWSKey;
        RespJson = SO(JsonJWSKey);
        AddLog("IcsJoseJWS Json JWK: " + RespJson->AsJSon(true, false));

        const String JsonJWSKid = IcsJoseJWSJson(JoseAlg, Data, TestHmacKey->Text, FPrivateKey->PrivateKey, "jws",
                                                 "", Mykid, "104", "", SignECDSAIEEE->Checked);
        RespJson = SO(JsonJWSKid);
        AddLog("IcsJoseJWS Json KID: " + RespJson->AsJSon(true, false));
        AddLog("");

        AddLog("Verifying Json Web Signature - Json JWS with new key");
        VerifyJWS(JsonJWSKey);
        AddLog("Verifying Json Web Signature - Json JWS with KID");
        VerifyJWS(JsonJWSKid);
    }
    catch (Exception &E) {
        AddLog("Json Web Signature exception - " + E.Message);
    }
    AddLog("");
}

void __fastcall TJsonDemoForm::doJWSReadClick(TObject *Sender)
{
    String NewKeyid, Payload;
    void *NewPubKey = FVerifyPKey->PrivateKey;
    AddLog("CheckJWS: " + RawJWK->Text);
    const TJoseVerify VerifyRes = IcsJoseCheckJWS(RawJWK->Text, "", TestHmacKey->Text, NewPubKey, NewKeyid, Payload,
                                                  SignECDSAIEEE->Checked);
    AddLog("Verify Result: " + JoseVerifyLits[static_cast<int>(VerifyRes)]);
    if (VerifyRes == JVerifyOkNewPubKey) {
        FVerifyPKey->PrivateKey = NewPubKey;   // keep the new public key
        AddLog("Saved JWK: " + Trim(FVerifyPKey->PublicKeySaveToText()));
    }
    if (!Payload.IsEmpty())
        AddLog(Payload);
    AddLog("");
}

void __fastcall TJsonDemoForm::doLoadFileClick(TObject *Sender)
{
    if (OpenDlg->Execute())
        JsonInput->Lines->LoadFromFile(OpenDlg->FileName);
}

// Parses the Json or XML text into the grid; bound to both the Parse Json and Parse XML buttons.
void __fastcall TJsonDemoForm::doParseClick(TObject *Sender)
{
    _di_ISuperObject RespObj;
    JsonGrid->Items->Clear();
    String SourceTxt = JsonInput->Lines->Text;

    if (SourceTxt.Pos("{") > 0 || SourceTxt.Pos("[") > 0 || Sender == doParseJson) {
        try {
            String ErrStr;
            RespObj = TSuperObject::ParseStringEx(SourceTxt.c_str(), true, ErrStr);
            if (!ErrStr.IsEmpty())
                AddLog("Error parsing Json: " + ErrStr);
        }
        catch (Exception &E) {
            AddLog("Error parsing Json: " + E.Message);
        }
        if (RespObj)
            AddLog("Json main content type: " + SuperTypeName(RespObj->DataType));
    }

    if (SourceTxt.Pos("<?xml version=") > 0 || Sender == doParseXML) {
        try {
            RespObj = XMLParseString(SourceTxt, CompactXML->Checked);
        }
        catch (Exception &E) {
            AddLog("Error parsing XML: " + E.Message);
        }
    }
    if (!RespObj)
        return;   // nothing to show

    if (LogJson->Checked)
        AddLog(RespObj->AsJSon(true, false));   // formatted
    try {
        JsonToListView(JsonGrid, RespObj, FJArrayItems);
    }
    catch (Exception &E) {
        AddLog("Error parsing Json: " + E.Message);
    }
}

// Opens the selected array element, or the first nested object or array in the row, in the
// Json Object window.
void __fastcall TJsonDemoForm::JsonGridDblClick(TObject *Sender)
{
    if (!FormObject)
        return;
    const int Index = JsonGrid->ItemIndex;
    if (Index < 0)
        return;
    FormObject->SubJsonGrid->Items->Clear();
    if (!FJArrayItems.empty()) {
        if (Index < static_cast<int>(FJArrayItems.size()))
            FormObject->DispJson(FJArrayItems[Index]);
        return;
    }
    TListItem *Item = JsonGrid->Items->Item[Index];
    if (Item->SubItems->Count >= 2 &&
        (Item->SubItems->Strings[0] == "stArray" || Item->SubItems->Strings[0] == "stObject"))
        FormObject->DispJson(Item->SubItems->Strings[1]);
    else if (StartsJson(Item->Caption))
        FormObject->DispJson(Item->Caption);
    else {
        // An array row may hold Json in any column; only the first is shown.
        for (int I = 0; I < Item->SubItems->Count; I++) {
            if (StartsJson(Item->SubItems->Strings[I])) {
                FormObject->DispJson(Item->SubItems->Strings[I]);
                break;
            }
        }
    }
    FormObject->BringToFront();
}

// Encrypts the Jose text with the public key and decrypts it with the private key.
void TJsonDemoForm::RsaEncryptDecrypt(TX509Base *PubKey)
{
    PubKey->PublicKeyLoadFromText(FPrivateKey->PublicKeySaveToText());   // just the public key
    // Encrypt with the public key; the result is Base64 encoded. Passes the UTF-8 bytes unchanged.
    const AnsiString EncStrNew = IcsJoseStrEncPKey(PubKey->PrivateKey, Utf8Bytes(Trim(JoseTextLines->Text)), true);
    AddLog("RSA 3.0 encrypted with Private Key and Base64 encoded: " + String(EncStrNew));
    // Decrypt the Base64 with the private key.
    const String DecStrNew = FromUtf8Bytes(IcsJoseStrDecPkey(FPrivateKey->PrivateKey, EncStrNew, true));
    AddLog("RSA 3.0 decrypted text again: " + DecStrNew);
    AddLog("");
}

// Generates a new RSA key, then encrypts and decrypts (sample moved from PemTool).
void __fastcall TJsonDemoForm::doEncryptPkeyClick(TObject *Sender)
{
    try {
        std::unique_ptr<TX509Base> PubKey(new TX509Base(nullptr, nullptr));
        ClearPrivateKey();
        FPrivateKey->PrivKeyType = PrivKeyRsa2048;
        AddLog("Generating private and public key pair, please wait");
        FPrivateKey->DoKeyPair();
        AddLog("Generated private and public key pair OK: " + FPrivateKey->PrivateKeyInfo);
        LabelPKey->Caption = "Private Key: " + FPrivateKey->PrivateKeyInfo;
        AddLog("Data to Encrypt: " + Trim(JoseTextLines->Lines->Text) + IcsCRLF +
               "Private Key: " + FPrivateKey->PrivateKeyInfo);
        // The Delphi legacy StrEncRsa comparison is compiled only with OpenSSL_Deprecated, which the C++ headers lack.
        RsaEncryptDecrypt(PubKey.get());
    }
    catch (Exception &E) {
        AddLog("RSA Encryption exception - " + E.Message);
    }
}

// Encrypts and decrypts with the key already created or loaded.
void __fastcall TJsonDemoForm::doEncryptPkeyExtClick(TObject *Sender)
{
    try {
        if (!FPrivateKey->PrivateKey) {
            AddLog("Need to create new private key or load old key file, first");
            return;
        }
        std::unique_ptr<TX509Base> PubKey(new TX509Base(nullptr, nullptr));
        LabelPKey->Caption = "Private Key: " + FPrivateKey->PrivateKeyInfo;
        AddLog("Data to Encrypt: " + Trim(JoseTextLines->Lines->Text) + IcsCRLF +
               "Private Key: " + FPrivateKey->PrivateKeyInfo);
        RsaEncryptDecrypt(PubKey.get());
    }
    catch (Exception &E) {
        AddLog("RSA Encryption exception - " + E.Message);
    }
}

// Legacy Blowfish password samples (moved from PemTool). The Delphi bodies are compiled only
// with OpenSSL_Deprecated, which the ICS default build leaves off, so both buttons do nothing.
void __fastcall TJsonDemoForm::doEncryprPWTextClick(TObject *Sender)
{
    // Delphi gives no message; the C++ says why nothing happens.
    AddLog("Legacy Blowfish samples need ICS built with OpenSSL_Deprecated; use the TIcsSymCrypto buttons");
}

void __fastcall TJsonDemoForm::doEncryprPWFileClick(TObject *Sender)
{
    // Delphi gives no message; the C++ says why nothing happens.
    AddLog("Legacy Blowfish samples need ICS built with OpenSSL_Deprecated; use the TIcsSymCrypto buttons");
}

// Lists every cipher OpenSSL offers (TIcsSymCrypto).
void TJsonDemoForm::GetCiphers()
{
    int Tot = 0;
    try {
        Tot = FSymCrypto->GetAllCiphers();   // the total; not all of them have names in the list
    }
    catch (Exception &E) {
        AddLog("Cipher exception - " + E.Message);
    }
    if (Tot > 0) {
        AddLog("Total ciphers found: " + IntToStr(Tot));
        ListCiphers->Items->Assign(FSymCrypto->AllCiphers);
    }
}

void TJsonDemoForm::ReportCipherInfo()
{
    String S = "Cipher Name:" + FSymCrypto->ReqName + IcsCRLF +
               "Raw Key Length: " + IntToStr(FSymCrypto->ReqKeyLength) + IcsCRLF +
               "Init Vector Length: " + IntToStr(FSymCrypto->ReqIVectorLen) + IcsCRLF +
               "Block Size: " + IntToStr(FSymCrypto->ReqBlockSize) + IcsCRLF +
               "Cipher Mode: " + FSymCrypto->ModeAsString() + IcsCRLF;
    S += FSymCrypto->FlagsAsString() + IcsCRLF;
    LabelCipher->Caption = S;
}

void __fastcall TJsonDemoForm::SelCiphFileClick(TObject *Sender)
{
    OpenDlg->InitialDir = ExtractFilePath(CipherFileName->Text);
    OpenDlg->FileName = CipherFileName->Text;
    if (OpenDlg->InitialDir.IsEmpty()) {
        OpenDlg->InitialDir = "c:\\";
        OpenDlg->FileName = "*.*";
    }
    if (OpenDlg->Execute())
        CipherFileName->Text = OpenDlg->FileName;
}

// A cipher from the list OpenSSL built.
void __fastcall TJsonDemoForm::ListCiphersClick(TObject *Sender)
{
    if (ListCiphers->ItemIndex < 0)
        return;
    CipherName->Text = FSymCrypto->AllCiphers->Strings[ListCiphers->ItemIndex];
    CipherNameClick(this);
}

// Blowfish key bits changed: update the cipher details.
void __fastcall TJsonDemoForm::CipherBFBitsClick(TObject *Sender)
{
    CipherNameClick(this);
}

// A cipher from the ICS TEvpCipher list.
void __fastcall TJsonDemoForm::CipherICsTypesClick(TObject *Sender)
{
    if (CipherICsTypes->ItemIndex < 0)
        return;
    FSymCrypto->EvpCipher = static_cast<TEvpCipher>(CipherICsTypes->ItemIndex);
    FSymCrypto->CipherBFLen = static_cast<TCipherBFLen>(CipherBFBits->ItemIndex);   // only used by Blowfish
    ReportCipherInfo();
    CipherName->Text = FSymCrypto->ReqName;
}

// A cipher by its OpenSSL name.
void __fastcall TJsonDemoForm::CipherNameClick(TObject *Sender)
{
    if (CipherName->Text.IsEmpty())
        return;
    FSymCrypto->CipherName = CipherName->Text;
    FSymCrypto->CipherBFLen = static_cast<TCipherBFLen>(CipherBFBits->ItemIndex);   // only used by Blowfish
    ReportCipherInfo();
}

// Sets and checks the encryption parameters.
void __fastcall TJsonDemoForm::doCheckCryptClick(TObject *Sender)
{
    try {
        CipherNameClick(this);
        FSymCrypto->CKeyDerMethod = static_cast<TCKeyDerMethod>(EncKeyType->ItemIndex);
        if (FSymCrypto->CKeyDerMethod == cktRaw) {
            FSymCrypto->CipherKey = IcsStringToTBytes(Trim(CipherRawKey->Text));
            FSymCrypto->IVector = IcsStringToTBytes(Trim(CipherRawIV->Text));
        }
        else
            FSymCrypto->Password = Trim(CipherPassword->Text);   // not used by cktRaw
        // Delphi also sets the trimmed salt for cktRaw, then overwrites it with this one.
        FSymCrypto->PwSalt = IcsStringToTBytes(CipherPwSalt->Text);   // 8 bytes or blank
        FSymCrypto->AddRandomSalt = CipherRandomSalt->Checked;   // adds a random 8 byte salt to the output, or reads it
        if (!FSymCrypto->InitEncrypt())
            AddLog("Initialise failed: " + FSymCrypto->LastError);
        else {
            AddLog("Initialised OK for: " + FSymCrypto->ReqName);
            AddLog("Key Derivation: " + EncKeyType->Items->Strings[EncKeyType->ItemIndex]);
            AddLog("Raw Cipher Key: " + IcsTBToHex(FSymCrypto->CipherKey));
            AddLog("Raw IVector: " + IcsTBToHex(FSymCrypto->IVector));
            if (FSymCrypto->AddRandomSalt)
                AddLog("Random PW Salt");
            else
                AddLog("Raw PW Salt: " + IcsTBToHex(FSymCrypto->PwSalt));
        }
    }
    catch (Exception &E) {
        AddLog("Initialisation failed - " + E.Message);
    }
}

// TIcsSymCrypto: encrypts and decrypts a string with the current settings.
void __fastcall TJsonDemoForm::doEncryptTextClick(TObject *Sender)
{
    doCheckCryptClick(this);
    if (!FSymCrypto->IsInitialised())
        return;
    CryptTextOut->Lines->Clear();
    const String OrigStr = Trim(CryptTextIn->Text);
    AddLog("String to Encrypt: " + OrigStr);
    try {
        const TBytes Encrypted = FSymCrypto->StrEncrypt(OrigStr);
        if (FSymCrypto->ReqAEADMode) {
            CipherAEAD->Text = IcsTBToHex(FSymCrypto->AEADTag);   // the AEAD tag made during encryption
            AddLog("Authenticated-Encryption with Associated-Data (AEAD): " + CipherAEAD->Text);
        }
        const String EncStr = String(IcsBase64EncodeTB(Encrypted));
        AddLog("B64 Encrypted: " + EncStr);
        CryptTextOut->Lines->Text = IcsStrBeakup(EncStr, 32);
        AddLog("Orignal Len=" + IntToStr(OrigStr.Length()) + ", Encrypted Len=" + IntToStr(Encrypted.Length));
        const String DecStr = FSymCrypto->StrDecrypt(Encrypted);
        AddLog("String Decrypted: " + DecStr);
        if (DecStr == OrigStr)
            AddLog("Encrypt and Decrypt Matched OK");
        else
            AddLog("Encrypt and Decrypt Failed");
    }
    catch (Exception &E) {
        AddLog("Encryption exception - " + E.Message);
    }
    AddLog("");
}

// TIcsSymCrypto: decrypts the Base64 text in the output box.
void __fastcall TJsonDemoForm::doDecryptTextClick(TObject *Sender)
{
    doCheckCryptClick(this);
    if (!FSymCrypto->IsInitialised())
        return;
    // Delphi decodes the tag as Base64, but Encrypt Text stores it as hex.
    if (FSymCrypto->ReqAEADMode)
        FSymCrypto->AEADTag = IcsHexToTB(AnsiString(CipherAEAD->Text));   // needed to decrypt AEAD
    AddLog("String to Decrypt: " + CryptTextOut->Lines->Text);
    try {
        const TBytes Encrypted = IcsBase64DecodeTB(AnsiString(CryptTextOut->Lines->Text));
        AddLog("String Decrypted: " + FSymCrypto->StrDecrypt(Encrypted));
    }
    catch (Exception &E) {
        AddLog("Decryption exception - " + E.Message);
    }
    AddLog("");
}

// Two standalone functions, with the cipher and key method left at their defaults.
void __fastcall TJsonDemoForm::doQuickEncryptClick(TObject *Sender)
{
    CryptTextOut->Lines->Clear();
    const String OrigStr = Trim(CryptTextIn->Text);
    AddLog("String to Encrypt: " + OrigStr);
    try {
        const TBytes Encrypted = IcsJoseStrEncrypt(OrigStr, Trim(CipherPassword->Text));
        const String EncStr = String(IcsBase64EncodeTB(Encrypted));
        AddLog("B64 Encrypted: " + EncStr);
        CryptTextOut->Lines->Text = IcsStrBeakup(EncStr, 32);
        const String DecStr = IcsJoseStrDecrypt(Encrypted, Trim(CipherPassword->Text));
        AddLog("String Decrypted: " + DecStr);
        if (DecStr == OrigStr)
            AddLog("Encrypt and Decrypt Matched OK");
        else
            AddLog("Encrypt and Decrypt Failed");
    }
    catch (Exception &E) {
        AddLog("Encryption exception - " + E.Message);
    }
    AddLog("");
}

// TIcsSymCrypto on streams: the text as UTF-8 in a memory stream, encrypted and decrypted.
void __fastcall TJsonDemoForm::doEncryptStreamClick(TObject *Sender)
{
    doCheckCryptClick(this);
    if (!FSymCrypto->IsInitialised())
        return;
    CryptTextOut->Lines->Clear();
    const String OrigStr = Trim(CryptTextIn->Text);
    AddLog("String to Encrypt as a Stream: " + OrigStr);
    try {
        std::unique_ptr<TMemoryStream> SrcStream(new TMemoryStream);
        std::unique_ptr<TMemoryStream> DestStream(new TMemoryStream);
        // Delphi writes Length(OrigStr) bytes, the character count, which cuts off non-ASCII UTF-8 text.
        const TBytes Utf8 = IcsStringToTBytes(OrigStr, CP_UTF8);
        if (Utf8.Length > 0)
            SrcStream->WriteBuffer(&Utf8[0], static_cast<NativeInt>(Utf8.Length));
        FSymCrypto->StreamEncrypt(SrcStream.get(), DestStream.get(), 2048);

        // The cipher text, for display.
        TBytes Encrypted;
        Encrypted.Length = static_cast<int>(DestStream->Size);
        DestStream->Position = 0;
        if (Encrypted.Length > 0)
            DestStream->Read(&Encrypted[0], Encrypted.Length);
        const String EncStr = String(IcsBase64EncodeTB(Encrypted));
        AddLog("B64 Encrypted Stream: " + EncStr);
        CryptTextOut->Lines->Text = IcsStrBeakup(EncStr, 32);

        SrcStream->Size = 0;
        FSymCrypto->StreamDecrypt(DestStream.get(), SrcStream.get(), 2048);

        TBytes Decrypted;
        Decrypted.Length = static_cast<int>(SrcStream->Size);
        SrcStream->Position = 0;
        if (Decrypted.Length > 0)
            SrcStream->Read(&Decrypted[0], Decrypted.Length);
        const String DecStr = IcsTBytesToString(Decrypted, 0, CP_UTF8);
        AddLog("Stream Decrypted: " + DecStr);
        if (DecStr == OrigStr)
            AddLog("Encrypt and Decrypt Matched OK");
        else
            AddLog("Encrypt and Decrypt Failed");
    }
    catch (Exception &E) {
        AddLog("Encryption exception - " + E.Message);
    }
    AddLog("");
}

// TIcsSymCrypto on files: writes <file>_encrypted and <file>_decrypted and compares the result.
void __fastcall TJsonDemoForm::doEncryptFileClick(TObject *Sender)
{
    doCheckCryptClick(this);
    if (!FSymCrypto->IsInitialised())
        return;
    const String OrigFName = CipherFileName->Text;
    if (!FileExists(OrigFName)) {
        AddLog("File not found: " + OrigFName);
        return;
    }
    const String NewFName = OrigFName + "_encrypted";
    const String DecFName = OrigFName + "_decrypted";
    if (FileExists(NewFName))
        IcsDeleteFile(NewFName, true);
    if (FileExists(DecFName))
        IcsDeleteFile(DecFName, true);
    try {
        // Owned here, so a failure opening the second or third file closes the first.
        std::unique_ptr<TFileStream> SrcStream(new TFileStream(OrigFName, fmOpenRead));
        std::unique_ptr<TFileStream> DestStream(new TFileStream(NewFName, fmCreate));
        std::unique_ptr<TFileStream> DecrypStream(new TFileStream(DecFName, fmCreate));   // only to test decryption
        AddLog("Creating encrypted file: " + NewFName);
        // Delphi holds the sizes in Integer, so a file over 2 GB is read and compared wrongly.
        const __int64 OrigLen = SrcStream->Size;
        AddLog("Original file length: " + IcsInt64ToCStr(OrigLen));
        const bool CanCompare = OrigLen <= MaxFileCompareSize;
        TBytes RawStreamTB;
        if (CanCompare) {
            RawStreamTB.Length = static_cast<int>(OrigLen);
            SrcStream->Position = 0;
            if (OrigLen > 0)
                SrcStream->Read(&RawStreamTB[0], static_cast<int>(OrigLen));
        }

        FSymCrypto->StreamEncrypt(SrcStream.get(), DestStream.get(), 2048);
        AddLog("Encrypted file length: " + IcsInt64ToCStr(DestStream->Size));

        FSymCrypto->StreamDecrypt(DestStream.get(), DecrypStream.get(), 2048);

        // Check the input and output are the same.
        const __int64 NewLen = DecrypStream->Size;
        AddLog("Decrypted file as: " + DecFName + ", file length: " + IcsInt64ToCStr(NewLen));
        if (OrigLen != NewLen) {
            AddLog("Decrypted file different size: " + IcsInt64ToCStr(NewLen));
            return;
        }
        if (!CanCompare) {
            AddLog("Sizes match; content not compared, the file is over " + IcsInt64ToCStr(MaxFileCompareSize) + " bytes");
            return;
        }
        TBytes DecryptedTB;
        DecryptedTB.Length = static_cast<int>(NewLen);
        DecrypStream->Position = 0;
        if (NewLen > 0)
            DecrypStream->Read(&DecryptedTB[0], static_cast<int>(NewLen));
        if (IcsTBytesCompare(RawStreamTB, DecryptedTB))
            AddLog("Encrypt and Decrypt Matched OK");
        else
            AddLog("Encrypt and Decrypt Failed");
    }
    catch (Exception &E) {
        AddLog("Encryption exception - " + E.Message);
    }
    AddLog("");
}
