/// Framework Core High-Level Public-Key Elliptic-Curve Cryptography
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.crypt.ecc;

{
  *****************************************************************************

   Certificate-based Public Key Cryptography Classes
    - High-Level Certificate-based Public Key Cryptography
    - IProtocol Implemented using our Public Key Cryptography
    - Registration of our ECC Engine to the TCryptAsym/TCryptCert Factories

  *****************************************************************************

   Legal Notice: as stated by our LICENSE.md terms, make sure that you comply
   to any restriction about the use of cryptographic software in your country.
}

interface

{$I ..\mormot.defines.inc}

uses
  classes,
  sysutils,
  mormot.core.base,
  mormot.core.os,
  mormot.core.unicode,
  mormot.core.text,
  mormot.core.buffers,
  mormot.core.data,
  mormot.core.datetime,
  mormot.core.variants,
  mormot.core.json,
  mormot.core.rtti,
  mormot.core.search, // for EccKeyFileFind()
  mormot.crypt.core,
  mormot.crypt.secure,
  mormot.crypt.ecc256r1;



{ ***************** High-Level Certificate-based Public Key Cryptography }

type
  /// the known algorithms implemented in ECIES encryption
  // - supports AES 256-bit encryption with safe block modes (weack ECB mode
  // is not available) - or AES 128-bit if needed (e.g. for regulatory issues)
  // - safe HMAC SHA-256 is used as MAC algorithm, or AES-GCM
  // - optional SynLZ compression can be enabled
  TEciesAlgo = (
    ecaUnknown,
    ecaPBKDF2_HMAC_SHA256_AES256_CFB,
    ecaPBKDF2_HMAC_SHA256_AES256_CBC,
    ecaPBKDF2_HMAC_SHA256_AES256_OFB,
    ecaPBKDF2_HMAC_SHA256_AES256_CTR,
    ecaPBKDF2_HMAC_SHA256_AES256_CFB_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES256_CBC_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES256_OFB_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES256_CTR_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES128_CFB_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES128_CBC_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES128_OFB_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES128_CTR_SYNLZ,
    ecaPBKDF2_HMAC_SHA256_AES128_CFB,
    ecaPBKDF2_HMAC_SHA256_AES128_CBC,
    ecaPBKDF2_HMAC_SHA256_AES128_OFB,
    ecaPBKDF2_HMAC_SHA256_AES128_CTR,
    ecaPBKDF2_AES128_GCM,
    ecaPBKDF2_AES256_GCM,
    ecaPBKDF2_AES128_GCM_SYNLZ,
    ecaPBKDF2_AES256_GCM_SYNLZ,
    ecaPBKDF2_AES128_CTC,
    ecaPBKDF2_AES256_CTC,
    ecaPBKDF2_AES128_CTC_SYNLZ,
    ecaPBKDF2_AES256_CTC_SYNLZ);

  /// binary header of a .synecc file, encrypted via ECC secp256r1
  // - as generated by TEccCertificate.Encrypt/EncryptFile, and decoded by
  // TEccCertificateSecret.Decrypt
  // - a sign-then-encrypt pattern may have been implemented for additional safety
  TEciesHeader = packed record
    /// contains 'SynEccEncrypted'#26
    // - so every .synecc file starts with those characters as signature
    magic: THash128;
    /// TEccCertificate.Issuer of the recipient public key used for encryption
    // - is either geniune random bytes, or some Baudot-encoded text
    rec: TEccCertificateIssuer;
    /// TEccCertificate.Serial of the recipient public key used for encryption
    recid: TEccCertificateID;
    /// the size of the plain content (may be compressed before encryption)
    size: cardinal;
    /// when this encryption was performed
    date: TEccDate;
    /// optional (local) timestamp, in Unix seconds since 1970, of the source file
    unixts: cardinal;
    /// actual encryption algorithm used
    algo: TEciesAlgo;
    /// the genuine random public key used for encryption
    rndpub: TEccPublicKey;
    /// optional ECDSA secp256r1 digital signature of the plain content
    sign: TEccSignatureCertifiedContent;
    /// the Message Authentication Code of the encrypted content
    hmac: THash256;
    /// a crc32c hash of the header (excluding this field)
    crc: cardinal;
  end;

  /// points to the binary header of a .synecc encrypted file
  PEciesHeader = ^TEciesHeader;

function ToText(algo: TEciesAlgo): PShortString; overload;

/// validate the binary header of a .synecc file buffer, encrypted via ECC secp256r1
// - will check against the expected layout, and values stored (e.g. crc)
// - returns true if head is a valid .synecc header, false otherwise
function EciesHeader(const head: TEciesHeader): boolean; overload;

/// extract the binary header of a .synecc file buffer, encrypted via ECC secp256r1
// - match the format generated by TEccCertificate.Encrypt/EncryptFile
// - returns true on success, false otherwise
function EciesHeader(const encrypted: RawByteString;
  out head: TEciesHeader): boolean; overload;

/// extract the binary header of a .synecc file, encrypted via ECC secp256r1
// - match the format generated by TEccCertificate.Encrypt/EncryptFile
// - returns true on success, false otherwise
// - if rawencryptedfile is specified, will also create such a file with the
// raw encrypted content (i.e. excluding the encryptedfile header)
function EciesHeaderFile(const encryptedfile: TFileName; out head: TEciesHeader;
  const rawencryptedfile: TFileName = ''): boolean;

/// convert the binary header of a .synecc file buffer into a JSON object
// - returns '' if the header is not a valid .synecc file
function EciesHeaderText(const head: TEciesHeader): RawUtf8; overload;

/// convert the header of a .synecc file into a JSON object
// - returns '' if the header is not a valid .synecc file
// - if rawencryptedfile is specified, will also create such a file with the
// raw encrypted content (i.e. excluding the encryptedfile header)
function EciesHeaderText(const encryptedfile: TFileName;
  const rawencryptedfile: TFileName = ''): RawUtf8; overload;

/// encrypt a message using a ECC secp256r1 public key
// - similar to EVP_PKEY.RsaSeal, as a cut-down version of our ECIES algorithm
// - store the ephemeral public key as output trailer, followed by ciphered data
// - ephemeral secret and IV are SHA-3 derivated from safe ECDH shared secret
function EciesSeal(aes: TAesAbstractClass; aesbits: integer;
  const pubkey: TEccPublicKey; const msg: RawByteString): RawByteString; overload;

/// decrypt a message using a ECC secp256r1 private key
// - similar to EVP_PKEY.RsaOpen, as a cut-down version of our ECIES algorithm
// - expects the ephemeral public key as input trailer, followed by ciphered data
// - ephemeral secret and IV are SHA-3 derivated from safe ECDH shared secret
function EciesOpen(aes: TAesAbstractClass; aesbits: integer;
  const privkey: TEccPrivateKey; const msg: RawByteString): RawByteString; overload;

/// encrypt a message using a ECC secp256r1 public key
function EciesSeal(const cipher: RawUtf8; const pubkey: TEccPublicKey;
  const msg: RawByteString): RawByteString; overload;

/// decrypt a message using a ECC secp256r1 private key
function EciesOpen(const cipher: RawUtf8; const privkey: TEccPrivateKey;
  const msg: RawByteString): RawByteString; overload;


const
  /// how many PBKDF2 rounds are performed by default for password derivation
  // - this value is high enough to circumvent most brute force attacks
  // - don't change this default value, unless you would break some expecations
  DEFAULT_ECCROUNDS = 60000;


type
  /// exception class associated with this mormot.crypt.ecc unit
  EEccException = class(ESynException);

  TEccSignatureCertified = class;

  /// a public certificate using ECC secp256r1 cryptography
  // - implements a custom binary format, with validation period, and chaining
  // - could be used for safe data signing, and authentication
  // - in fact, Base64 published property is enough to persist this instance:
  // but consider also ToBase64/FromBase64/LoadFromStream/SaveToStream methods
  TEccCertificate = class(TSynPersistent)
  protected
    fContent: TEccCertificateContent;
    fStoreOnlyPublicKey: boolean;
    fMaxVersion: byte;
    fUncompressed: TEccPublicKeyUncompressed; // =fContent.Head.Signed.PublicKey
    fUncompressedP: PEccPublicKeyUncompressed;
    function GetAuthorityIssuer: RawUtf8;
      {$ifdef HASINLINE} inline; {$endif}
    function GetAuthoritySerial: RawUtf8;
      {$ifdef HASINLINE} inline; {$endif}
    function GetIssueDate: RawUtf8;
      {$ifdef HASINLINE} inline; {$endif}
    function GetIssuer: RawUtf8;
      {$ifdef HASINLINE} inline; {$endif}
    function GetSerial: RawUtf8;
      {$ifdef HASINLINE} inline; {$endif}
    function GetValidityEnd: RawUtf8;
      {$ifdef HASINLINE} inline; {$endif}
    function GetValidityStart: RawUtf8;
      {$ifdef HASINLINE} inline; {$endif}
    function GetUsage: TCryptCertUsages;
      {$ifdef HASINLINE} inline; {$endif}
    function GetSubject: RawUtf8;
      {$ifdef HASINLINE} inline; {$endif}
    function GetIsSelfSigned: boolean;
      {$ifdef HASINLINE} inline; {$endif}
    procedure SetBase64(const base64: RawUtf8);
      {$ifdef HASINLINE} inline; {$endif}
    // do nothing by default but TEccCertificateSecret will include private key
    function AppendLoad(const data: RawByteString): boolean; virtual;
    function AppendSave: RawByteString; virtual;
  public
    /// initialize this certificate
    constructor Create; override;
    /// initialize this certificate with an explicit maximum version support
    constructor CreateVersion(MaxVers: integer);
    /// initialize this certificate from a supplied Base64 encoded binary
    // - will raise an EEccException if the supplied base64 is incorrect
    constructor CreateFromBase64(const base64: RawUtf8); virtual;
    /// initialize this certificate from a set of potential inputs
    // - will first search from a .public file name, Base64 encoded binary,
    // or a serial number which be used to search for a local .public file
    // (as located by EccKeyFileFind)
    // - will raise an EEccException if no supplied media is correct
    constructor CreateFromAuth(const AuthPubKey: TFileName; const AuthBase64,
      AuthSerial: RawUtf8); virtual;
    /// the certification information, digitaly signed in the Signature field
    property Signed: TEccCertificateSigned
      read fContent.Head.Signed;
    /// SHA-256 + ECDSA secp256r1 signature of the Certificate record
    property Signature: TEccSignature
      read fContent.Head.Signature;
    /// persist the certificate as some binary
    // - will use SaveToStream serialization, with optional StoreOnlyPublicKey
    function SaveToBinary(PublicKeyOnly: boolean = false): RawByteString;
    /// persist the certificate as some Base64 encoded binary
    // - will use SaveToStream serialization
    // - see PublicToBase64 to exclude the private key from a
    // TEccCertificateSecret instance
    function ToBase64: RawUtf8;
    /// persist only the public certificate as some Base64 encoded binary
    // - will follow TEccCertificate.SaveToStream/ToBase64 serialization,
    // even when called from a TEccCertificateSecret instance
    // - could be used to safely publish the public information of a newly
    // created certificate
    function PublicToBase64: RawUtf8;
    /// retrieve the certificate from some Base64 encoded binary
    // - will use LoadFromStream serialization
    // - returns true on success, false otherwise
    function FromBase64(const base64: RawUtf8): boolean;
    /// retrieve the certificate from its JSON object serialization
    // - will use LoadFromStream format from a "Base64": field
    // - returns true on success, false otherwise
    function FromJson(const json: RawUtf8): boolean;
    /// retrieve the certificate from some binary
    // - will use LoadFromStream serialization, so the SaveToBinary format
    // - returns true on success, false otherwise
    function LoadFromBinary(const binary: RawByteString): boolean;
    /// retrieve the certificate from the "Base64": JSON entry of a .public file
    // - will use FromBase64/LoadFromStream serialization
    // - returns true on success, false otherwise
    function FromFile(const filename: TFileName): boolean;
    /// retrieve the certificate from a set of potential inputs
    // - will first search from a .public file name, Base64 encoded binary,
    // or a serial number which be used to search for a local .public file in
    // the current folder or EccKeyFileFolder (as located by EccKeyFileFind)
    // - returns true on success, false otherwise
    function FromAuth(const AuthPubKey: TFileName; const AuthBase64,
      AuthSerial: RawUtf8): boolean;
    /// compare all fields of two Certificates
    // - don't compare the private key for inherited TEccCertificateSecret
    function IsEqual(another: TEccCertificate): boolean;
    /// persist the certificate as some binary
    // - returns true on success (i.e. this class stores a certificate),
    // false otherwise
    function SaveToStream(Stream: TStream): boolean;
    /// retrieve the certificate from some Base64 encoded binary
    // - returns true on success, false otherwise
    function LoadFromStream(Stream: TStream): boolean;
    /// fast check of the binary buffer storage of this certificate
    // - ensure Content.CRC has the expected value, using FNV-1a checksum
    // - does not validate the certificate against the certificates chain, nor
    // perform any ECC signature: use TEccCertificateChain.IsValid instead
    function CheckCRC: boolean;
    /// use this Certificate as Authority to verify an ECC digital signature
    function Verify(const hash: THash256;
      const Signature: TEccSignatureCertifiedContent;
      TimeUtc: TDateTime = 0): TEccValidity;
    /// verify the certificate signature using the given Authority public key
    function VerifyCertificate(Authority: TEccCertificate;
      TimeUtc: TDateTime = 0): TEccValidity;
    /// encrypt using the ECIES scheme, using this public certificate as key,
    // via AES-256-CFB/PKCS7 overPbkdf2HmacSha256, and HmacSha256
    // - returns the encrypted content, in the .synecc optimized format
    // - optional salt information used for PBKDF2 or HMAC can be customized
    // - ecaUnknown algorithm will use either ecaPBKDF2_HMAC_SHA256_AES256_CFB
    // or ecaPBKDF2_HMAC_SHA256_AES256_CFB_SYNLZ depending if the supplied
    // contain is compressible or not - but you may force another algorithm
    // - you can optionally associate an ECDSA secp256r1 digital signature,
    // and a timestamp which may be used when re-creating a decyphered file
    // - use TEccCertificateSecret.Decrypt to uncypher the resulting content
    function Encrypt(const Plain: RawByteString;
      Signature: TEccSignatureCertified = nil; FileDateTime: TDateTime = 0;
      const KDFSalt: RawUtf8 = 'salt'; KDFRounds: integer = DEFAULT_ECCROUNDS;
      const MACSalt: RawUtf8 = 'hmac'; MACRounds: integer = 100;
      Algo: TEciesAlgo = ecaUnknown): RawByteString;
    /// encrypt a file using the ECIES scheme, using this public certificate as
    // key,via AES-256-CFB/PKCS7 overPbkdf2HmacSha256, and HmacSha256
    // - by default, will create a FileToCrypt.synecc encrypted file
    // - ecaUnknown algorithm will use either ecaPBKDF2_HMAC_SHA256_AES256_CFB
    // or ecaPBKDF2_HMAC_SHA256_AES256_CFB_SYNLZ depending if the supplied
    // contain is compressible or not - but you may force another algorithm
    // - any available .sign ECDSA secp256r1 digital signature file will be
    // recognized and embedded to the resulting .synecc content
    // - optional salt information used for PBKDF2 can be customized, to lock
    // the encryted file with the supplied password
    function EncryptFile(const FileToCrypt: TFileName;
      const DestFile: TFileName = ''; const Salt: RawUtf8 = 'salt';
      SaltRounds: integer = DEFAULT_ECCROUNDS; Algo: TEciesAlgo = ecaUnknown;
      IncludeSignFile: boolean = true): boolean;
    /// encrypt some message using the ECDH/SHA3 EciesSeal() pattern
    // - so is not compatible with TEccCertificate.Encrypt() but with
    // ICryptCert.Encrypt and TEccCertificateSecret.DecryptMessage
    function EncryptMessage(const Plain: RawByteString;
      const Cipher: RawUtf8 = 'aes-128-ctr'): RawByteString;
    /// returns a TDocVariant object of all published properties of this instance
    // - excludes the Base64 property content if withBase64 is set to false
    function ToVariant(withBase64: boolean = true): variant;
    /// save the public key as a JSON content
    // - i.e. like the ToFile() .public json file
    function ToJson(withBase64: boolean = true): RawUtf8;
    /// save the public key as a .public json file
    // - i.e. a json containing all published properties of this instance
    // - persist ToVariant() as an human-readable JSON file
    function ToFile(const filename: TFileName): boolean;
    /// compute the hexadecimal fingerprint of this Certificate
    // - is the hash of its certificate and public key binary serialization
    function GetDigest(Algo: THashAlgo): RawUtf8;
    /// the maximum storage version allowed for this Certificate
    // - is 1 by default, but could be set e.g. to 2 to enable long Subject and
    // Usage fields
    property MaxVersion: byte
      read fMaxVersion write fMaxVersion;
    /// the CSV-encoded subjects of this certificate
    // - stored in Issuer on V1 format, in a separated field on V2 format
    property Subject: RawUtf8
      read GetSubject;
    /// low-level access to the ECC secp256r1 cryptography binary buffer
    // - you should not use this property, but other methods
    property Content: TEccCertificateContent
      read fContent write fContent;
  published
    /// the TEccCertificate format version
    // - currently equals 1
    property Version: word
      read fContent.Head.Version;
    /// the genuine identifier of this certificate, as hexadecimal text
    property Serial: RawUtf8
      read GetSerial;
    /// identify the certificate issuer, as text
    property Issuer: RawUtf8
      read GetIssuer;
    /// when this certificate was generated, as ISO-8601 text
    property IssueDate: RawUtf8
      read GetIssueDate;
    /// valid not before this date, as ISO-8601 text
    property ValidityStart: RawUtf8
      read GetValidityStart;
    /// valid not after this date, as ISO-8601 text
    property ValidityEnd: RawUtf8
      read GetValidityEnd;
    /// hexadecimal text of the authority certificate identifier used for signing
    property AuthoritySerial: RawUtf8
      read GetAuthoritySerial;
    /// identify the authoritify issuer used for signing, as text
    property AuthorityIssuer: RawUtf8
      read GetAuthorityIssuer;
    /// the allowed usages of this certificate
    // - only encoded with V2 format, so not published as JSON property
    // - V1 would set CU_ALL = 65335 which won't be serialized
    property Usage: TCryptCertUsages
      read GetUsage default CU_ALL;
    /// if this certificate has been signed by itself
    // - a self-signed certificate will have its AuthoritySerial/AuthorityIssuer
    // fields matching Serial/Issuer, and should be used as "root" certificates
    property IsSelfSigned: boolean
      read GetIsSelfSigned;
    /// Base64 encoded text of the whole certificate binary information
    // - only the public part of the certificate will be shown: any private key
    // of a TEccCertificateSecret instance would be trimmed
    property Base64: RawUtf8
      read PublicToBase64 write SetBase64;
  end;
  PEccCertificate = ^TEccCertificate;

  /// used to store a list of TEccCertificate instances
  // - e.g. in TEccCertificateChain.Items
  TEccCertificateObjArray = array of TEccCertificate;

  /// a public/private certificate using ECC secp256r1 cryptography
  // - will store TEccCertificate public and associated private secret key
  // - implements a custom binary format, with validation period, and chaining
  // - could be used for safe data signing via SignToBase64/SignFile, and
  // authentication / key derivation
  // - allows optional anti-forensic diffusion during storage via AFSplitStripes
  TEccCertificateSecret = class(TEccCertificate)
  protected
    fPrivateKey: TEccPrivateKey;
    fAFSplitStripes: integer;
    // include the private key, with AFSplitStripes, to the output stream
    function AppendLoad(const data: RawByteString): boolean; override;
    function AppendSave: RawByteString; override;
  public
    /// generate a new certificate, signed using the supplied Authority
    // - if Authority is nil, will generate a self-signed certificate
    // - the supplied Issuer name would be stored using AsciiToBaudot(),
    // truncated to the Issuer buffer size, i.e. 16 bytes - if Issuer is '',
    // TAesPrng.Fill() will be used
    // - you may specify some validity time range, if needed
    // - default ParanoidVerify=true will validate the certificate digital
    // signature via a call Ecc256r1Verify() to ensure its usefulness
    // - warning: signature would take around 1 ms under a 32-bit compiler
    constructor CreateNew(Authority: TEccCertificateSecret;
      const IssuerText: RawUtf8 = ''; ExpirationDays: integer = 0;
      StartDate: TDateTime = 0; ParanoidVerify: boolean = true;
      Usage: TCryptCertUsages = CU_ALL; const
      Subjects: RawUtf8 = ''; MaxVers: byte = 1);
    /// create a certificate with its private secret key from a password-protected
    // secure binary buffer
    // - perform all reverse steps from SaveToSecureBinary() method
    // - will raise an EEccException if the supplied Binary is incorrect
    constructor CreateFromSecureBinary(const Binary: RawByteString;
      const PassWord: RawUtf8; Pbkdf2Round: integer = DEFAULT_ECCROUNDS;
      Aes: TAesAbstractClass = nil); overload;
    /// create a certificate with its private secret key from a password-protected
    // secure binary buffer
    // - may be used on a constant array in executable, created via SaveToSource()
    // - perform all reverse steps from SaveToSecureBinary() method
    // - will raise an EEccException if the supplied Binary is incorrect
    constructor CreateFromSecureBinary(Data: pointer; Len: integer;
      const PassWord: RawUtf8; Pbkdf2Round: integer = DEFAULT_ECCROUNDS;
      Aes: TAesAbstractClass = nil); overload;
    /// create a certificate with its private secret key from an encrypted
    // secure .private binary file and its associated password
    // - perform all reverse steps from SaveToSecureFile() method
    // - will raise an EEccException if the supplied file is incorrect
    constructor CreateFromSecureFile(const FileName: TFileName;
      const PassWord: RawUtf8; Pbkdf2Round: integer = DEFAULT_ECCROUNDS;
      Aes: TAesAbstractClass = nil); overload;
    /// create a certificate with its private secret key from an encrypted
    // secure .private binary file stored in a given folder
    // - overloaded constructor retrieving the file directly from its folder
    // - perform all reverse steps from SaveToSecureFile() method
    // - will raise an EEccException if the supplied file is incorrect
    constructor CreateFromSecureFile(const FolderName: TFileName;
      const Serial, PassWord: RawUtf8; Pbkdf2Round: integer = DEFAULT_ECCROUNDS;
      Aes: TAesAbstractClass = nil); overload;
    /// create a certificate with its private secret key from an existing
    // plain TEccCertificate information and an optional private key binary
    // - raise EEccException if EccPrivateKey does not match the Cert public key
    // - FreeCert=true would call Cert.Free once done, e.g. to replace it
    constructor CreateFrom(Cert: TEccCertificate; EccPrivateKey: PEccPrivateKey;
      FreeCert: boolean = false);
    /// finalize the instance, and safe erase fPrivateKey stored buffer
    destructor Destroy; override;
    /// returns TRUE if the private secret key is not filled with zeros
    function HasSecret: boolean;
    /// computes the 'Serial.private' file name of this certificate
    // - as used by SaveToSecureFile()
    function SaveToSecureFileName(FileNumber: integer = 0): TFileName;
    /// backup the private secret key into an encrypted .private binary file
    // - you should keep all your private keys in a safe dedicated folder
    // - filename will be the certificate hexadecimal as 'Serial.private'
    // - will use anti-forensic diffusion of the private key (64 stripes = 2KB)
    // - then AES-256-CFB encryption (or the one specified in AES parameter) will
    // be performed fromPbkdf2HmacSha256 derivation of an user-supplied password
    function SaveToSecureFile(const PassWord: RawUtf8;
      const DestFolder: TFileName; AFStripes: integer = 64;
      Pbkdf2Round: integer = DEFAULT_ECCROUNDS; Aes: TAesAbstractClass = nil;
      NoHeader: boolean = false): boolean;
    /// backup the private secret key into several encrypted -###.private binary files
    // - secret sharing can be used to store keys at many different places, e.g.
    // on several local or remote drives, and therefore enhance privacy and safety
    // - it will use anti-forensic diffusion of the private key to distribute it
    // into pieces, in a manner that a subset of files can not regenerate the key:
    // as a result, a compromission of one sub-file won't affect the secret key
    // - filename will be the certificate hexadecimal as 'Serial-###.private'
    // - AES-256-CFB encryption (or the one specified in AES parameter) will be
    // performed fromPbkdf2HmacSha256 derivation of an user-supplied password
    function SaveToSecureFiles(const PassWord: RawUtf8;
      const DestFolder: TFileName; DestFileCount: integer;
      AFStripes: integer = 64; Pbkdf2Round: integer = DEFAULT_ECCROUNDS;
      Aes: TAesAbstractClass = nil; NoHeader: boolean = false): boolean;
    /// read a private secret key from an encrypted .private binary file
    // - perform all reverse steps from SaveToSecureFile() method
    // - returns TRUE on success, FALSE otherwise
    function LoadFromSecureFile(const FileName: TFileName;
      const PassWord: RawUtf8; Pbkdf2Round: integer = DEFAULT_ECCROUNDS;
      Aes: TAesAbstractClass = nil): boolean;
    /// backup the private secret key into an encrypted secure binary buffer
    // - you should keep all your private keys in a safe place
    // - will use anti-forensic diffusion of the private key (64 stripes = 2KB)
    // - then AES-256-CFB encryption (or the one specified in AES parameter) will
    // be performed fromPbkdf2HmacSha256 derivation of an user-supplied password
    function SaveToSecureBinary(const PassWord: RawUtf8; AFStripes: integer = 64;
      Pbkdf2Round: integer = DEFAULT_ECCROUNDS; Aes: TAesAbstractClass = nil;
      NoHeader: boolean = false): RawByteString;
    /// backup the private secret key into an encrypted source code constant
    // - may be used to integrate some private keys within an executable
    // - if ConstName='', _HEXASERIAL will be used, from 24 first chars of Serial
    // - the password may also be included as ConstName_PASS associated constant,
    // and as ConstName_CYPH in TSynPersistentWithPassword/TEccCertificateSecretSetting
    // encrypted format
    function SaveToSource(const ConstName, Comment, PassWord: RawUtf8;
      IncludePassword: boolean = true; AFStripes: integer = 0;
      Pbkdf2Round: integer = 100; Aes: TAesAbstractClass = nil;
      IncludeRaw: boolean = true): RawUtf8;
    /// read a private secret key from an encrypted secure binary buffer
    // - perform all reverse steps from SaveToSecureBinary() method
    // - returns TRUE on success, FALSE otherwise
    function LoadFromSecureBinary(const Binary: RawByteString;
       const PassWord: RawUtf8; Pbkdf2Round: integer = DEFAULT_ECCROUNDS;
       Aes: TAesAbstractClass = nil): boolean; overload;
    /// read a private secret key from an encrypted secure binary buffer
    // - perform all reverse steps from SaveToSecureBinary() method
    // - returns TRUE on success, FALSE otherwise
    function LoadFromSecureBinary(Data: pointer; Len: integer;
      const PassWord: RawUtf8; Pbkdf2Round: integer = DEFAULT_ECCROUNDS;
      Aes: TAesAbstractClass = nil): boolean; overload;
  public
    /// compute a Base64 encoded signature of some digital content
    // - memory buffer will be hashed using SHA-256, then will be signed using
    // ECDSA over the private secret key of this certificate instance
    // - you could later on verify this text signature according to the public
    // key of this certificate, calling TEccCertificateChain.IsSigned()
    // - create internally a temporary TEccSignatureCertified instance
    function SignToBase64(Data: pointer; Len: integer): RawUtf8; overload;
    /// compute a Base64 encoded signature of some digital content hash
    // - signature will be certified by private secret key of this instance
    // - you could later on verify this text signature according to the public
    // key of this certificate, calling TEccCertificateChain.IsSigned()
    // - supplied hash is likely to be from SHA-256, but could be e.g. crc256c
    // - create internally a temporary TEccSignatureCertified instance
    function SignToBase64(const Hash: THash256): RawUtf8; overload;
    /// compute a binary encoded signature of some digital content
    function SignToBinary(Data: pointer; Len: integer): RawByteString; overload;
    /// compute a Binary encoded signature of some digital content hash
    function SignToBinary(const Hash: THash256): RawByteString; overload;
    /// compute a .sign digital signature of any file
    // - SHA-256/ECDSA digital signature is included in a JSON document
    // - you can set some additional metadata information for the "meta": field
    // - will raise an EEccException if FileToSign does not exist
    // - returns the .sign file name, which is in fact FileToSign+'.sign'
    // - use TEccSignatureCertifiedFile class to load and validate such files
    function SignFile(const FileToSign: TFileName;
      const MetaNameValuePairs: array of const): TFileName;
    /// digital sign another certificate content
    // - as used e.g. by CreateNew(Authority) or TCryptCertInternal.Sign()
    // - if self is nil, Dest will be self-signed
    procedure SignCertificate(Dest: TEccCertificate;
      ParanoidVerify: boolean = false);
    /// decrypt using the ECIES scheme, using this private certificate as key,
    // via AES-256-CFB/PKCS7 overPbkdf2HmacSha256, and HmacSha256
    // - expects TEccCertificate.Crypt() cyphered content with its public key
    // - returns the decrypted content, or '' in case of failure
    // - optional shared information used for PBKDF2 or HMAC can be customized
    // - optionally, you can retrieve the sign-then-encrypt ECDSA secp256r1
    // signature and metadata stored in the header (to be checked via
    // TEccCertificateChain.IsSigned method), and/or the associated file timestamp
    function Decrypt(const Encrypted: RawByteString;
      out Decrypted: RawByteString; Signature: PEccSignatureCertifiedContent = nil;
      MetaData: PRawJson = nil; FileDateTime: PDateTime = nil;
      const KDFSalt: RawUtf8 = 'salt'; KDFRounds: integer = DEFAULT_ECCROUNDS;
      const MACSalt: RawUtf8 = 'hmac'; MACRounds: integer = 100): TEccDecrypt;
    /// decrypt using the ECIES scheme, using this private certificate as key,
    /// decrypt a file using the ECIES scheme, using this private certificate as
    // key, via AES-256-CFB/PKCS7 overPbkdf2HmacSha256, and HmacSha256
    // - makes the reverse operation of TEccCertificate.EncryptFile method
    // - by default, will erase the (.synecc) extension to FileToDecrypt name
    // - optional salt information used for PBKDF2 can be customized, to unlock
    // the encryted file with the supplied password
    // - optionally, you can retrieve the sign-then-encrypt ECDSA secp256r1
    // signature stored in the header for TEccCertificateChain.IsSigned() in
    // supplied Signature^ and MetaData^ values
    function DecryptFile(const FileToDecrypt: TFileName;
      const DestFile: TFileName = ''; const Salt: RawUtf8 = 'salt';
      SaltRounds: integer = DEFAULT_ECCROUNDS;
      Signature: PEccSignatureCertifiedContent = nil;
      MetaData: PRawJson = nil): TEccDecrypt;
    /// decrypt some message using the ECDH/SHA3 EciesOpen() pattern
    // - so is not compatible with TEccCertificateSecret.Decrypt() but with
    // ICryptCert.Decrypt and TEccCertificate.EncryptMessage
    function DecryptMessage(const Encrypted: RawByteString;
      const Cipher: RawUtf8 = 'aes-128-ctr'): RawByteString;
  public
    /// how many anti-forensic diffusion stripes are used for private key storage
    // - default is 0, meaning no diffusion, i.e. 32 bytes of storage space
    // - you may set e.g. to 32 to activate safe diffusion to 1KB of storage
    // for ToBase64/SaveToStream methods
    // - is modified temporarly by SaveToSecure() method
    property AFSplitStripes: integer
      read fAFSplitStripes;
    /// disable private secret key storage in SaveToStream()
    // - default is false, i.e. the private secret key will be serialized
    // - you may set TRUE here so that SaveToStream() would store only the
    // public certificate, as expected by a TEccCertificate class
    // - is used e.g. by PublicToBase64 method to trim the private information
    property StoreOnlyPublicKey: boolean
      read fStoreOnlyPublicKey write fStoreOnlyPublicKey;
    /// read access to the low-level stored private key 
    property PrivateKey: TEccPrivateKey
      read fPrivateKey;
  end;

  /// store settings pointing to a local .private file containing a secret key
  // - following TEccCertificateSecret secure binary file format
  // - you may use "ECC infocrypt" command to retrieve SaveToSource constants
  TEccCertificateSecretSetting = class(TSynPersistentWithPassword)
  protected
    fSerial: RawUtf8;
    fFileName: TFileName;
    fPasswordRounds: integer;
  public
    /// initialize the settings with default values
    constructor Create; override;
    /// generate a TEccCertificateSecret instance corresponding to the settings
    // - is a wrapper around TEccCertificateSecret.CreateFromSecureFile
    // - will read the FileName file (if supplied), or search for the
    // <Serial>.private file in the supplied folder otherwise, then
    // use associated Password/PasswordRounds values to uncypher it
    // - returns nil if Serial and FileName are '', or raise an exception
    // on unexpected error
    // - caller is responsible of freeing the returned class instance
    function CertificateSecret(const FolderName: TFileName): TEccCertificateSecret;
  published
    /// the first characters of the .private file holding the secret key
    // - equals '' by default, meaning no private secret is defined
    // - you may use the FileName property instead to specify a full path name
    property Serial: RawUtf8
      read fSerial write fSerial;
    /// the first characters of the .private file holding the secret key
    // - equals '' by default, meaning no private secret is defined
    // - you may use the Serial property instead to search in an application
    // specific folder
    property FileName: TFileName
      read fFileName write fFileName;
    /// the password used to protect the .private file
    // - matches the -authpass parameter used with "ECC decrypt" command, but
    // with TSynPersistentWithPassword encryption
    // - i.e. matches ConstName_CYPH as generated by TEccCertificateSecret.SaveToSource
    property Password: SpiUtf8
      read fPassword write fPassword;
    /// number of PBKDF2 rounds to be applied to the associated password
    // - matches ConstName_ROUNDS as generated by TEccCertificateSecret.SaveToSource
    // - matches the -authrounds parameter used with "ECC decrypt" command
    // - default is DEFAULT_ECCROUNDS, i.e. 60000
    property PasswordRounds: integer
      read fPasswordRounds write fPasswordRounds;
  end;

  /// store settings pointing to a local .private file containing a secret key
  // for .synecc file decryption
  // - following TEccCertificateSecret secure binary file format
  // - publishes Salt and SaltRounds values, as expected by
  // TEccCertificateSecret.Decrypt method
  TEccCertificateDecryptSetting = class(TEccCertificateSecretSetting)
  protected
    fSalt: RawUtf8;
    fSaltRounds: integer;
  public
    /// initialize the settings with default values
    constructor Create; override;
  published
    /// the Salt passphrase used to protect the .synecc encrypted file
    // - matches the -saltpass parameter used with "ECC crypt" command
    // - default is 'salt'
    property Salt: RawUtf8
      read fSalt write fSalt;
    /// number of PBKDF2 rounds to be applied to the associated Salt
    // - matches the -saltrounds parameter used with "ECC crypt" command
    // - default is DEFAULT_ECCROUNDS, i.e. 60000
    property SaltRounds: integer
      read fSaltRounds write fSaltRounds;
  end;

  /// a ECDSA secp256r1 digital signature of some content, signed by an authority
  TEccSignatureCertified = class(TSynPersistent)
  protected
    fCertified: TEccSignatureCertifiedContent;
    function GetAuthorityIssuer: RawUtf8;
    function GetAuthoritySerial: RawUtf8;
    function GetDate: RawUtf8;
  public
    /// initialize this signature
    constructor Create; override;
    /// compute a new signature of some digital content
    // - memory buffer will be hashed using SHA-256, then will be signed using
    // ECDSA over the private secret key of the supplied Authority certificate
    constructor CreateNew(Authority: TEccCertificateSecret;
      Data: pointer; Len: integer); overload;
    /// compute a new signature of some digital content hash
    // - supplied hash is likely to be from SHA-256, but could be e.g. crc256c
    // - the hash will be signed using ECDSA over the private secret key of
    // the supplied Authority certificate
    constructor CreateNew(Authority: TEccCertificateSecret; const Hash: THash256); overload;
    /// initialize this signature from a supplied binary
    // - will raise an EEccException if the supplied binary content is incorrect
    constructor CreateFrom(const binary: TEccSignatureCertifiedContent;
      NoException: boolean = false);
    /// initialize this signature from a supplied Base64 encoded binary
    // - will raise an EEccException if the supplied base64 is incorrect
    constructor CreateFromBase64(const base64: RawUtf8;
      NoException: boolean = false);
    /// initialize this signature from the "sign": field of a JSON .sign file
    // - will raise an EEccException if the supplied file is incorrect
    constructor CreateFromFile(const signfilename: TFileName;
      NoException: boolean = false);
    /// fast check of the binary buffer storage of this signature
    // - performs basic checks, avoiding any void date, authority or signature
    // - use Verify() or TEccCertificateChain.IsSigned() methods for full
    // digital signature validation
    function Check: boolean;
    /// check if this digital signature matches a given data hash
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the supplied signing authority
    // - supplied hash is likely to be from SHA-256, but could be e.g. crc256c
    // - this method is thread-safe, and not blocking
    function Verify(Authority: TEccCertificate; const hash: THash256;
      TimeUtc: TDateTime = 0): TEccValidity; overload;
    /// check if this digital signature matches a given memory buffer
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the supplied signing authority
    // - will compute and verify the SHA-256 hash of the supplied data
    // - this method is thread-safe, and not blocking
    function Verify(Authority: TEccCertificate; Data: pointer; Len: integer;
      TimeUtc: TDateTime = 0): TEccValidity; overload;
    /// persist the signature as some Base64 encoded binary
    function ToBase64: RawUtf8;
    /// persist the signature as raw TEccSignatureCertifiedContent binary buffer
    function ToBinary: RawByteString;
    /// returns a TDocVariant object of all published properties of this instance
    function ToVariant: variant; virtual;
    /// retrieve the signature from some Base64 encoded binary
    // - returns true on success, false otherwise
    function FromBase64(const base64: RawUtf8): boolean;
    /// retrieve the signature from the "sign": field of a JSON .sign file
    // - returns true on success, false otherwise
    function FromFile(const signfilename: TFileName): boolean; virtual;
    /// save the ECDSA signature into a ASN.1's binary DER buffer
    // - note that DER content only stores the ECDSA digital signature, so
    // all certification information is lost
    function SaveToDerBinary: RawByteString;
    /// save the ECDSA signature into a ASN.1's binary DER file
    // - note that DER content only stores the ECDSA digital signature, so
    // all certification information is lost - consider using
    // TEccSignatureCertifiedFile instead
    // - returns TRUE on success, FALSE otherwise
    function SaveToDERFile(const FileName: TFileName): boolean;
    /// save the ECDSA signature into a X509 PEM text
    // - PEM is just a base64-encoded DER with some minimal header/footer
    // - note that PEM/DER content only stores the ECDSA digital signature, so
    // all certification information is lost
    function SaveToPemText: RawUtf8;
    /// save the ECDSA signature into a X509 PEM file
    // - note that PEM/DER content only stores the ECDSA digital signature, so
    // all certification information is lost - consider using
    // TEccSignatureCertifiedFile instead
    // - returns TRUE on success, FALSE otherwise
    function SaveToPEMFile(const FileName: TFileName): boolean;
    /// low-level access to the binary buffer used ECDSA secp256r1 cryptography
    // - you should not use this property, but other methods
    property Certified: TEccSignatureCertifiedContent
      read fCertified write fCertified;
  published
    /// the TEccSignatureCertified format version
    // - currently equals 1
    property Version: word
      read fCertified.Version;
    /// when this signature was generated, as ISO-8601 text
    property Date: RawUtf8
      read GetDate;
    /// hexadecimal text of the authority certificate identifier used for signing
    property AuthoritySerial: RawUtf8
      read GetAuthoritySerial;
    /// identify the authoritify issuer used for signing, as text
    property AuthorityIssuer: RawUtf8
      read GetAuthorityIssuer;
  end;

  /// handle a .sign file content as generated by TEccCertificateSecret.SignFile
  // - JSON document of a SHA-256/ECDSA secp256r1 digital signature
  TEccSignatureCertifiedFile = class(TEccSignatureCertified)
  protected
    fLowLevelInfo: TDocVariantData;
    fMd5Digest: TMd5Digest;
    fSha256Digest: TSha256Digest;
    fMetaData: variant;
    fSize: integer;
    fMD5: RawUtf8;
    fSHA256: RawUtf8;
  public
    /// create and set .sign fields after TEccCertificateSecret.Decrypt() process
    // - will compute Size, and MD5/SHA-256 hashes from aDecryptedContent
    // - will raise an EEccException if the supplied parameters are incorrect
    constructor CreateFromDecryptedFile(const aDecryptedContent: RawByteString;
      const Signature: TEccSignatureCertifiedContent; const MetaData: RawJson);
    /// read a .sign digital signature file
    // - as previously generated by TEccCertificateSecret.SignFile
    // - will append '.sign' to aFileName, if it does not match this extension
    // - returns true on success, false otherwise
    function FromFile(const aFileName: TFileName): boolean; override;
    /// read a .sign digital signature JSON content
    // - as previously generated by TEccCertificateSecret.SignFile
    // - returns true on success, false otherwise
    function FromFileJson(const aFileContent: RawUtf8): boolean;
    /// compute .sign fields after TEccCertificateSecret.Decrypt() process
    // - will compute Size, and MD5/SHA-256 hashes from aDecryptedContent
    function FromDecryptedFile(const aDecryptedContent: RawByteString;
      const Signature: TEccSignatureCertifiedContent; const MetaData: RawJson): boolean;
    /// low-level access to the whole JSON document members
    property LowLevelInfo: TDocVariantData
      read fLowLevelInfo;
    /// the MD5 binary hash as stored in the .sign file
    property Md5Digest: TMd5Digest
      read fMd5Digest;
    /// the SHA-256 binary hash as stored in the .sign file
    property Sha256Digest: TSha256Digest
      read fSha256Digest;
  published
    /// the meta data document as stored in the .sign file
    property MetaData: variant
      read fMetaData;
    /// the signed file size in bytes, as stored in the .sign file
    property Size: integer
      read fSize;
    /// the MD5 hexadecimal signature as stored in the .sign file
    property MD5: RawUtf8
      read fMD5;
    /// the SHA-256 hexadecimal signature as stored in the .sign file
    property SHA256: RawUtf8
      read fSHA256;
  end;

  /// manage PKI certificates using ECC secp256r1 cryptography
  // - will implement a simple and efficient public-key infrastructure (PKI),
  // based on JSON objects or even plain Base64 encoded JSON strings
  // - store certificates list and revoked serials (CRL)
  TEccCertificateChain = class(TSynPersistentRWLightLock)
  protected
    fItems: TEccCertificateObjArray;
    fCrl: TEccCertificateRevocationDynArray;
    fMaxVersion: byte;
    fIsValidCached: boolean;
    fIsValidCacheCount: integer;
    fIsValidCacheSalt: RawByteString; // avoid flooding on forged input
    fIsValidCache: THash128DynArray;  // low TEccCertificateContent.ComputeHash
    function GetCount: integer;
      {$ifdef HASINLINE} inline; {$endif}
    function GetCrlCount: integer;
      {$ifdef HASINLINE} inline; {$endif}
    procedure LockedClear;
    function InternalAdd(cert: TEccCertificate; expected: TEccValidity): PtrInt;
    procedure SetIsValidCached(const Value: boolean);
  public
    /// initialize a blank certificate store
    constructor Create; override;
    /// initialize a blank store with an explicit maximum version support
    constructor CreateVersion(MaxVers: integer);
    /// initialize the certificate store from some JSON array of strings
    // - the serialization format is just a JSON array of Base64 encoded
    // certificates (with only public keys) - so diverse from CreateFromFile()
    // - will call LoadFromJson(), and raise EEccException on any error
    constructor CreateFromJson(const json: RawUtf8; maxvers: integer = 2);
    /// initialize the certificate store from an array of Base64 encoded strings
    // - a TRawUtf8DynArray value is very convenient when storing the
    // certificates chain as part of JSON settings, e.g. TDDDAppSettings
    // - will call LoadFromArray(), and raise EEccException on any error
    constructor CreateFromArray(const values: TRawUtf8DynArray;
      maxvers: integer = 2);
    /// finalize the certificate store
    destructor Destroy; override;
    /// delete all stored certificates
    // - this method is thread-safe, calling Safe.WriteLock/WriteUnlock
    procedure Clear;
    /// search for a certificate from its hexadecimal text identifier
    // - this method is not thread-safe, unless you use Safe.ReadLock/ReadUnlock
    function GetBySerial(const Serial: RawUtf8): TEccCertificate; overload;
    /// search for a certificate from its binary identifier
    // - this method is not thread-safe, unless you use Safe.ReadLock/ReadUnlock
    function GetBySerial(const Serial: TEccCertificateID): TEccCertificate; overload;
    /// search for a certificate public key from its binary identifier
    // - returns ecvValidSigned/ecvValidSelfSigned if the Serial identifier
    // was found and not deprecated/revoked, for the proper Usage
    // - returns ecvUnknownAuthority/ecvDeprecatedAuthority/ecvRevoked/ecvWrongUsage
    // otherwise
    // - this method is thread-safe, since it makes a private copy of the key
    function GetKeyBySerial(const Serial: TEccCertificateID; Usage: TCryptCertUsages;
      out PublicKey: TEccPublicKey; Valid: TEccValidity = ecvUnknown;
      TimeUtc: TDateTime = 0; IgnoreDate: boolean = false): TEccValidity;
    /// quickly check if a given certificate ID is part of the CRL
    // - will check the internal Certificate Revocation List and the current date
    // - returns crrNotRevoked is the serial is not known as part of the CRL
    // - returns the reason why this certificate has been revoked otherwise
    function IsRevoked(const Serial: RawUtf8): TCryptCertRevocationReason; overload;
    /// quickly check if a given certificate ID is part of the CRL
    // - will check the internal Certificate Revocation List and the current date
    // - returns crrNotRevoked is the serial is not known as part of the CRL
    // - returns the reason why this certificate has been revoked otherwise
    function IsRevoked(const Serial: TEccCertificateID): TCryptCertRevocationReason; overload;
    /// add a new Serial number to the internal Certificate Revocation List
    // - you can set Reason as crrNotRevoked to remove a previous revocation
    function Revoke(const Serial: RawUtf8; RevocationDate: TDateTime;
      Reason: TCryptCertRevocationReason): boolean; overload;
    /// add a new Serial number to the internal Certificate Revocation List
    // - you can set Reason as crrNotRevoked to remove a previous revocation
    function Revoke(const Serial: TEccCertificateID; RevocationDate: TDateTime;
      Reason: TCryptCertRevocationReason): boolean; overload;
    /// check if the certificate is valid, against known certificates chain
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - consider setting IsValidCached property to TRUE to reduce resource use
    // - this method is thread-safe, and not blocking
    function IsValid(cert: TEccCertificate;
      TimeUtc: TDateTime = 0): TEccValidity; overload;
    /// check if the raw certificate is valid, against known certificates chain
    // - will check internal properties of the certificate (e.g. validity dates,
    // unless ignoreDate=TRUE), and validate the stored ECDSA signature
    // according to the public key of the associated signing authority (which
    // should be valid, and stored in Items[])
    // - self-signed certificates should be registered with AddSelfSigned() or
    // allowPlainSelfSigned will allow them even without AddSelfSigned()
    // - consider setting IsValidCached property to TRUE to reduce resource use
    // - this method is thread-safe, and not blocking
    function IsValidRaw(const content: TEccCertificateContent;
      ignoreDate: boolean = false; allowPlainSelfSigned: boolean = false;
      TimeUtc: TDateTime = 0): TEccValidity;
    /// check all stored certificates and their authorization chain
    // - returns nil if all items were valid
    // - returns the list of any invalid instances
    // - do not free the returned items, since they are reference to Items[]
    function ValidateItems: TEccCertificateObjArray;
    /// check if the digital signature is recognized by the stored certificates
    // - ensure that sign.AuthoritySerial is part of Items[] list but not the CRL
    // - this method won't perform the ECDSA verification: use IsSigned() instead
    // - this method is thread-safe, and not blocking
    function IsAuthorized(sign: TEccSignatureCertified): boolean; overload;
    /// check if the digital signature is recognized by the stored certificates
    // - ensure that sign.AuthoritySerial is part of Items[] list but not the CRL
    // - this method won't perform the ECDSA verification: use IsSigned() instead
    // - this method is thread-safe, and not blocking
    function IsAuthorized(const sign: TEccSignatureCertifiedContent): boolean; overload;
    /// check if the digital signature is recognized by the stored certificates
    // - will check that the supplied base64 encoded text is a ECC signature,
    // and that its AuthoritySerial is part of the Items[] list
    // - this method won't perform the ECDSA verification: use IsSigned() instead
    // - this method is thread-safe, and not blocking
    function IsAuthorized(const base64sign: RawUtf8): boolean; overload;
    /// check if the digital signature of a given data hash is valid
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - supplied hash is likely to be from SHA-256, but could be e.g. crc256c
    // - this method is thread-safe, and not blocking
    function IsSigned(sign: TEccSignatureCertified; const hash: THash256): TEccValidity; overload;
    /// check if the digital signature of a given memory buffer is valid
    // - if sign is a TEccSignatureCertifiedFile, the Size, MD5 and SHA256 fields
    // stored in the .sign file content will be checked against the supplied data
    // before ECDSA signature, and would return ecvCorrupted on error
    // - it will then check internal properties of the certificate (e.g. validity
    // dates), and validate the stored SHA-256/ECDSA signature according to the
    // public key of the associated signing authority (stored in Items[])
    // - this method is thread-safe, and not blocking
    function IsSigned(sign: TEccSignatureCertified;
      Data: pointer; Len: integer): TEccValidity; overload;
    /// check if the digital signature file (.sign content) is valid
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - will use TEccSignatureCertifiedFile Size, MD5 and SHA256 fields,
    // so could be used without any actual memory buffer
    // - this method is thread-safe, and not blocking
    function IsSigned(sign: TEccSignatureCertifiedFile): TEccValidity; overload;
    /// check if the digital signature of a given data hash is valid
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - supplied hash is likely to be from SHA-256, but could be e.g. crc256c
    // - this method is thread-safe, and not blocking
    function IsSigned(const sign: TEccSignatureCertifiedContent;
      const hash: THash256; TimeUtc: TDateTime = 0): TEccValidity; overload;
    /// check if the digital signature of a given memory buffer is valid
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - will compute and verify the SHA-256 hash of the supplied data
    // - this method is thread-safe, and not blocking
    function IsSigned(const sign: TEccSignatureCertifiedContent;
      Data: pointer; Len: integer; TimeUtc: TDateTime = 0): TEccValidity; overload;
    /// verify the Base64 encoded digital signature of a given hash
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - supplied hash is likely to be from SHA-256, but could be e.g. crc256c
    // - this method is thread-safe, and not blocking
    function IsSigned(const base64sign: RawUtf8;
      const hash: THash256; TimeUtc: TDateTime = 0): TEccValidity; overload;
    /// verify the Base64 encoded digital signature of a given memory buffer
    // - will check internal properties of the certificate (e.g. validity dates),
    // and validate the stored ECDSA signature according to the public key of
    // the associated signing authority (which should be stored in Items[])
    // - will compute and verify the SHA-256 hash of the supplied data
    // - this method is thread-safe, and not blocking
    function IsSigned(const base64sign: RawUtf8; Data: pointer; Len: integer;
      TimeUtc: TDateTime = 0): TEccValidity; overload;
    /// register a certificate in the internal certificate chain
    // - returns the index of the newly inserted certificate
    // - returns -1 on error, e.g. if the certificate was not valid, if it has
    // no cuCA/cuDigitalSignature in its Usage, or its serial was already part
    // of the internal list
    // - any self-signed certificate will be rejected: use AddSelfSigned() instead
    // - this method is thread-safe
    function Add(cert: TEccCertificate): PtrInt;
    /// register a self-signed certificate in the internal certificate chain
    // - a self-signed certificate will have its AuthoritySerial/AuthorityIssuer
    // fields matching Serial/Issuer, and should be used as "root" certificates
    // (aka as "trust anchors" in X.509 terminology)
    // - returns -1 on error, e.g. if the certificate was not valid, if it has
    // no cuCA/cuDigitalSignature in its Usage, or its serial was already part
    // of the internal list
    // - this method is thread-safe
    function AddSelfSigned(cert: TEccCertificate): PtrInt;
    /// register certificate or certificate chain from a memory buffer
    // - is able to add some binary, Base64 or JSON encoded TEccCertificate or
    // TEccCertificateChain input
    // - returns the serials of added certificate(s)
    function AddFromBuffer(const Content: RawByteString): TRawUtf8DynArray;
    /// register certificates from another certificate chain
    function AddFrom(Chain: TEccCertificateChain): TRawUtf8DynArray;
    /// returns the serials of the stored certificate(s)
    function GetSerials: TRawUtf8DynArray;
    /// save the whole certificates chain as an array of Base64 encoded content
    // - each certificate would be stored via PublicToBase64() into a RawUtf8
    // - any private key would be trimmed from the output: private secret keys
    // should NOT be kept in the main chain, in which only public keys will appear
    function SaveToArray: TRawUtf8DynArray;
    /// load a certificates chain from an array of Base64 encoded content
    // - follows SaveToArray format
    // - would create only TEccCertificate instances with their public keys,
    // since no private key, therefore no TEccCertificateSecret is expected
    function LoadFromArray(const values: TRawUtf8DynArray): boolean;
    /// save the whole certificates chain as a JSON array
    // - each certificate would be stored via PublicToBase64() into a JSON string
    // - any private key would be trimmed from the output JSON: private secret
    // keys should NOT be kept in the main chain, in which only public keys
    // should appear
    function SaveToJson: RawUtf8;
    /// load a certificates chain from a JSON array of strings
    // - follows SaveToJson format, i.e. Base64 encoded strings
    // - would create only TEccCertificate instances with their public keys,
    // since no private key, therefore no TEccCertificateSecret is expected
    function LoadFromJson(const json: RawUtf8): boolean;
    /// save the whole certificates chain as a binary content
    // - each certificate and CRL would be stored via its SaveToStream() binary layout
    // - by design, any private key would be trimmed from the output content
    function SaveToBinary: RawByteString;
    /// load a certificates chain from binary content
    // - each certificate and CRL is read via its LoadFromStream() binary layout
    // - would create only TEccCertificate instances with their public keys
    function LoadFromBinary(const binary: RawByteString): boolean;
  public
    /// initialize the certificate store from some JSON-serialized .ca file
    // - the file would store plain verbose information of all certificates,
    // i.e. Base64 full information (containing only public keys) and also
    // high-level published properties of all stored certificates (e.g. Serial)
    // - as such, this file format is more verbose than CreateFromJson/SaveToJson
    // and may be convenient for managing certificates with a text/json editor
    // - you may use SaveToFile() method to create such JSON file
    // - will call LoadFromFile(), and raise EEccException on any error
    constructor CreateFromFile(const jsonfile: TFileName);
    /// initialize the certificate store from an array of .public file names
    // - raise EEccException on any error when reading a .public file
    constructor CreateFromFiles(const files: array of TFileName);
    /// save the whole certificates chain as a JSON object, matching .ca format
    // - is in fact the human-friendly JSON serialization of this instance
    // - would store plain verbose information of all certificates,
    // i.e. Base64 full information (containing only public keys) and also
    // high-level published properties of all stored certificates (e.g. Serial)
    // - as such, .ca file format is more verbose than CreateFromJson/SaveToJson
    // and may be convenient for managing certificates with a text/json editor
    function SaveToFileVariant: variant;
    /// save the whole certificates chain as a JSON content, matching .ca format
    // - is in fact the human-friendly JSON serialization of this instance
    // - would store plain verbose information of all certificates,
    // i.e. Base64 full information (containing only public keys) and also
    // high-level published properties of all stored certificates (e.g. Serial)
    // - as such, .ca file format is more verbose than CreateFromJson/SaveToJson
    // and may be convenient for managing certificates with a text/json editor
    function SaveToFileContent: RawUtf8;
    /// load a certificates chain from some JSON-serialized .ca file content
    // - you may use SaveToFileContent method to create such JSON content
    // - would create only TEccCertificate instances with their public keys,
    // since no private key, therefore no TEccCertificateSecret is expected
    function LoadFromFileContent(const cajsoncontent: RawUtf8): boolean;
    /// save the whole certificates chain as a .ca JSON file
    // - is in fact the human-friendly JSON serialization of this instance
    // - the .ca file would store plain verbose information of all certificates,
    // i.e. Base64 full information (containing only public keys) and also
    // high-level published properties of all stored certificates (e.g. Serial)
    // - as such, this file format is more verbose than CreateFromJson/SaveToJson
    // and may be convenient for managing certificates with a text/json editor
    function SaveToFile(const jsonfile: TFileName): boolean;
    /// load a certificates chain from some JSON-serialized .ca file
    // - you may use SaveToFile() method to create such JSON file
    // - would create only TEccCertificate instances with their public keys,
    // since no private key, therefore no TEccCertificateSecret is expected
    // - if jsonfile is not in the current folder, will try EccKeyFileFolder
    function LoadFromFile(const jsonfile: TFileName): boolean;
  published
    /// low-level access to the internal certificates chain
    // - thread-safe process may be done using
    // ! Safe.Lock; try ... finally Safe.Unlock; end;
    property Items: TEccCertificateObjArray
      read fItems;
    /// how many certificates are currently stored in the certificates chain
    property Count: integer
      read GetCount;
    /// how many certificates are currently revoked in the certificates CRL
    property CrlCount: integer
      read GetCrlCount default 0;
    /// the maximum storage version allowed for this Certificate
    // - is 2 by default, but could be set e.g. to 1 for backward compatibility
    property MaxVersion: byte
      read fMaxVersion write fMaxVersion;
    /// if the IsValid() calls should maintain a cache of all valid certificates
    // - will store the low 128-bit of the SHA-256 hashing of verified contents
    // - since Ecc256r1Verify() is demanding, such a cache may have a huge speed
    // benefit if the certificates are about to be supplied several times
    // - is disabled by default, for paranoid safety
    property IsValidCached: boolean
      read fIsValidCached write SetIsValidCached;
  end;

const
  /// file extension of the JSON file storing a TEccCertificate public key
  // - as read by TEccCertificate.FromFile method
  ECCCERTIFICATEPUBLIC_FILEEXT = '.public';
  /// file extension of the binary encrypted file storing a private key
  // - as generated by TEccCertificateSecret.SaveToSecureFile method
  ECCCERTIFICATESECRET_FILEEXT = '.private';

  /// file extensions used to store TEccCertificate keys
  ECCCERTIFICATE_FILEEXT: array[{secret=}boolean] of TFileName = (
    ECCCERTIFICATEPUBLIC_FILEEXT,
    ECCCERTIFICATESECRET_FILEEXT);

  /// file extension of the JSON file storing a digital signature of a file
  // - by convention, this .sign extension is appended to the original file name
  // - as generated by TEccCertificateSecret.SignFile, and loaded by the
  // TEccSignatureCertifiedFile class
  ECCCERTIFICATESIGN_FILEEXT = '.sign';
  /// file extension of the JSON file storing a certificate authorities chain
  // - as generated by TEccCertificateChain.SaveToFile()
  // and loaded by TEccCertificateChain.LoadFromFile
  ECCCERTIFICATES_FILEEXT = '.ca';
  /// file extension of the ECIES encrypted file
  // - with optional digital signature of the plain content
  // - as generated by TEccCertificate.Encrypt/EncryptFile, and decoded via
  // TEccCertificateSecret.Decrypt
  ENCRYPTED_FILEEXT = '.synecc';

  CHAIN_MAGIC = $513c2a13;

/// search the single .public or .private file starting with the supplied file name
// - as used in the ECC.dpr command-line sample project
// - returns true and set the full file name of the matching file
// - returns false is there is no match, or more than one matching file
// - will also search in EccKeyFileFolder, if the supplied folder is not enough
function EccKeyFileFind(var TruncatedFileName: TFileName; privkey: boolean): boolean;

/// search the single .public or .private file used to crypt a given content
// - match the format generated by TEccCertificate.Encrypt/EncryptFile
// - returns true on success, false otherwise
// - will also search in EccKeyFileFolder, if the current folder is not enough
function EciesKeyFileFind(const encrypted: RawByteString; out keyfile: TFileName;
  privkey: boolean = true): boolean;

/// retrieve the private local folder used to store .public or .private files
// - it is better to store all you key files in a single place, for easier
// and safer management
// - under Windows, returns 'C:\Users\<username>\AppData\Local\Synopse\Keys\'
// - under Linux, returns '$HOME/.synopse/keys/'
function EccKeyFileFolder: TFileName;

/// convert a raw ECDSA secp256r1 signature into a DER compatible content
function EccToDer(const sign: TEccSignature): RawByteString; overload;

/// convert a raw ECC secp256r1 private key into a DER compatible content
function EccToDer(const priv: TEccPrivateKey): RawByteString; overload;

/// convert a raw ECC secp256r1 public key into a DER compatible content
function EccToDer(const pub: TEccPublicKey): RawByteString; overload;

/// convert a DER compatible content into a raw ECDSA secp256r1 signature
// - does not support all potential DER layout, just the one generated by EccSignToDer
function DerToEcc(der: PByteArray; derlen: PtrInt; out sign: TEccSignature): boolean; overload;

/// convert a DER compatible content into a raw ECC secp256r1 private key
// - does not support all potential DER layout, just the one generated by EccPrivToDer
function DerToEcc(der: PByteArray; derlen: PtrInt; out priv: TEccPrivateKey): boolean; overload;

/// convert a DER compatible content into a raw ECC secp256r1 public key
// - does not support all potential DER layout, just the one generated by EccPubToDer
function DerToEcc(der: PByteArray; derlen: PtrInt; out pub: TEccPublicKey): boolean; overload;

/// parse ECDSA signature in raw, PEM or DER format into its binary raw buffer
function PemDerRawToEcc(const pem: RawUtf8; out sig: TEccSignature): boolean; overload;

/// parse ECC private key in raw, PEM or DER format into its binary raw buffer
// - supports plain TEccPrivateKey binary, ASN1_SEQ format, or PKCS#8 encoding
function PemDerRawToEcc(const pem: RawUtf8; out priv: TEccPrivateKey): boolean; overload;

/// parse ECC public key in raw, PEM or DER format into its binary raw buffer
function PemDerRawToEcc(const pem: RawUtf8; out pub: TEccPublicKey): boolean; overload;

/// cipher a raw ECC secp256r1 private key buffer into some binary
// - encryption uses safe PBKDF2 HMAC-SHA256 AES-CTR-128 and AF-32 algorithms
// - as used by pemSynopseEccEncryptedPrivateKey format and EccPrivateKeyDecrypt()
function EccPrivateKeyEncrypt(const Input: TEccPrivateKey;
  const PrivatePassword: SpiUtf8): RawByteString;

/// uncipher some binary into a raw ECC secp256r1 private key buffer
// - encryption uses safe PBKDF2 HMAC-SHA256 AES-CTR-128 and AF-32 algorithms
// - as used by pemSynopseEccEncryptedPrivateKey format and EccPrivateKeyEncrypt()
function EccPrivateKeyDecrypt(const Input: RawByteString;
  const PrivatePassword: SpiUtf8): RawByteString;


{ ***************** IProtocol Implemented using our Public Key Cryptography }

{
  On FPC x86_64, from our regression tests:
  - ECDHE stream protocol: 54,228 assertions passed  916.75ms
    100 efAesCrc128 in 1.57ms i.e. 63,371/s, aver. 15us, 1.1 GB/s
    100 efAesCfb128 in 1.65ms i.e. 60,313/s, aver. 16us, 1 GB/s
    100 efAesOfb128 in 2.51ms i.e. 39,840/s, aver. 25us, 734.5 MB/s
    100 efAesCtr128 in 833us i.e. 120,048/s, aver. 8us, 2.1 GB/s
    100 efAesCbc128 in 2.69ms i.e. 37,119/s, aver. 26us, 684.4 MB/s
    100 efAesCrc256 in 2.13ms i.e. 46,816/s, aver. 21us, 863.2 MB/s
    100 efAesCfb256 in 2.17ms i.e. 45,998/s, aver. 21us, 848.1 MB/s
    100 efAesOfb256 in 3.35ms i.e. 29,788/s, aver. 33us, 549.2 MB/s
    100 efAesCtr256 in 1.04ms i.e. 95,328/s, aver. 10us, 1.7 GB/s
    100 efAesCbc256 in 3.30ms i.e. 30,266/s, aver. 33us, 558 MB/s
    100 efAesGcm128 in 1.04ms i.e. 95,328/s, aver. 10us, 1.7 GB/s
    100 efAesGcm256 in 1.26ms i.e. 79,365/s, aver. 12us, 1.4 GB/s
    100 efAesCtc128 in 741us i.e. 134,952/s, aver. 7us, 2.4 GB/s
    100 efAesCtc256 in 954us i.e. 104,821/s, aver. 9us, 1.8 GB/s
  - if mormot.core.openssl is included, AES-GCM is even faster:
    100 efAesGcm128 in 721us i.e. 138,696/s, aver. 7us, 2.4 GB/s
    100 efAesGcm256 in 924us i.e. 108,225/s, aver. 9us, 1.9 GB/s

  On FPC i386, from our regression tests:
    100 efAesCrc128 in 2.31ms i.e. 43,159/s, aver. 23us, 795.7 MB/s
    100 efAesCfb128 in 2.40ms i.e. 41,580/s, aver. 24us, 766.6 MB/s
    100 efAesOfb128 in 3.17ms i.e. 31,525/s, aver. 31us, 581.2 MB/s
    100 efAesCtr128 in 4.08ms i.e. 24,497/s, aver. 40us, 451.6 MB/s
    100 efAesCbc128 in 5.45ms i.e. 18,341/s, aver. 54us, 338.1 MB/s
    100 efAesCrc256 in 2.95ms i.e. 33,829/s, aver. 29us, 623.7 MB/s
    100 efAesCfb256 in 2.95ms i.e. 33,863/s, aver. 29us, 624.3 MB/s
    100 efAesOfb256 in 4.02ms i.e. 24,826/s, aver. 40us, 457.7 MB/s
    100 efAesCtr256 in 4.91ms i.e. 20,333/s, aver. 49us, 374.9 MB/s
    100 efAesCbc256 in 6.68ms i.e. 14,970/s, aver. 66us, 276 MB/s
    100 efAesGcm128 in 5.54ms i.e. 18,040/s, aver. 55us, 332.6 MB/s
    100 efAesGcm256 in 6.48ms i.e. 15,427/s, aver. 64us, 284.4 MB/s
    100 efAesCtc128 in 3.53ms i.e. 28,320/s, aver. 35us, 522.1 MB/s
    100 efAesCtc256 in 4.34ms i.e. 23,036/s, aver. 43us, 424.7 MB/s
  - if mormot.core.openssl is included, AES-CTR and AES-GCM are much faster:
    100 efAesCtr128 in 1.67ms i.e. 59,630/s, aver. 16us, 1 GB/s
    100 efAesCtr256 in 1.88ms i.e. 53,134/s, aver. 18us, 0.9 GB/s
    100 efAesGcm128 in 1.72ms i.e. 57,937/s, aver. 17us, 1 GB/s
    100 efAesGcm256 in 2.02ms i.e. 49,455/s, aver. 20us, 911.8 MB/s

  Using ECDHEPROT_EF2MAC[] so weak Crc32c but for AesCrc/AesCtc and AesGcm
  which have their own stronger MAC computation (CRC32C+AES or GMAC).

  -> Default efAesCrc128 is safe and fast, but on x86_64 or with OpenSSL
     efAesGcm128/efAesGcm256 would be both stronger and faster.
}


type
  /// the Authentication schemes recognized by TEcdheProtocol
  // - specifying the authentication allows a safe one-way handshake
  TEcdheAuth = (
    authMutual,
    authServer,
    authClient);

  /// set of Authentication schemes recognized by TEcdheProtocolServer
  TEcdheAuths = set of TEcdheAuth;

  /// the Key Derivation Functions recognized by TEcdheProtocol
  // - for TEcdheProtocol.SharedSecret to compute ephemeral EF and MAC secrets
  // - only HMAC SHA-256 safe algorithm is proposed currently
  TEcdheKdf = (
    kdfHmacSha256);

  /// the Encryption Functions recognized by TEcdheProtocol
  // - all supported AES chaining blocks have their 128-bit and 256-bit flavours
  // - default efAesCrc128 (or its 256-bit efAesCrc256) will use the TAesCfc
  // class, i.e. AES-CFB encryption with on-the-fly 256-bit CRC computation of
  // the plain and encrypted blocks, and AES-encryption of the CRC to ensure
  // cryptographic level message authentication and integrity - associated
  // TEcdheMac property should be macDuringEF
  // - efAesGcm128/efAesGcm256 will use TAesGcm and macDuringEF and the proven
  // AES-GCM algorithm - on x86_64 or if OpenSSL is enabled, you should favor it
  // - efAesCtc128/efAesCtc256 is TAesCtc, so around 2.4GB/s on x86_64, with
  // 256-bit macDuringEF authentication of both plain and ciphered content
  // - other values will define TAesCfb/TAesOfb/TAesCtr/TAesCbc in 128-bit or
  // 256-bit mode, in conjunction with a TEcdheMac setting
  // - of course, weack ECB mode is not available
  // - adding any new ciphers to this enum needs to keep the existing order
  TEcdheEF = (
    efAesCrc128,
    efAesCfb128,
    efAesOfb128,
    efAesCtr128,
    efAesCbc128,
    efAesCrc256,
    efAesCfb256,
    efAesOfb256,
    efAesCtr256,
    efAesCbc256,
    efAesGcm128,
    efAesGcm256,
    efAesCtc128,
    efAesCtc256);

  /// the Message Authentication Codes recognized by TEcdheProtocol
  // - default macDuringEF (680MB/s for efAesCrc128 with SSE4.2 and AES-NI)
  // means that no separated MAC is performed, but done during encryption step:
  // supported by efAesCrc128/256, efAesCtc128/256 and efAesGcm128/efAesGcm256
  // - macHmacSha256 is the safest, but slow, especially when used as MAC for
  // AES-NI accellerated encryption (110MB/s with efAesCfb128, to be compared
  // with macDuringEF, which produces a similar level of MAC)
  // - macHmacCrc256c and macHmacCrc32c are faster (550-650MB/s with efAesCfb128),
  // and prevent transmission errors but not message integrity or authentication
  // since composition of two crcs is a multiplication by a polynomial - see
  // http://mslc.ctf.su/wp/boston-key-party-ctf-2016-hmac-crc-crypto-5pts
  // - macXxHash32 will use the xxhash32() algorithm, fastest without SSE4.2
  // - macNone (800MB/s, which is the speed of AES-NI encryption itself for a
  // random set of small messages) won't check errors, but only replay attacks
  TEcdheMac = (
    macDuringEF,
    macHmacSha256,
    macHmacCrc256c,
    macHmacCrc32c,
    macXxHash32,
    macNone);

  /// defines one protocol Algorithm recognized by TEcdheProtocol
  // - only safe and strong parameters are allowed, and the default values
  // (i.e. all fields set to 0) will ensure a very good combination
  // - in current implementation, there is no negociation between nodes:
  // client and server should have the very same algorithm
  TEcdheAlgo = packed record
    /// the current Authentication scheme
    auth: TEcdheAuth;
    /// the current Key Derivation Function
    kdf: TEcdheKdf;
    /// the current Encryption Function
    ef: TEcdheEF;
    /// the current Message Authentication Code
    mac: TEcdheMac;
  end;

  /// points to one protocol Algorithm recognized by TEcdheProtocol
  PECDHEAlgo = ^TEcdheAlgo;

  /// the binary handshake message, sent by client to server
  // - the frame will always have the same fixed size of 290 bytes (i.e. 388
  // base64-encoded chars, which could be transmitted in a HTTP header),
  // for both mutual or unilateral authentication
  // - ephemeral keys may be included for perfect forward security
  TEcdheFrameClient = packed record
    /// expected algorithm used
    Algo: TEcdheAlgo;
    /// a client-generated random seed
    RndA: THash128;
    /// client public key, with its certificate
    // - may be zero, in case of unilateral authentication (algo=authServer)
    QCA: TEccCertificateContentV1;
    /// client-generated ephemeral public key
    // - may be zero, in case of unilateral authentication (algo=authClient)
    QE: TEccPublicKey;
    /// SHA-256 + ECDSA secp256r1 signature of the previous fields, computed
    // with the client private key
    // - i.e. ECDSASign(dA,sha256(algo|RndA|QCA|QE))
    // - may be zero, in case of unilateral authentication (algo=authServer)
    Sign: TEccSignature;
  end;

  /// the binary handshake message, sent back from server to client
  // - the frame will always have the same fixed size of 306 bytes (i.e. 408
  // base64-encoded chars, which could be transmitted in a HTTP header),
  // for both mutual or unilateral authentication
  // - ephemeral keys may be included for perfect forward security
  TEcdheFrameServer = packed record
    /// algorithm used by the server
    Algo: TEcdheAlgo;
    /// client-generated random seed
    RndA: THash128;
    /// a server-generated random seed
    RndB: THash128;
    /// server public key, with its certificate
    // - may be zero, in case of unilateral authentication (algo=authClient)
    QCB: TEccCertificateContentV1;
    /// server-generated ephemeral public key
    // - may be zero, in case of unilateral authentication (algo=authServer)
    QF: TEccPublicKey;
    /// SHA-256 + ECDSA secp256r1 signature of the other fields, computed
    // with the server private key
    // - i.e. ECDSASign(dB,sha256(algo|RndA|RndB|QCB|QF))
    // - may be zero, in case of unilateral authentication (algo=authClient)
    Sign: TEccSignature;
  end;


function ToText(algo: TEcdheAuth): PShortString; overload;
function ToText(algo: TEcdheKdf): PShortString; overload;
function ToText(algo: TEcdheEF): PShortString; overload;
function ToText(algo: TEcdheMac): PShortString; overload;

type
  /// abstract ECDHE secure protocol with unilateral or mutual authentication
  // - inherited TEcdheProtocolClient and TEcdheProtocolServer
  // classes will implement a secure client/server transmission, with a one-way
  // handshake and asymmetric encryption via public/private key pairs
  // - will validate ECDSA signatures using certificates of the associated PKI
  // - will create an ephemeral ECC key pair for perfect forward security
  // - will use ECDH to compute a shared ephemeral session on both sides,
  // for AES-128 or AES-256 encryption, and HMAC with anti-replay - default
  // algorithm will use fast and safe AES-CTR 128-bit encryption, with efficient
  // AES-CRC 256-bit MAC, and full hardware accelleration on Intel CPUs
  TEcdheProtocol = class(TInterfacedObjectLocked, IProtocol)
  protected
    fPKI: TEccCertificateChain;
    fPrivate: TEccCertificateSecret;
    fAlgo: TEcdheAlgo;
    fEFSalt: RawByteString;
    fMacSalt: RawByteString;
    fOwned: set of (ownPKI, ownPrivate);
    fCertificateValidity: TEccValidity;
    fRndA, fRndB: THash128;
    // RX/TX AES encryption engines
    fAes: array[boolean] of TAesAbstract;
    // RX/TX sequence numbers against replay attack
    fkM: array[boolean] of THash256Rec;
    procedure SetIVAndMacNonce(aEncrypt: boolean);
    procedure IncKM(aEncrypt: boolean);
      {$ifdef HASINLINE} inline; {$endif}
    procedure ComputeMAC(aEncrypt: boolean; aEncrypted: pointer; aLen: integer;
      out aMAC: THash256Rec);
    // raw ECDHE functions used by ProcessHandshake
    function Verify(frame: PByteArray; len: integer;
      const QC: TEccCertificateContentV1; out res: TProtocolResult): boolean;
    procedure Sign(frame: PByteArray; len: integer; out QC: TEccCertificateContentV1);
    procedure SharedSecret(sA, sB: PHash256);
  public
    /// initialize the ECDHE protocol with a PKI and a private secret key
    // - if aPKI is not set, the certificates won't be validated and the
    // protocol will allow self-signed credentials unless FromKeySetCA() was set
    // - aPrivate should always be set for mutual or unilateral authentication
    // - will implement unilateral authentication if aPrivate=nil for this end
    // - efAesCrc128 is default / mORMot 1.18 compatible at 1.1GB/s, but on
    // x86_64 you may use the faster efAesCtc128 (2.4GB/s) or efAesGcm128
    // (1.7GB/s with our asm or 2.4GB/s with OpenSSL)
    constructor Create(aAuth: TEcdheAuth; aPKI: TEccCertificateChain;
      aPrivate: TEccCertificateSecret; aEF: TEcdheEF = efAesCrc128;
      aPrivateOwned: boolean = false);
        reintroduce; overload; virtual;
    /// will create another instance of this communication protocol
    constructor CreateFrom(aAnother: TEcdheProtocol); virtual;
    /// initialize the communication by exchanging some client/server information
    // - this method should be overriden with the proper implementation
    function ProcessHandshake(const MsgIn: RawUtf8;
      out MsgOut: RawUtf8): TProtocolResult; virtual; abstract;
    /// creates a new TEcdheProtocolClient or TEcdheProtocolServer from a text key
    // - expected layout is values separated by ; with at least trailing 'a=...'
    // e.g. 'a=mutual;e=aesctc128;p=34a2;pw=password;ca=..'
    // - if needed, you can specify 'p=...' as the password file name (searching
    // for first matching unique file name with .private extension in the
    // current directory of in EccKeyFileFolder), and 'pw=...;pr=...' for the
    // associated password protection (password content and rounds)
    // - optional 'ca=..;a=..;k=..;e=..;m=..' switches will match PKI, Auth, KDF,
    // EF and MAC properties of this class instance (triming left lowercase chars)
    // - global value set by FromKeySetCA() is used as PKI, unless 'ca=..' is set
    // (as a .ca file name, or as 'ca=base64,base64' or 'ca="base64","base64"')
    // - a full text key with default values may be:
    // $ a=mutual;k=hmacsha256;e=aescrc128;m=duringef;p=34a2;pw=passwordFor34a2;
    // $ pr=60000;ca=websockets
    // - returns nil if aKey does not match this format, i.e. has no p=..,pw=..
    // - see FromKeyCompute() to generate such a key programatically
    // - see FromPasswordSecureFile() human-friendly alternative
    class function FromKey(const aKey: RawUtf8; aServer: boolean): TEcdheProtocol;
    /// creates a new TEcdheProtocolClient or TEcdheProtocolServer from
    // 'password#xxxx.private' or 'password#/path/to/xxxxxxxxxxxx.private' input
    // - human-friendly alternative to FromKey() powerful/complex layout
    // - returns nil if aPasswordSecureFile was not able to load the file
    // - uses DEFAULT_ECCROUNDS + authMutual + efAesCrc128 as parameters
    // - note: FromKeySetCA() should have been called to set the global PKI
    class function FromPasswordSecureFile(const aPasswordSecureFile: RawUtf8;
      aServer: boolean; aAuth: TEcdheAuth = authMutual; aEF: TEcdheEF = efAesCrc128;
      aRounds: integer = DEFAULT_ECCROUNDS): TEcdheProtocol;
    /// defines the global/default trusted PKI Chain to be used by FromKey
    // - used if the ca=... property is not set in the aKey value
    class procedure FromKeySetCA(aPKI: TEccCertificateChain);
    /// generates a TSynPersistentWithPassword key expected by FromKey()
    // - the .private key file name, and its associated password/rounds should
    // be specified, but for unilateral authentication on the other side
    // - pki should be a .ca file name, 'base64,base64' or '"base64","base64"'
    // - result of this method can be stored directly in a .settings file,
    // to enable the TEcdheProtocol safe protocol for transmission
    class function FromKeyCompute(const privkey, privpassword: RawUtf8;
      privrounds: integer = DEFAULT_ECCROUNDS; const pki: RawUtf8 = '';
      auth: TEcdheAuth = authMutual; kdf: TEcdheKdf = kdfHmacSha256;
      ef: TEcdheEF = efAesCrc128; mac: TEcdheMac = macDuringEF;
      customkey: cardinal = 0): RawUtf8;
    /// finalize the instance
    // - also erase all temporary secret keys, for safety
    destructor Destroy; override;
    /// encrypt a message on one side, ready to be transmitted to the other side
    // - will use the Encryption Function EF, according to the shared secret key
    // - this method is thread-safe
    procedure Encrypt(const aPlain: RawByteString;
      out aEncrypted: RawByteString); virtual;
    /// decrypt a message on one side, as transmitted from the other side
    // - will use the Encryption Function EF, according to the shared secret key
    // - returns sprInvalidMAC in case of wrong aEncrypted input (e.g. packet
    // corruption, MiM or Replay attacks attempts)
    // - this method is thread-safe
    function Decrypt(const aEncrypted: RawByteString;
      out aPlain: RawByteString): TProtocolResult; virtual;
    /// check for any transmission error of the supplied encrypted text
    // - returns sprSuccess if the stored CRC of the encrypted flow matches
    // - returns sprInvalidMAC in case of wrong aEncrypted input
    // - is only implemented for MAC=macDuringEF, otherwise returns sprUnsupported
    // - to be called before Decrypt(), since this later method will change the
    // internal kM[false] sequence number
    function CheckError(const aEncrypted: RawByteString): TProtocolResult; virtual;
    /// will create another instance of this communication protocol
    function Clone: IProtocol;
    /// shared public-key infrastructure, used to validate exchanged certificates
    // - will be used for authenticity validation of ECDSA signatures
    property PKI: TEccCertificateChain
      read fPKI;
    /// the current Authentication scheme
    // - this value on client side should match server's Authorized
    // - this value on server side may change if the client forced another mode
    property Auth: TEcdheAuth
      read fAlgo.auth;
    /// the current Key Derivation Function
    // - this value should match on both client and server sides
    property KDF: TEcdheKdf
      read fAlgo.kdf write fAlgo.kdf;
    /// the current salt, used by the Key Derivation Function KDF to compute the
    // key supplied to the Encryption Function EF
    // - equals 'ecdhesalt' by default
    // - this value should match on both client and server sides
    property EFSalt: RawByteString
      read fEFSalt write fEFSalt;
    /// the current Encryption Function
    // - this value should match on both client and server sides
    property EF: TEcdheEF
      read fAlgo.ef write fAlgo.ef;
    /// the current salt, used by the Key Derivation Function KDF to compute the
    // key supplied to the Message Authentication Code MAC
    // - equals 'ecdhemac' by default
    // - this value should match on both client and server sides
    property MACSalt: RawByteString
      read fMacSalt write fMacSalt;
    /// the current Message Authentication Code
    // - this value should match on both client and server sides
    property MAC: TEcdheMac
      read fAlgo.mac write fAlgo.mac;
    /// after handshake, contains the information about the other side
    // public key certificate validity, against the shared PKI
    property CertificateValidity: TEccValidity
      read fCertificateValidity;
  end;

  /// meta-class of the TEcdheProtocol type
  TEcdheProtocolClass = class of TEcdheProtocol;

  /// implements ECDHE secure protocol on client side
  TEcdheProtocolClient = class(TEcdheProtocol)
  protected
    fdE: TEccPrivateKey;
  public
    /// initialize the ECDHE protocol on the client side
    // - will check that aAuth is compatible with the supplied aPKI/aPrivate
    constructor Create(aAuth: TEcdheAuth; aPKI: TEccCertificateChain;
      aPrivate: TEccCertificateSecret; aEF: TEcdheEF = efAesCrc128;
      aPrivateOwned: boolean = false); override;
    /// generate the authentication frame sent from the client
    procedure ComputeHandshake(out aClient: TEcdheFrameClient);
    /// validate the authentication frame sent back by the server
    function ValidateHandshake(const aServer: TEcdheFrameServer): TProtocolResult;
    /// initialize the client communication
    // - if MsgIn is '', will call ComputeHandshake
    // - if MsgIn is set, will call ValidateHandshake
    function ProcessHandshake(const MsgIn: RawUtf8;
      out MsgOut: RawUtf8): TProtocolResult; override;
  end;

  /// implements ECDHE secure protocol on server side
  TEcdheProtocolServer = class(TEcdheProtocol)
  protected
    fAuthorized: TEcdheAuths;
  public
    /// initialize the ECDHE protocol on the client side
    // - will check that aAuth is compatible with the supplied aPKI/aPrivate
    constructor Create(aAuth: TEcdheAuth; aPKI: TEccCertificateChain;
      aPrivate: TEccCertificateSecret; aEF: TEcdheEF = efAesCrc128;
      aPrivateOwned: boolean = false); override;
    /// will create another instance of this communication protocol
    constructor CreateFrom(aAnother: TEcdheProtocol); override;
    /// generate the authentication frame corresponding to the client request
    // - may change Auth property if the Client requested another authentication
    // scheme, allowed in Authorized setting and compatible with fPrivate
    function ComputeHandshake(const aClient: TEcdheFrameClient;
      out aServer: TEcdheFrameServer): TProtocolResult;
    /// initialize the server communication
    // - will call ComputeHandshake
    function ProcessHandshake(const MsgIn: RawUtf8;
      out MsgOut: RawUtf8): TProtocolResult; override;
    /// the Authentication Schemes allowed by this server
    // - by default, only the aAuth value specified to Create is allowed
    // - you can set e.g. [authMutual,authServer] for a weaker pattern
    property Authorized: TEcdheAuths
      read fAuthorized write fAuthorized;
  end;


const
  /// the TEcdheProtocol class to create depending on the asymmetric side
  ECDHEPROT_CLASS: array[ {server=} boolean ] of TEcdheProtocolClass = (
    TEcdheProtocolClient,
    TEcdheProtocolServer);

  /// how TEcdheProtocol.SharedSecret initialize the AES engines
  ECDHEPROT_EF2BITS: array[TEcdheEF] of integer = (
    128,  // efAesCrc128
    128,  // efAesCfb128
    128,  // efAesOfb128
    128,  // efAesCtr128
    128,  // efAesCbc128
    256,  // efAesCrc256
    256,  // efAesCfb256
    256,  // efAesOfb256
    256,  // efAesCtr256
    256,  // efAesCbc256
    128,  // efAesGcm128
    256,  // efAesGcm256
    128,  // efAesCtc128
    256); // efAesCtc256

  /// default MAC used for a given Encryption Function
  // - as used by TEcdheProtocol.Create/FromKey for its default MAC
  // - favor performance - you may force macHmacSha256 for cryptographic level
  ECDHEPROT_EF2MAC: array[TEcdheEF] of TEcdheMac = (
    macDuringEF,    // efAesCrc128
    macHmacCrc32c,  // efAesCfb128
    macHmacCrc32c,  // efAesOfb128
    macHmacCrc32c,  // efAesCtr128
    macHmacCrc32c,  // efAesCbc128
    macDuringEF,    // efAesCrc256
    macHmacCrc32c,  // efAesCfb256
    macHmacCrc32c,  // efAesOfb256
    macHmacCrc32c,  // efAesCtr256
    macHmacCrc32c,  // efAesCbc256
    macDuringEF,    // efAesGcm128
    macDuringEF,    // efAesGcm256
    macDuringEF,    // efAesCtc128
    macDuringEF);   // efAesCtc256


{ ********* Registration of our ECC Engine to the TCryptAsym/TCryptCert Factories }

/// compute a new 'syn-es256' ICryptCert instance from DER or PEM input
// - returns nil if the input is not correct or not supported
// - or returns a TCryptCertInternal instance from function TX509.LoadFromDer()
// - called e.g. by TCryptCertCacheInternal
function SynEccLoad(const Cert: RawByteString): ICryptCert;

{
  NOTICE:
  - the algorithms of this unit are available as 'syn-es256-v1', 'syn-es256',
    'syn-store', 'syn-store-nocache'
  - mormot.crypt.secure also exposes CryptCertSyn and CryptStoreSyn globals
  - they use our proprietary TEccCertificate/TEccCertificateSecret format,
    so are NOT compatible with X.509 certificates
  - to work with standards, the mormot.crypt.x509 engine may be preferred
}

type
  /// store a ECC public key in ICryptPublicKey format
  // - using our pure pascal mormot.crypt.ecc256r1 unit
  // - registered in mormot.crypt.secure CryptPublicKey[ckaEcc] factory
  TCryptPublicKeyEcc = class(TCryptPublicKey)
  protected
    fEcc: TEcc256r1VerifyAbstract;
    fEccPub: TEccPublicKey;
    fSubjectPublicKey: RawByteString;
    function VerifyDigest(Sig: pointer; Dig: THash512Rec;
      SigLen, DigLen: integer; Hash: THashAlgo): boolean; override;
  public
    /// finalize this instance
    destructor Destroy; override;
    /// unserialized the public key from most known formats
    function Load(Algorithm: TCryptKeyAlgo;
      const PublicKeySaved: RawByteString): boolean; override;
    /// as used by ICryptCert.GetKeyParams
    function GetParams(out x, y: RawByteString): boolean; override;
    /// use EciesSeal, i.e. encryption with this public key
    function Seal(const Message: RawByteString;
      const Cipher: RawUtf8): RawByteString; override;
  end;

  /// store a secp256r1/prime256v1 private key in ICryptPrivateKey format
  // - using our pure pascal mormot.crypt.ecc256r1 unit
  // - registered in mormot.crypt.secure CryptPrivateKey[ckaEcc] factory
  TCryptPrivateKeyEcc = class(TCryptPrivateKey)
  protected
    fEcc: TEccPrivateKey;
    // decode the ECC private key ASN.1 and check for any associated public key
    function FromDer(algo: TCryptKeyAlgo; const der: RawByteString;
      pub: TCryptPublicKey): boolean; override;
    /// sign a memory buffer digest with ECC using the stored private key
    function SignDigest(const Dig: THash512Rec; DigLen: integer;
      DigAlgo: TCryptAsymAlgo): RawByteString; override;
  public
    /// finalize this instance
    destructor Destroy; override;
    /// create a new private / public key pair
    // - returns the associated public key binary in SubjectPublicKey format
    function Generate(Algorithm: TCryptAsymAlgo): RawByteString; override;
    /// return the private key as raw binary
    // - follow PKCS#8 PrivateKeyInfo encoding for secp256r1/prime256v1
    function ToDer: RawByteString; override;
    /// return the associated public key as stored in a X509 certificate
    function ToSubjectPublicKey: RawByteString; override;
    /// use EciesSeal, i.e. decryption with this private key
    function Open(const Message: RawByteString;
      const Cipher: RawUtf8): RawByteString; override;
    /// compute the shared-secret with another public key
    // - by design, ECDHE is only available for ECC
    function SharedSecret(const PeerKey: ICryptPublicKey): RawByteString;
      override;
  end;


implementation

{ ***************** High-Level Certificate-based Public Key Cryptography }

function ToText(algo: TEciesAlgo): PShortString;
begin
  result := GetEnumName(TypeInfo(TEciesAlgo), ord(algo));
end;

const
  ecaFIRST = succ(low(TEciesAlgo));
  ecaLAST = high(TEciesAlgo);

  /// used by TEccCertificate.Encrypt and TEccCertificateSecret.Decrypt
  ECIES_AES: array[ecaFIRST .. ecaLAST] of TAesMode = (
    mCfb, mCbc, mOfb, mCtr, mCfb, mCbc, mOfb, mCtr,
    mCfb, mCbc, mOfb, mCtr, mCfb, mCbc, mOfb, mCtr,
    mGcm, mGcm, mGcm, mGcm, mCtc, mCtc, mCtc, mCtc);

  ECIES_AESSIZE: array[ecaFIRST .. ecaLAST] of integer = (
    256, 256, 256, 256, 256, 256, 256, 256,
    128, 128, 128, 128, 128, 128, 128, 128,
    128, 256, 128, 256, 128, 256, 128, 256);

  ECIES_MAGIC: array[0..1] of array[0..15] of AnsiChar = (
    'SynEccEncrypted'#26,
    'SynEccEncrypt01'#26);

  ECIES_SYNLZ = [
    ecaPBKDF2_HMAC_SHA256_AES256_CFB_SYNLZ ..
      ecaPBKDF2_HMAC_SHA256_AES128_CTR_SYNLZ,
    ecaPBKDF2_AES128_GCM_SYNLZ .. ecaPBKDF2_AES256_GCM_SYNLZ,
    ecaPBKDF2_AES128_CTC_SYNLZ .. ecaPBKDF2_AES256_CTC_SYNLZ];

  ECIES_AEAD = [ecaPBKDF2_AES128_GCM .. ecaPBKDF2_AES256_CTC_SYNLZ];

  ECIES_WITHOUTSYNLZ: array[ecaFIRST .. ecaLAST] of TEciesAlgo = (
    ecaPBKDF2_HMAC_SHA256_AES256_CFB, ecaPBKDF2_HMAC_SHA256_AES256_CBC,
    ecaPBKDF2_HMAC_SHA256_AES256_OFB, ecaPBKDF2_HMAC_SHA256_AES256_CTR,
    ecaPBKDF2_HMAC_SHA256_AES256_CFB, ecaPBKDF2_HMAC_SHA256_AES256_CBC,
    ecaPBKDF2_HMAC_SHA256_AES256_OFB, ecaPBKDF2_HMAC_SHA256_AES256_CTR,
    ecaPBKDF2_HMAC_SHA256_AES128_CFB, ecaPBKDF2_HMAC_SHA256_AES128_CBC,
    ecaPBKDF2_HMAC_SHA256_AES128_OFB, ecaPBKDF2_HMAC_SHA256_AES128_CTR,
    ecaPBKDF2_HMAC_SHA256_AES128_CFB, ecaPBKDF2_HMAC_SHA256_AES128_CBC,
    ecaPBKDF2_HMAC_SHA256_AES128_OFB, ecaPBKDF2_HMAC_SHA256_AES128_CTR,
    ecaPBKDF2_AES128_GCM, ecaPBKDF2_AES256_GCM,
    ecaPBKDF2_AES128_GCM, ecaPBKDF2_AES256_GCM,
    ecaPBKDF2_AES128_CTC, ecaPBKDF2_AES128_CTC,
    ecaPBKDF2_AES128_CTC, ecaPBKDF2_AES128_CTC);

type
  TEciesFeatures = set of (
    efMetaData);

function EciesLevel(const head: TEciesHeader): integer;
  // inline; defeats Delphi optimizer for IsEqual()
begin
  for result := 0 to high(ECIES_MAGIC) do
    if IsEqual(head.magic, THash128(ECIES_MAGIC[result])) then
      exit;
  result := -1;
end;

function EciesFeatures(const head: TEciesHeader): TEciesFeatures;
var
  level: integer;
begin
  byte(result) := 0;
  level := EciesLevel(head);
  if level > 0 then
    include(result, efMetaData);
end;

function EciesHeader(const head: TEciesHeader): boolean;
begin
  result := (EciesLevel(head) >= 0) and
    (head.Algo in [ecaFIRST .. ecaLAST]) and
    (head.crc = crc32c(PCardinal(@head.hmac)^, @head, SizeOf(head) - SizeOf(head.crc)));
end;

function EciesHeader(const encrypted: RawByteString; out head: TEciesHeader): boolean;
begin
  result := (length(encrypted) > SizeOf(head)) and
    EciesHeader(PEciesHeader(encrypted)^);
  if result then
    head := PEciesHeader(encrypted)^;
end;

function EciesHeaderFile(const encryptedfile: TFileName; out head: TEciesHeader;
  const rawencryptedfile: TFileName): boolean;
var
  F: THandle;
  len: PtrInt;
  size: Int64;
  tmp: RawByteString;
begin
  result := false;
  if encryptedfile = '' then
    exit;
  F := FileOpenSequentialRead(encryptedfile);
  if not ValidHandle(F) then
    exit;
  if FileRead(F, head, SizeOf(head)) = SizeOf(head) then
    result := EciesHeader(head);
  if result and
     (rawencryptedfile <> '') then
  begin
    size := FileSize(F);
    if size < MaxInt then // FileReadAll() is limited to 2GB
    begin
      len := size - SizeOf(head);
      FastNewRawByteString(tmp, len);
      result := FileReadAll(F, pointer(tmp), len) and
                FileFromString(tmp, rawencryptedfile);
    end;
  end;
  FileClose(F);
end;

function EciesHeaderText(const head: TEciesHeader): RawUtf8;
var
  s: variant;
  sign: TEccSignatureCertified;
begin
  sign := TEccSignatureCertified.CreateFrom(head.sign, true);
  try
    if sign.Check then
    begin
      s := sign.ToVariant;
      _ObjAddPropU('ECDSA', EccText(head.sign.Signature), s);
    end;
  finally
    sign.Free;
  end;
  with head do
    FormatUtf8('{"Date":"%","Size":%,"Recipient":"%","RecipientSerial":"%",' +
      '"FileTime":"%","Algorithm":"%","RandomPublicKey":"%","HMAC":"%",' +
      '"Signature":%,"Meta":%}', [EccText(date), size, EccText(rec),
      EccText(recid), DateTimeToIso8601Text(UnixTimeToDateTime(unixts)),
      ToText(algo)^, mormot.core.text.BinToHex(@rndpub, SizeOf(rndpub)),
      Sha256DigestToString(hmac), _Safe(s)^.ToJson,
      BOOL_STR[efMetaData in EciesFeatures(head)]], result);
end;

function EciesHeaderText(const encryptedfile, rawencryptedfile: TFileName): RawUtf8;
var
  h: TEciesHeader;
begin
  if EciesHeaderFile(encryptedfile, h, rawencryptedfile) then
    result := EciesHeaderText(h)
  else
    result := '';
end;

function EciesSeal(aes: TAesAbstractClass; aesbits: integer;
  const pubkey: TEccPublicKey; const msg: RawByteString): RawByteString;
var
  ephpub: TEccPublicKey;
  ephprv: TEccPrivateKey;
  ephsec: TEccSecretKey;
  a: TAesAbstract;
  l, o: integer;
  p: PEccPublicKey;
  secret: THash512Rec; // uses 256-bit or 384-bit of it
  sha3: TSha3;
begin
  result := '';
  l := length(msg);
  if (aes = nil) or
     (l = 0) or
     IsZero(pubkey) or
     not Ecc256r1MakeKey(ephpub, ephprv) or
     not Ecc256r1SharedSecret(pubkey, ephprv, ephsec) then
    exit;
  sha3.Full(@ephsec, SizeOf(ephsec), secret.b);
  a := aes.Create(secret.l, aesbits); // use 128-bit or 256-bit of secret.l
  try
    a.IV := secret.h.Lo; // use 128-bit of secret.h
    o := a.EncryptPkcs7Length(l, {withiv=}false);
    FastNewRawByteString(result, o + SizeOf(ephpub));
    p := pointer(result);
    p^ := ephpub;
    inc(p);
    if not a.EncryptPkcs7Buffer(pointer(msg), p, l, o, false) then
      result := '';
  finally
    a.Free;
    FillZero(secret.b);
    FillZero(ephprv);
    FillZero(ephsec);
  end;
end;

function EciesOpen(aes: TAesAbstractClass; aesbits: integer;
  const privkey: TEccPrivateKey; const msg: RawByteString): RawByteString;
var
  l: integer;
  ephsec: TEccSecretKey;
  secret: THash512Rec;
  a: TAesAbstract;
  p: PEccPublicKey;
  sha3: TSha3;
begin
  p := pointer(msg);
  l := length(msg);
  result := '';
  if (aes = nil) or
     (l <= SizeOf(p^)) or
     IsZero(privkey) or
     not Ecc256r1SharedSecret(p^, privkey, ephsec) then
    exit;
  inc(p);
  sha3.Full(@ephsec, SizeOf(ephsec), secret.b);
  a := aes.Create(secret.l, aesbits);
  try
    a.IV := secret.h.Lo;
    result := a.DecryptPkcs7Buffer(p, l - SizeOf(p^), false, false);
  finally
    a.Free;
    FillZero(secret.b);
    FillZero(ephsec);
  end;
end;

function EciesSeal(const cipher: RawUtf8; const pubkey: TEccPublicKey;
  const msg: RawByteString): RawByteString;
var
  mode: TAesMode;
  bits: integer;
begin
  if AesAlgoNameDecode(pointer(cipher), mode, bits) then
    result := EciesSeal(TAesFast[mode], bits, pubkey, msg)
  else
    result := '';
end;

function EciesOpen(const cipher: RawUtf8; const privkey: TEccPrivateKey;
  const msg: RawByteString): RawByteString;
var
  mode: TAesMode;
  bits: integer;
begin
  if AesAlgoNameDecode(pointer(cipher), mode, bits) then
    result := EciesOpen(TAesFast[mode], bits, privkey, msg)
  else
    result := '';
end;

var
  _EccKeyFileFolder: TFileName;

const
  {$ifdef OSWINDOWS}
  _KEYFILEFOLDER = 'Synopse\Keys\';
  {$else}
  _KEYFILEFOLDER = '.synopse/keys/';
  {$endif OSWINDOWS}

function EccKeyFileFolder: TFileName;
begin
  result := _EccKeyFileFolder;
  if result <> '' then
    exit;
  result := EnsureDirectoryExists(GetSystemPath(spUserData) + _KEYFILEFOLDER);
  _EccKeyFileFolder := result;
end;

function EccKeyFileFind(var TruncatedFileName: TFileName; privkey: boolean): boolean;
var
  match: TFindFilesDynArray;
  ext, mask, fn: TFileName;
begin
  result := true;
  if FileExists(TruncatedFileName) then
    exit;
  ext := ECCCERTIFICATE_FILEEXT[privkey];
  if ExtractFileExt(TruncatedFileName) <> ext then
  begin
    fn := TruncatedFileName + ext;
    if FileExists(fn) then
    begin
      TruncatedFileName := fn;
      exit;
    end;
  end;
  mask := ExtractFileName(TruncatedFileName) + '*' + ext;
  match := FindFiles(ExtractFilePath(TruncatedFileName), mask);
  if length(match) <> 1 then
    match := FindFiles(EccKeyFileFolder, mask);
  if (length(match) <> 1) or
     (match[0].Size < SizeOf(TEccCertificateContentV1)) then
    result := false
  else
    TruncatedFileName := match[0].Name;
end;

function EciesKeyFileFind(const encrypted: RawByteString; out keyfile: TFileName;
  privkey: boolean): boolean;
var
  head: TEciesHeader;
begin
  result := EciesHeader(encrypted, head);
  if result then
  begin
    Utf8ToFileName(EccText(head.recid), keyfile);
    result := EccKeyFileFind(keyfile, privkey);
  end;
end;

function EccToDer(const sign: TEccSignature): RawByteString;
var
  der: array[0..511] of AnsiChar;
  len: PtrInt;
begin
  if IsZero(sign) then
    len := 0
  else
  begin
    len := DerAppend(DerAppend(@der[2], @sign[0], ECC_BYTES),
                @sign[ECC_BYTES], ECC_BYTES) - PAnsiChar(@der);
    der[0] := AnsiChar(ASN1_SEQ); // SEQ of ECDSA's R and S integers
    der[1] := AnsiChar(len - 2);
  end;
  FastSetRawByteString(result{%H-}, @der, len);
end;

function EccToDer(const priv: TEccPrivateKey): RawByteString;
var
  der: array[0..511] of AnsiChar;
  len: PtrInt;
begin
  if IsZero(priv) then
    len := 0
  else
  begin
    len := DerAppend(@der[2], @priv, SizeOf(priv)) - PAnsiChar(@der);
    der[0] := AnsiChar(ASN1_SEQ);
    der[1] := AnsiChar(len - 2);
  end;
  FastSetRawByteString(result{%H-}, @der, len);
end;

function EccToDer(const pub: TEccPublicKey): RawByteString;
var
  der: array[0..511] of AnsiChar;
  len: PtrInt;
begin
  if IsZero(pub) then
    len := 0
  else
  begin
    len := DerAppend(@der[2], @pub, SizeOf(pub)) - PAnsiChar(@der);
    der[0] := AnsiChar(ASN1_SEQ);
    der[1] := AnsiChar(len - 2);
  end;
  FastSetRawByteString(result{%H-}, @der, len);
end;

function DerToEcc(der: PByteArray; derlen: PtrInt; out sign: TEccSignature): boolean;
begin
  if (derlen < 50) or
     (der[0] <> ASN1_SEQ) or
     (der[1] > derlen - 2) then
    result := false
  else
    result := DerParse(DerParse(@der[2], @sign[0], ECC_BYTES),
      @sign[ECC_BYTES], ECC_BYTES) = PAnsiChar(@der[der[1] + 2]);
end;

function DerToEcc(der: PByteArray; derlen: PtrInt; out priv: TEccPrivateKey): boolean;
begin
  if (derlen < 30) or
     (der[0] <> ASN1_SEQ) or
     (der[1] > derlen - 2) then
    result := false
  else
    result := DerParse(@der[2], @priv[0], SizeOf(priv)) = PAnsiChar(@der[der[1] + 2]);
end;

function DerToEcc(der: PByteArray; derlen: PtrInt; out pub: TEccPublicKey): boolean;
begin
  if (derlen < 30) or
     (der[0] <> ASN1_SEQ) or
     (der[1] > derlen - 2) then
    result := false
  else
    result := DerParse(@der[2], @pub[0], SizeOf(pub)) = PAnsiChar(@der[der[1] + 2]);
end;

function PemDerRawToEcc(const pem: RawUtf8; out priv: TEccPrivateKey): boolean; overload;
var
  der, key: RawByteString;
begin
  der := PemToDer(pem); // return input if not PEM (assume was DER)
  result := DerToEcc(pointer(der), length(der), priv);
  if not result then
    if length(der) = SizeOf(priv) then
    begin
      priv := PEccPrivateKey(der)^; // accept key in raw, PEM or DER format
      result := true;
    end
    else
    begin
      // decode prime256v1 PKCS#8 PrivateKeyInfo as generated by OpenSSL
      key := SeqToEccPrivKey(ckaEcc256, der);
      if length(key) = SizeOf(priv) then
      begin
        priv := PEccPrivateKey(key)^;
        result := true;
      end;
    end;
  FillZero(der); // anti-forensic protection of the private key
  FillZero(key);
end;

function PemDerRawToEcc(const pem: RawUtf8; out pub: TEccPublicKey): boolean; overload;
var
  der: RawByteString;
begin
  result := false;
  der := PemToDer(pem);
  if not DerToEcc(pointer(der), length(der), pub) then
    if length(der) = SizeOf(pub) then
      pub := PEccPublicKey(der)^
    else if not Ecc256r1CompressAsn1(SeqToEccPubKey(ckaEcc256, der), pub) then
      exit;
  result := true; // accept key in raw, PEM or DER format
end;

function PemDerRawToEcc(const pem: RawUtf8; out sig: TEccSignature): boolean; overload;
var
  der: RawByteString;
begin
  result := false;
  der := PemToDer(pem);
  if der = '' then
    exit;
  if not DerToEcc(pointer(der), length(der), sig) then
    if length(der) = SizeOf(sig) then
      sig := PEccSignature(der)^
    else
      exit; // accept signature in raw, PEM or DER format
  result := true;
end;


{ TEccCertificate }

constructor TEccCertificate.Create;
begin
  inherited Create; // may have been overriden
  fContent.Head.Version := 1;
  fMaxVersion := 1;
end;

constructor TEccCertificate.CreateVersion(MaxVers: integer);
begin
  Create;
  fMaxVersion := MaxVers;
end;

constructor TEccCertificate.CreateFromBase64(const base64: RawUtf8);
begin
  Create;
  if not FromBase64(base64) then
    EEccException.RaiseUtf8('Invalid %.CreateFromBase64', [self]);
end;

constructor TEccCertificate.CreateFromAuth(const AuthPubKey: TFileName;
  const AuthBase64, AuthSerial: RawUtf8);
begin
  Create;
  if not FromAuth(AuthPubKey, AuthBase64, AuthSerial) then
    EEccException.RaiseUtf8('Invalid %.CreateFromAuth', [self]);
end;

function TEccCertificate.GetAuthorityIssuer: RawUtf8;
begin
  result := EccText(fContent.Head.Signed.AuthorityIssuer);
end;

function TEccCertificate.GetAuthoritySerial: RawUtf8;
begin
  result := EccText(fContent.Head.Signed.AuthoritySerial);
end;

function TEccCertificate.GetIssueDate: RawUtf8;
begin
  result := EccText(fContent.Head.Signed.IssueDate);
end;

function TEccCertificate.GetIssuer: RawUtf8;
begin
  result := EccText(fContent.Head.Signed.Issuer);
end;

function TEccCertificate.GetSerial: RawUtf8;
begin
  result := EccText(fContent.Head.Signed.Serial);
end;

function TEccCertificate.GetValidityEnd: RawUtf8;
begin
  result := EccText(fContent.Head.Signed.ValidityEnd);
end;

function TEccCertificate.GetValidityStart: RawUtf8;
begin
  result := EccText(fContent.Head.Signed.ValidityStart);
end;

function TEccCertificate.GetUsage: TCryptCertUsages;
begin
  result := TCryptCertUsages(word(fContent.GetUsage));
end;

function TEccCertificate.GetSubject: RawUtf8;
begin
  result := fContent.GetSubject;
end;

function TEccCertificate.GetIsSelfSigned: boolean;
begin
  result := (self <> nil) and
            fContent.IsSelfSigned;
end;

function TEccCertificate.CheckCRC: boolean;
begin
  result := (self <> nil) and
            fContent.Check;
end;

function TEccCertificate.FromBase64(const base64: RawUtf8): boolean;
begin
  result := LoadFromBinary(Base64ToBinSafe(base64));
end;

function TEccCertificate.FromJson(const json: RawUtf8): boolean;
var
  tmp: TSynTempBuffer;
begin
  result := false;
  if GotoNextNotSpace(pointer(json))^ <> '{' then
    exit;
  tmp.Init(json);
  result := FromBase64(JsonDecode(tmp.buf, 'Base64', nil, true));
  tmp.Done;
end;

function TEccCertificate.LoadFromBinary(const binary: RawByteString): boolean;
var
  st: TRawByteStringStream;
begin
  if binary = '' then
    result := false
  else
  begin
    st := TRawByteStringStream.Create(binary);
    try
      result := LoadFromStream(st) and
                fContent.Check;
    finally
      st.Free;
    end;
  end;
end;

function TEccCertificate.FromFile(const filename: TFileName): boolean;
var
  content: RawUtf8;
begin
  content := StringFromFile(FileName);
  if (content = '') and
     (ExtractFileExt(filename) = '') then
    content := StringFromFile(filename + ECCCERTIFICATEPUBLIC_FILEEXT);
  if content = '' then
    result := false
  else if GotoNextNotSpace(pointer(content))^ = '{' then
    result := FromBase64(JsonDecode(content, 'Base64', nil, true))
  else
    result := LoadFromBinary(content);
end;

function TEccCertificate.FromAuth(const AuthPubKey: TFileName;
  const AuthBase64, AuthSerial: RawUtf8): boolean;
var
  authfilename: TFileName;
begin
  result := true;
  if FromFile(AuthPubKey) or
     FromBase64(AuthBase64) then
    exit;
  if AuthSerial <> '' then
  begin
    Utf8ToFileName(AuthSerial, authfilename);
    if EccKeyFileFind(authfilename, false) and
       FromFile(authfilename) then
      exit;
  end;
  result := false;
end;

procedure TEccCertificate.SetBase64(const base64: RawUtf8);
begin
  FromBase64(base64);
end;

function TEccCertificate.SaveToBinary(PublicKeyOnly: boolean): RawByteString;
var
  st: TRawByteStringStream;
  sav: boolean;
begin
  result := '';
  sav := fStoreOnlyPublicKey;
  st := TRawByteStringStream.Create;
  try
    fStoreOnlyPublicKey := PublicKeyOnly;
    if SaveToStream(st) then
      result := st.DataString;
  finally
    fStoreOnlyPublicKey := sav;
    st.Free;
  end;
end;

function TEccCertificate.ToBase64: RawUtf8;
begin
  result := BinToBase64(SaveToBinary);
end;

function TEccCertificate.PublicToBase64: RawUtf8;
begin
  result := BinToBase64(SaveToBinary({publickeyonly=}true));
end;

function TEccCertificate.IsEqual(another: TEccCertificate): boolean;
begin
  result := (self <> nil) and
            (another <> nil) and
            self.fContent.FieldsEqual(another.fContent);
end;

function TEccCertificate.LoadFromStream(Stream: TStream): boolean;
begin
  result := fContent.LoadFromStream(Stream, fMaxVersion) and
            AppendLoad(ReadStringFromStream(Stream, 524288));
  if result and
     (@Ecc256r1Verify = @ecdsa_verify_pas) then
  begin
    // OpenSSL Ecc256r1VerifyUncomp() is actually slower than Ecc256r1Verify()
    Ecc256r1Uncompress(fContent.Head.Signed.PublicKey, fUncompressed);
    fUncompressedP := @fUncompressed;
  end;
end;

function TEccCertificate.SaveToStream(Stream: TStream): boolean;
begin
  result := CheckCRC and
            fContent.SaveToStream(Stream) and
            WriteStringToStream(Stream, AppendSave);
end;

function TEccCertificate.AppendLoad(const data: RawByteString): boolean;
begin
  result := true; // no additional data by default
end;

function TEccCertificate.AppendSave: RawByteString;
begin
  result := '';
end;

function TEccCertificate.Verify(const hash: THash256;
  const Signature: TEccSignatureCertifiedContent; TimeUtc: TDateTime): TEccValidity;
begin
  if self = nil then
    result := ecvUnknownAuthority
  else if not (cuDigitalSignature in GetUsage) then
    result := ecvWrongUsage
  else
    result := Signature.Verify(hash, fContent, fUncompressedP, TimeUtc);
end;

function TEccCertificate.VerifyCertificate(Authority: TEccCertificate;
  TimeUtc: TDateTime): TEccValidity;
var
  hash: THash256;
begin
  if self = nil then
    result := ecvBadParameter
  else if not fContent.Check then
    result := ecvCorrupted
  else if not fContent.CheckDate(nil, TimeUtc) then
    result := ecvInvalidDate
  else if fContent.IsSelfSigned then
  begin
    Authority := self;
    result := ecvValidSelfSigned;
  end
  else if (Authority = nil) or
          not mormot.crypt.ecc256r1.IsEqual(fContent.Head.Signed.AuthoritySerial,
            Authority.fContent.Head.Signed.Serial) or
          not Authority.fContent.Check then
    result := ecvUnknownAuthority
  else if not Authority.fContent.CheckDate(nil, TimeUtc) then
    result := ecvDeprecatedAuthority
  else if not (cuKeyCertSign in Authority.GetUsage) then
    result := ecvWrongUsage
  else
    result := ecvValidSigned;
  if not (result in ECC_VALIDSIGN) then
    exit;
  fContent.ComputeHash(hash); // sha-256 cryptographic hash
  if fUncompressedP = nil then
  begin
    if not Ecc256r1Verify( // OpenSSL compressed is faster than uncompressed
             Authority.Signed.PublicKey, hash, fContent.Head.Signature) then
      result := ecvInvalidSignature;
  end
  else if not Ecc256r1VerifyUncomp(
                Authority.fUncompressed, hash, fContent.Head.Signature) then
      result := ecvInvalidSignature;
end;

{$ifdef ISDELPHI20062007}
  {$WARNINGS OFF} // circumvent Delphi 2007 false positive warning
{$endif}

function TEccCertificate.Encrypt(const Plain: RawByteString;
  Signature: TEccSignatureCertified; FileDateTime: TDateTime;
  const KDFSalt: RawUtf8; KDFRounds: integer; const MACSalt: RawUtf8;
  MACRounds: integer; Algo: TEciesAlgo): RawByteString;
var
  rndpriv: TEccPrivateKey;
  head: TEciesHeader;
  secret, dec, enc, content: RawByteString;
  aeskey, mackey: THash256Rec;
  c: TAesAbstractClass;
begin
  result := '';
  if Plain = '' then
    exit;
  if not CheckCRC then
    EEccException.RaiseUtf8('%.Encrypt: no public key', [self]);
  if GetUsage * [cuDataEncipherment, cuEncipherOnly] = [] then
    EEccException.RaiseUtf8(
      '%.Encrypt: missing cuDataEncipherment/cuEncipherOnly Usage', [self]);
  if Algo = ecaUnknown then // use safest algorithm by default
    if IsContentCompressed(pointer(Plain), length(Plain)) then
      Algo := ecaPBKDF2_HMAC_SHA256_AES256_CFB
    else
      Algo := ecaPBKDF2_HMAC_SHA256_AES256_CFB_SYNLZ;
  if not (Algo in [ecaFIRST .. ecaLAST]) then
    EEccException.RaiseUtf8('%.Encrypt: unsupported %', [self, ToText(Algo)^]);
  try
    head.magic := THash128(ECIES_MAGIC[0]);
    head.rec := fContent.Head.Signed.Issuer;
    head.recid := fContent.Head.Signed.Serial;
    head.size := length(Plain);
    head.date := NowEccDate;
    head.unixts := DateTimeToUnixTime(FileDateTime);
    content := Plain;
    if Signature.Check then
    begin
      head.sign := Signature.fCertified;
      if Signature.InheritsFrom(TEccSignatureCertifiedFile) then
        with _Safe(TEccSignatureCertifiedFile(Signature).MetaData)^ do
          if IsObject and
             (Count > 0) then
          begin
            head.magic := THash128(ECIES_MAGIC[1]); // indicates efMetaData
            // new format storing {metadata}+#0+plain
            content := ToJson('', '', jsonUnquotedPropNameCompact);
            Append(content, #0); // circumvent FPC compiler error
            Append(content, plain);
          end;
    end
    else
      FillcharFast(head.sign, SizeOf(head.sign), 255); // Version=255=not signed
    if not Ecc256r1MakeKey(head.rndpub, rndpriv) then
      EEccException.RaiseUtf8('%.Encrypt: MakeKey?', [self]);
    FastNewRawByteString(secret, SizeOf(TEccSecretKey));
    if not Ecc256r1SharedSecret(
        fContent.Head.Signed.PublicKey, rndpriv, PEccSecretKey(secret)^) then
      EEccException.RaiseUtf8('%.Encrypt: SharedSecret?', [self]);
    Pbkdf2HmacSha256(secret, KDFSalt, KDFRounds, aeskey.b, 'salt');
    if Algo in ECIES_SYNLZ then
    begin
      dec := AlgoSynLZ.Compress(content);
      if length(dec) > length(content) then
      begin
        // SynLZ was inefficient -> just store, don't compress
        FillZero(dec);
        dec := content;
        Algo := ECIES_WITHOUTSYNLZ[Algo];
      end;
    end
    else
      dec := content;
    head.Algo := Algo;
    if Algo in ECIES_AEAD then
    begin
      // encrypt e.g. with AES-GCM single pass message authentication
      c := TAesFast[ECIES_AES[Algo]];
      case ECIES_AESSIZE[Algo] of
        128:
          enc := c.MacEncrypt(dec, aeskey.Lo, {encrypt=}true);
        256:
          enc := c.MacEncrypt(dec, aeskey.b, true);
      end;
      if length(enc) < SizeOf(TMacAndCryptData) then
        exit;
      // retrieve the GMAC directly from the raw result
      head.hmac := PMacAndCryptData(enc)^.mac;
    end
    else
    begin
      // encrypt with PKCS7 padding
      enc := AesPkcs7(dec, {encrypt=}true, aeskey, ECIES_AESSIZE[Algo], ECIES_AES[Algo]);
      // HMAC of the encrypted content
      Pbkdf2HmacSha256(secret, MACSalt, MACRounds, mackey.b, 'hmac');
      HmacSha256(mackey.b, enc, head.hmac);
    end;
    head.crc := crc32c(PCardinal(@head.hmac)^, @head, SizeOf(head) - SizeOf(head.crc));
    FastNewRawByteString(result, SizeOf(head) + length(enc));
    PEciesHeader(result)^ := head;
    MoveFast(pointer(enc)^, PByteArray(result)[SizeOf(head)], length(enc));
  finally
    FillZero(aeskey.b);
    FillZero(mackey.b);
    FillCharFast(rndpriv, SizeOf(rndpriv), 0);
    if dec <> Plain then
      FillZero(dec);
    if content <> Plain then
      FillZero(content);
    FillZero(secret);
  end;
end;

{$ifdef ISDELPHI20062007}
  {$WARNINGS ON} // circumvent Delphi 2007 false positive warning
{$endif}

function TEccCertificate.EncryptFile(const FileToCrypt: TFileName;
  const DestFile: TFileName; const Salt: RawUtf8; SaltRounds: integer;
  Algo: TEciesAlgo; IncludeSignFile: boolean): boolean;
var
  plain, encrypted: RawByteString;
  cert: TEccSignatureCertified;
  dest: TFileName;
  filetime: TDateTime;
begin
  plain := StringFromFile(FileToCrypt);
  if plain = '' then
    EEccException.RaiseUtf8('File not found: [%]', [FileToCrypt]);
  if DestFile = '' then
    dest := FileToCrypt + ENCRYPTED_FILEEXT
  else
    dest := DestFile;
  filetime := FileAgeToDateTime(FileToCrypt);
  try
    if IncludeSignFile then
      cert := TEccSignatureCertifiedFile.CreateFromFile(FileToCrypt, true)
    else
      cert := nil;
    try
      encrypted := Encrypt(plain, cert, filetime, Salt, SaltRounds, 'hmac', 100, Algo);
      if encrypted = '' then
        result := false
      else
        result := FileFromString(encrypted, dest);
    finally
      cert.Free;
    end;
  finally
    FillZero(plain);
  end;
end;

function TEccCertificate.EncryptMessage(const Plain: RawByteString;
  const Cipher: RawUtf8): RawByteString;
begin
  if CheckCRC and
    (GetUsage * [cuDataEncipherment, cuEncipherOnly] <> []) then
    result := EciesSeal(Cipher, fContent.Head.Signed.PublicKey, Plain)
  else
    result := '';
end;

function TEccCertificate.ToVariant(withBase64: boolean): variant;
var
  u: TCryptCertUsages;
begin
  result := _ObjFast([
    'Version',         Version,
    'Serial',          Serial,
    'Issuer',          Issuer,
    'IssueDate',       IssueDate,
    'ValidityStart',   ValidityStart,
    'ValidityEnd',     ValidityEnd,
    'AuthoritySerial', AuthoritySerial,
    'AuthorityIssuer', AuthorityIssuer,
    'IsSelfSigned',    IsSelfSigned]);
  u := GetUsage;
  if u <> CU_ALL then
    TDocVariantData(result).AddValue(
      'Usage',  SetNameToVariant(word(u), TypeInfo(TCryptCertUsages)));
  if withBase64 then
    TDocVariantData(result).AddValue(
      'Base64', RawUtf8ToVariant(ToBase64));
end;

function TEccCertificate.ToJson(withBase64: boolean): RawUtf8;
begin
  _VariantSaveJson(ToVariant(withBase64), twJsonEscape, result{%H-});
end;

function TEccCertificate.ToFile(const filename: TFileName): boolean;
begin
  if CheckCRC then
    result := JsonReformatToFile(ToJson, filename)
  else
    result := false;
end;

function TEccCertificate.GetDigest(Algo: THashAlgo): RawUtf8;
begin
  result := HashFull(Algo, SaveToBinary({publickeyonly=}true));
end;


{ TEccCertificateSecret }

constructor TEccCertificateSecret.CreateNew(Authority: TEccCertificateSecret;
  const IssuerText: RawUtf8; ExpirationDays: integer; StartDate: TDateTime;
  ParanoidVerify: boolean; Usage: TCryptCertUsages;
  const Subjects: RawUtf8; MaxVers: byte);
var
  now: TEccDate;
begin
  CreateVersion(MaxVers);
  with fContent.Head.Signed do
  begin
    now := NowEccDate;
    IssueDate := now;
    if ExpirationDays > 0 then
    begin
      if StartDate = 0 then
        ValidityStart := now
      else
        ValidityStart := EccDate(StartDate);
      ValidityEnd := ValidityStart + ExpirationDays;
    end;
    TAesPrng.Fill(TAesBlock(Serial));
    fContent.SetUsage(word(Usage), MaxVers);
    if IssuerText = '' then
      if Subjects <> '' then
        fContent.SetSubject(Subjects, MaxVers)
      else
        TAesPrng.Fill(TAesBlock(Issuer))
    else
      EccIssuer(IssuerText, Issuer);
    if not Ecc256r1MakeKey(PublicKey, fPrivateKey) then
      EEccException.RaiseUtf8('%.CreateNew: MakeKey?', [self]);
    if @Ecc256r1Verify = @ecdsa_verify_pas then
    begin
       // OpenSSL Ecc256r1VerifyUncomp() is actually slower than Ecc256r1Verify()
      Ecc256r1Uncompress(fContent.Head.Signed.PublicKey, fUncompressed);
      fUncompressedP := @fUncompressed;
    end;
  end;
  Authority.SignCertificate(self, ParanoidVerify);
end;

constructor TEccCertificateSecret.CreateFromSecureBinary(
  const Binary: RawByteString; const PassWord: RawUtf8; Pbkdf2Round: integer;
  Aes: TAesAbstractClass);
begin
  CreateFromSecureBinary(pointer(Binary), length(Binary), PassWord, Pbkdf2Round, Aes);
end;

constructor TEccCertificateSecret.CreateFromSecureBinary(Data: pointer;
  Len: integer; const PassWord: RawUtf8; Pbkdf2Round: integer; Aes: TAesAbstractClass);
begin
  Create;
  if not LoadFromSecureBinary(Data, Len, PassWord, Pbkdf2Round, Aes) then
    EEccException.RaiseUtf8('Invalid %.CreateFromSecureBinary', [self]);
end;

constructor TEccCertificateSecret.CreateFromSecureFile(const FileName: TFileName;
  const PassWord: RawUtf8; Pbkdf2Round: integer; Aes: TAesAbstractClass);
begin
  Create;
  if not LoadFromSecureFile(FileName, PassWord, Pbkdf2Round, Aes) then
    EEccException.RaiseUtf8(
      'Invalid %.CreateFromSecureFile("%")', [self, FileName]);
end;

constructor TEccCertificateSecret.CreateFromSecureFile(
  const FolderName: TFileName; const Serial, PassWord: RawUtf8;
  Pbkdf2Round: integer; Aes: TAesAbstractClass);
begin
  CreateFromSecureFile(
    IncludeTrailingPathDelimiter(FolderName) + Utf8ToString(Serial),
    PassWord, Pbkdf2Round, Aes);
end;

constructor TEccCertificateSecret.CreateFrom(Cert: TEccCertificate;
  EccPrivateKey: PEccPrivateKey; FreeCert: boolean);
begin
  if Cert = nil then
    EEccException.RaiseUtf8('Invalid %.CreateFrom(nil)', [self]);
  CreateVersion(Cert.fMaxVersion);
  fContent := Cert.fContent; // inject whole certificate information at once
  fUncompressed := Cert.fUncompressed;
  fUncompressedP := Cert.fUncompressedP;
  if EccPrivateKey <> nil then
    if Ecc256r1MatchKeys(EccPrivateKey^, fContent.Head.Signed.PublicKey) then
      fPrivateKey := EccPrivateKey^
    else
      EEccException.RaiseUtf8(
        '%.CreateFrom: public and private keys do not match ', [self]);
  if FreeCert then
    Cert.Free;
end;

destructor TEccCertificateSecret.Destroy;
begin
  FillZero(fPrivateKey);
  inherited Destroy;
end;

function TEccCertificateSecret.AppendLoad(const data: RawByteString): boolean;
begin
  result := fStoreOnlyPublicKey or
            TAesPrng.AFUnsplit(data, fPrivateKey, SizeOf(fPrivateKey));
end;

function TEccCertificateSecret.AppendSave: RawByteString;
begin
  if fStoreOnlyPublicKey then
    result := ''
  else
    result := TAesPrng.Main.AFSplit(
                fPrivateKey, SizeOf(fPrivateKey), fAFSplitStripes);
end;

function TEccCertificateSecret.HasSecret: boolean;
begin
  result := (self <> nil) and
            not IsZero(fPrivateKey);
end;

const
  // header of a private key binary file
  PRIVKEY_MAGIC: array[0..15] of AnsiChar =
    'SynEccPrivatKey'#26;
  // 128-bit is enough, since it is transmitted as clear
  PRIVKEY_SALTSIZE = 16;

function TEccCertificateSecret.SaveToSecureBinary(const PassWord: RawUtf8;
  AFStripes, Pbkdf2Round: integer; Aes: TAesAbstractClass;
  NoHeader: boolean): RawByteString;
var
  pksav: boolean;
  stsav, head: integer;
  plain, salt, enc: RawByteString;
  aeskey: TAesKey;
  a: TAesAbstract;
  e: PAnsiChar absolute result;
begin
  result := '';
  if Aes = nil then
    Aes := TAesCfb;
  pksav := fStoreOnlyPublicKey;
  stsav := fAFSplitStripes;
  try
    fStoreOnlyPublicKey := false;
    fAFSplitStripes := AFStripes; // for AppendSave/SaveToBinary
    plain := SaveToBinary;
    if plain <> '' then
      try
        salt := TAesPrng.Fill(PRIVKEY_SALTSIZE);
        Pbkdf2HmacSha256(PassWord, salt, Pbkdf2Round, aeskey);
        a := Aes.Create(aeskey);
        try
          enc := a.EncryptPkcs7(plain, true);
          // result := PRIVKEY_MAGIC+salt+enc; fails under FPC :(
          if NoHeader then
            head := 0
          else
            head := SizeOf(PRIVKEY_MAGIC);
          FastNewRawByteString(result, head + PRIVKEY_SALTSIZE + length(enc));
          MoveFast(PRIVKEY_MAGIC, e[0], head);
          XorBlock16(pointer(salt), @e[head], @PRIVKEY_MAGIC);
          MoveFast(pointer(enc)^, e[head + PRIVKEY_SALTSIZE], length(enc));
        finally
          a.Free;
        end;
      finally
        FillZero(plain);
      end;
  finally
    fStoreOnlyPublicKey := pksav;
    fAFSplitStripes := stsav;
    FillZero(aeskey);
  end;
end;

function TEccCertificateSecret.SaveToSecureFileName(
  FileNumber: integer): TFileName;
var
  tmp: RawUtf8;
begin
  if self = nil then
    result := ''
  else
  begin
    if FileNumber > 0 then
      FormatUtf8('%-%', [Serial, UInt3DigitsToShort(FileNumber)], tmp)
    else
      tmp := Serial;
    FormatString('%' + ECCCERTIFICATESECRET_FILEEXT, [tmp], string(result));
  end;
end;

function TEccCertificateSecret.SaveToSecureFile(const PassWord: RawUtf8;
  const DestFolder: TFileName; AFStripes, Pbkdf2Round: integer;
  Aes: TAesAbstractClass; NoHeader: boolean): boolean;
begin
  if (self = nil) or
     not DirectoryExists(DestFolder) then
    result := false
  else
    result := FileFromString(SaveToSecureBinary(PassWord, AFStripes,
      Pbkdf2Round, Aes, NoHeader), IncludeTrailingPathDelimiter(DestFolder) +
      SaveToSecureFileName);
end;

function TEccCertificateSecret.SaveToSecureFiles(const PassWord: RawUtf8;
  const DestFolder: TFileName; DestFileCount, AFStripes, Pbkdf2Round: integer;
  Aes: TAesAbstractClass; NoHeader: boolean): boolean;
var
  diff, one: RawByteString;
  head, index, pos, difflen, onechunk, onelen: integer;
  dest: TFileName;
begin
  if DestFileCount = 1 then
  begin
    result := SaveToSecureFile(
      PassWord, DestFolder, AFStripes, Pbkdf2Round, Aes, NoHeader);
    exit;
  end;
  result := false;
  dest := IncludeTrailingPathDelimiter(DestFolder);
  if (self = nil) or
     (DestFileCount <= 0) or
     not DirectoryExists(dest) then
    exit;
  if DestFileCount > 255 then
    DestFileCount := 255;
  diff := SaveToSecureBinary(
    PassWord, AFStripes * DestFileCount, Pbkdf2Round, Aes, true);
  difflen := length(diff);
  onechunk := difflen div DestFileCount;
  if NoHeader then
    head := 0
  else
    head := SizeOf(PRIVKEY_MAGIC);
  pos := 0;
  for index := 1 to DestFileCount do
  begin
    if index < DestFileCount then
      onelen := onechunk
    else
      onelen := difflen - pos;
    FastNewRawByteString(one, head + 2 + onelen);
    MoveFast(PRIVKEY_MAGIC, PByteArray(one)^[0], head);
    PByteArray(one)^[head] := index;
    PByteArray(one)^[head + 1] := DestFileCount;
    MoveFast(PByteArray(diff)[pos], PByteArray(one)^[head + 2], onelen);
    inc(pos, onelen);
    if not FileFromString(one, SaveToSecureFileName(index)) then
      exit;
  end;
  result := true;
end;

function TEccCertificateSecret.LoadFromSecureBinary(
  const Binary: RawByteString; const PassWord: RawUtf8;
  Pbkdf2Round: integer; Aes: TAesAbstractClass): boolean;
begin
  result := LoadFromSecureBinary(
    pointer(Binary), length(Binary), PassWord, Pbkdf2Round, Aes);
end;

function TEccCertificateSecret.LoadFromSecureBinary(Data: pointer; Len: integer;
  const PassWord: RawUtf8; Pbkdf2Round: integer; Aes: TAesAbstractClass): boolean;
var
  salt, decrypted: RawByteString;
  st: TRawByteStringStream;
  aeskey: TAesKey;
  a: TAesAbstract;
begin
  result := false;
  dec(Len, PRIVKEY_SALTSIZE);
  if (self = nil) or
     (Len <= SizeOf(PRIVKEY_MAGIC) + SizeOf(TAesBlock)) then
    exit;
  if mormot.core.base.IsEqual(THash128(PRIVKEY_MAGIC), PHash128(Data)^) then
  begin
    dec(Len, 16);
    inc(PHash128(Data));
  end; // may exist with NoHeader=true (e.g. SaveToSource)
  if Len and AesBlockMod <> 0 then
    exit;
  FastSetRawByteString(salt, Data, PRIVKEY_SALTSIZE);
  inc(PByte(Data), PRIVKEY_SALTSIZE);
  XorBlock16(pointer(salt), @PRIVKEY_MAGIC);
  try
    Pbkdf2HmacSha256(PassWord, salt, Pbkdf2Round, aeskey);
    if Aes = nil then
      Aes := TAesCfb;
    a := Aes.Create(aeskey);
    try
      decrypted := a.DecryptPkcs7Buffer(Data, Len, true, false);
      if decrypted = '' then
        exit; // invalid content
    finally
      a.Free;
    end;
    st := TRawByteStringStream.Create(decrypted);
    try
      if LoadFromStream(st) then
        result := fContent.Check and
                  not IsZero(fPrivateKey);
    finally
      st.Free;
    end;
  finally
    FillZero(decrypted);
    FillZero(aeskey);
  end;
end;

function TEccCertificateSecret.LoadFromSecureFile(const FileName: TFileName;
  const PassWord: RawUtf8; Pbkdf2Round: integer; Aes: TAesAbstractClass): boolean;
var
  FN: TFileName;
begin
  if ExtractFileExt(FileName) = '' then
    FN := FileName + ECCCERTIFICATESECRET_FILEEXT
  else
    FN := FileName;
  result := LoadFromSecureBinary(
    StringFromFile(FN), PassWord, Pbkdf2Round, Aes);
end;

function TEccCertificateSecret.SaveToSource(
  const ConstName, Comment, PassWord: RawUtf8; IncludePassword: boolean;
  AFStripes, Pbkdf2Round: integer; Aes: TAesAbstractClass;
  IncludeRaw: boolean): RawUtf8;
var
  data: RawByteString;
  name, suffix: RawUtf8;
begin
  result := '';
  if (self = nil) or
     (PassWord = '') then
    exit;
  data := SaveToSecureBinary(
    PassWord, AFStripes, Pbkdf2Round, Aes, {NoHeader=}true);
  if data = '' then
    exit;
  if ConstName = '' then
    name := '_' + copy(Serial, 1, 24)
  else
    name := UpperCase(ConstName);
  if IncludePassword then
    suffix := FormatUtf8('  %_PASS = %;'#13#10'  %_CYPH = ''%'';'#13#10,
      [name, QuotedStr(PassWord), name,
       TSynPersistentWithPassword.ComputePassword(PassWord)]);
  if ConstName <> '' then
    suffix := FormatUtf8('  %_SERIAL = ''%'';'#13#10'%',
      [name, Serial, {%H-}suffix]);
  suffix := FormatUtf8('  %_ROUNDS = %;'#13#10'%',
    [name, Pbkdf2Round, suffix]);
  if IncludeRaw then
    suffix := FormatUtf8('  %_RAW = ''%'';'#13#10'%', [name,
      mormot.core.text.BinToHex(@fPrivateKey, SizeOf(fPrivateKey)), suffix]);
  result := BinToSource(name, Comment, pointer(data), length(data), 16, suffix)
end;

function TEccCertificateSecret.SignToBase64(Data: pointer; Len: integer): RawUtf8;
begin
  result := BinToBase64(SignToBinary(Data, Len));
end;

function TEccCertificateSecret.SignToBase64(const Hash: THash256): RawUtf8;
begin
  result := BinToBase64(SignToBinary(Hash));
end;

function TEccCertificateSecret.SignToBinary(Data: pointer; Len: integer): RawByteString;
begin
  if (Data = nil) or
     (Len < 0) or
     not (cuDigitalSignature in GetUsage) then
    result := ''
  else
    result := SignToBinary(Sha256Digest(Data, Len));
end;

function TEccCertificateSecret.SignToBinary(const Hash: THash256): RawByteString;
var
  sign: TEccSignatureCertified;
begin
  result := '';
  if (self = nil) or
     IsZero(Hash) or
     not (cuDigitalSignature in GetUsage) then
    exit;
  sign := TEccSignatureCertified.CreateNew(self, Hash);
  try
    result := sign.ToBinary;
  finally
    sign.Free;
  end;
end;

function TEccCertificateSecret.SignFile(const FileToSign: TFileName;
  const MetaNameValuePairs: array of const): TFileName;
var
  content: RawByteString;
  sign: RawUtf8;
  doc, meta: TDocVariantData;
  sha: TSha256Digest;
begin
  content := StringFromFile(FileToSign);
  if content = '' then
    EEccException.RaiseUtf8(
      '%.SignFile: file [%] not found', [self, FileToSign]);
  sha := Sha256Digest(pointer(content), length(content));
  sign := SignToBase64(sha);
  meta.InitObject([
    'name',   ExtractFileName(FileToSign),
    'date',   DateTimeToIso8601Text(FileAgeToDateTime(FileToSign))],
    JSON_FAST);
  meta.AddNameValuesToObject(MetaNameValuePairs);
  doc.InitObject([
    'meta',   variant(meta),
    'size',   length(content),
    'md5',    Md5(content),
    'sha256', Sha256DigestToString(sha),
    'sign',   sign],
    JSON_FAST);
  result := FileToSign + ECCCERTIFICATESIGN_FILEEXT;
  FileFromString(doc.ToJson('', '', jsonHumanReadable), result);
end;

procedure TEccCertificateSecret.SignCertificate(Dest: TEccCertificate;
  ParanoidVerify: boolean);
var
  dst: PEccCertificateSigned;
  priv: PEccPrivateKey;
  pub: PEccPublicKey;
  hash: TSha256Digest;
begin
  if Dest = nil then
    exit; // nothing to sign
  dst := @Dest.fContent.Head.Signed;
  if HasSecret then
  begin
    // Dest is signed by this TEccCertificateSecret
    if not (cuKeyCertSign in GetUsage) then
      EEccException.RaiseUtf8(
        '%.SignCertificate: % authority has no cuKeyCertSign', [self, Serial]);
    dst.AuthoritySerial := fContent.Head.Signed.Serial;
    dst.AuthorityIssuer := fContent.Head.Signed.Issuer;
    priv := @fPrivateKey;
    pub := @fContent.Head.Signed.PublicKey;
  end
  else if Dest.InheritsFrom(TEccCertificateSecret) and
          TEccCertificateSecret(Dest).HasSecret then
  begin
    // Dest is self-signed (e.g. if self=nil or without secret)
    dst.AuthoritySerial := dst.Serial;
    dst.AuthorityIssuer := dst.Issuer;
    priv := @TEccCertificateSecret(Dest).fPrivateKey;
    pub := @dst.PublicKey;
  end
  else
    raise EEccException.CreateUtf8(
      '%.SignCertificate: self-sign with no secret', [self]);
  // compute the digital signature of Dest.fContent
  Dest.fContent.ComputeHash(hash);
  if not Ecc256r1Sign(priv^, hash, Dest.fContent.Head.Signature) then
    EEccException.RaiseUtf8('%.SignCertificate: Ecc256r1Sign?', [self]);
  if ParanoidVerify and
     not Ecc256r1Verify(pub^, hash, Dest.fContent.Head.Signature) then
    EEccException.RaiseUtf8('%.SignCertificate: Ecc256r1Verify?', [self]);
  Dest.fContent.Head.CRC := Dest.fContent.ComputeCrc32; // crc changed
end;

function TEccCertificateSecret.Decrypt(const Encrypted: RawByteString;
  out Decrypted: RawByteString; Signature: PEccSignatureCertifiedContent;
  MetaData: PRawJson; FileDateTime: PDateTime; const KDFSalt: RawUtf8;
  KDFRounds: integer; const MACSalt: RawUtf8; MACRounds: integer): TEccDecrypt;
var
  head: TEciesHeader;
  features: TEciesFeatures;
  data: PAnsiChar;
  datalen, metaend: integer;
  secret, enc, dec, temp: RawByteString;
  aeskey, mackey: THash256Rec;
  hmac: THash256;
  c: TAesAbstractClass;
begin
  result := ecdUnsupported;
  if GetUsage * [cuDataEncipherment, cuDecipherOnly] = [] then
    exit;
  result := ecdCorrupted;
  datalen := length(Encrypted) - SizeOf(TEciesHeader);
  if (datalen <= 0) or
     not EciesHeader(Encrypted, head) then
    exit;
  data := @PByteArray(Encrypted)[SizeOf(TEciesHeader)];
  if CheckCRC and
     HasSecret then
  try
    if not mormot.crypt.ecc256r1.IsEqual(head.recid, fContent.Head.Signed.Serial) then
    begin
      result := ecdInvalidSerial;
      exit;
    end;
    FastNewRawByteString(secret, SizeOf(TEccSecretKey));
    if not Ecc256r1SharedSecret(
        head.rndpub, fPrivateKey, PEccSecretKey(secret)^) then
      exit;
    result := ecdInvalidMAC;
    Pbkdf2HmacSha256(secret, KDFSalt, KDFRounds, aeskey.b, 'salt');
    if head.Algo in ECIES_AEAD then
    begin
      // decrypt e.g. with AES-GCM single pass message authentication
      c := TAesFast[ECIES_AES[head.Algo]];
      case ECIES_AESSIZE[head.Algo] of
        128:
          enc := c.MacEncrypt(dec{%H-}, aeskey.Lo, {encrypt=}false);
        256:
          enc := c.MacEncrypt(dec, aeskey.b, false);
      end;
      if length(enc) < SizeOf(TMacAndCryptData) then
        // MacDecryptCheckTag() failed within MacEncrypt() above
        exit;
    end
    else
    begin
      // validate the HMAC signature of the encrypted content
      Pbkdf2HmacSha256(secret, MACSalt, MACRounds, mackey.b, 'hmac');
      HmacSha256(@mackey, data, SizeOf(mackey), datalen, hmac);
      if not mormot.core.base.IsEqual(hmac, head.hmac) then
        exit;
      // decrypt the content
      FastSetRawByteString(enc, data, datalen);
      dec := AesPkcs7(enc, {encrypt=}false,
        aeskey, ECIES_AESSIZE[head.Algo], ECIES_AES[head.Algo]);
    end;
    if head.Algo in ECIES_SYNLZ then
      AlgoSynLZ.Decompress(pointer(dec), length(dec), Decrypted)
    else
      Decrypted := dec;
    result := ecdDecryptError;
    features := EciesFeatures(head);
    if efMetaData in features then
    begin
      if (Decrypted = '') or
         (Decrypted[1] <> '{') then
        exit;
      metaend := PosExChar(#0, Decrypted); // {metadata}+#0+plain
      if metaend = 0 then
        exit;
      if MetaData <> nil then
        MetaData^ := copy(Decrypted, 1, metaend - 1);
      temp := copy(Decrypted, metaend + 1, maxInt);
      FillZero(Decrypted); // avoid uncyphered content on heap
      Decrypted := temp;
    end;
    if cardinal(length(Decrypted)) <> head.size then
      exit;
    if FileDateTime <> nil then
      if head.unixts = 0 then
        FileDateTime^ := 0
      else
        FileDateTime^ := UnixTimeToDateTime(head.unixts);
    if (Signature <> nil) and
       head.sign.Check then
    begin
      result := ecdDecryptedWithSignature;
      Signature^ := head.sign;
    end
    else
      result := ecdDecrypted;
  finally
    FillZero(aeskey.b);
    FillZero(mackey.b);
    FillZero(secret);
    if dec <> Decrypted then
      FillZero(dec);
  end
  else
    result := ecdNoPrivateKey;
end;

function TEccCertificateSecret.DecryptFile(const FileToDecrypt,
  DestFile: TFileName; const Salt: RawUtf8; SaltRounds: integer;
  Signature: PEccSignatureCertifiedContent; MetaData: PRawJson): TEccDecrypt;
var
  content, plain: RawByteString;
  dest: TFileName;
  filetime: TDateTime;
begin
  content := StringFromFile(FileToDecrypt);
  result := ecdNoContent;
  if content <> '' then
  try
    result := ecdNoPrivateKey;
    if not CheckCRC then
      exit;
    if DestFile = '' then
      dest := GetFileNameWithoutExt(FileToDecrypt)
    else
      dest := DestFile;
    result := Decrypt(
      content, plain, Signature, MetaData, @filetime, Salt, SaltRounds);
    if result in ECC_VALIDDECRYPT then
      if FileFromString(plain, dest) and
         FileSetDateFromUnixUtc(dest, DateTimeToUnixTime(filetime)) then
      else
        result := ecdWriteFileError;
  finally
    FillZero(plain);
  end;
end;

function TEccCertificateSecret.DecryptMessage(const Encrypted: RawByteString;
  const Cipher: RawUtf8): RawByteString;
begin
  if (GetUsage * [cuDataEncipherment, cuDecipherOnly] <> []) and
     CheckCRC and
     HasSecret then
    result := EciesOpen(Cipher, fPrivateKey, Encrypted)
  else
    result := '';
end;


{ TEccCertificateSecretSetting }

function TEccCertificateSecretSetting.CertificateSecret(
  const FolderName: TFileName): TEccCertificateSecret;
begin
  if self = nil then
    result := nil
  else if filename <> '' then
    result := TEccCertificateSecret.CreateFromSecureFile(
      filename, GetPassWordPlain, PasswordRounds)
  else if Serial = '' then
    result := nil
  else
    result := TEccCertificateSecret.CreateFromSecureFile(
      FolderName, Serial, GetPassWordPlain, PasswordRounds);
end;

constructor TEccCertificateSecretSetting.Create;
begin
  inherited Create; // may have been overriden
  fPasswordRounds := DEFAULT_ECCROUNDS;
end;


{ TEccCertificateDecryptSetting }

constructor TEccCertificateDecryptSetting.Create;
begin
  inherited Create;
  fSalt := 'salt';
  fSaltRounds := DEFAULT_ECCROUNDS;
end;


{ TEccSignatureCertified }

constructor TEccSignatureCertified.Create;
begin
  inherited Create; // may have been overriden
  fCertified.Version := 1;
end;

constructor TEccSignatureCertified.CreateFrom(
  const binary: TEccSignatureCertifiedContent; NoException: boolean);
begin
  Create;
  if binary.Check then
    fCertified := binary
  else if not NoException then
    EEccException.RaiseUtf8(
      'Invalid %.CreateFrom', [self]);
end;

constructor TEccSignatureCertified.CreateFromBase64(const base64: RawUtf8;
  NoException: boolean);
begin
  Create;
  if not FromBase64(base64) then
    if not NoException then
      EEccException.RaiseUtf8(
        'Invalid %.CreateFromBase64', [self]);
end;

constructor TEccSignatureCertified.CreateFromFile(const signfilename: TFileName;
  NoException: boolean);
begin
  Create;
  if not FromFile(signfilename) then
    if not NoException then
      EEccException.RaiseUtf8(
        'Invalid %.CreateFromFile("%")', [self, signfilename]);
end;

constructor TEccSignatureCertified.CreateNew(Authority: TEccCertificateSecret;
  Data: pointer; Len: integer);
begin
  CreateNew(Authority, Sha256Digest(Data, Len));
end;

constructor TEccSignatureCertified.CreateNew(Authority: TEccCertificateSecret;
  const Hash: THash256);
begin
  Create;
  if not Authority.HasSecret then
    EEccException.RaiseUtf8(
      '%.CreateNew: secret=0 %', [self, Authority]);
  if IsZero(Hash) then
    EEccException.RaiseUtf8(
      '%.CreateNew(Hash=0)', [self]);
  fCertified.Date := NowEccDate;
  fCertified.AuthoritySerial := Authority.Content.Head.Signed.Serial;
  fCertified.AuthorityIssuer := Authority.Content.Head.Signed.Issuer;
  if not Ecc256r1Sign(Authority.fPrivateKey, Hash, fCertified.Signature) then
    EEccException.RaiseUtf8('%.CreateNew: Ecc256r1Sign?', [self]);
end;

function TEccSignatureCertified.GetAuthorityIssuer: RawUtf8;
begin
  result := EccText(fCertified.AuthorityIssuer);
end;

function TEccSignatureCertified.GetAuthoritySerial: RawUtf8;
begin
  result := EccText(fCertified.AuthoritySerial);
end;

function TEccSignatureCertified.GetDate: RawUtf8;
begin
  result := EccText(fCertified.Date);
end;

function TEccSignatureCertified.FromBase64(const base64: RawUtf8): boolean;
begin
  result := (self <> nil) and
    Base64ToBin(pointer(base64), @fCertified, length(base64), SizeOf(fCertified)) and
    fCertified.Check;
end;

function TEccSignatureCertified.FromFile(const signfilename: TFileName): boolean;
var
  json: RawUtf8;
begin
  if FileExists(signfilename + ECCCERTIFICATESIGN_FILEEXT) then
    json := StringFromFile(signfilename + ECCCERTIFICATESIGN_FILEEXT)
  else
    json := StringFromFile(signfilename);
  if json = '' then
    result := false
  else
    result := FromBase64(JsonDecode(json, 'sign', nil, true));
end;

function TEccSignatureCertified.ToBase64: RawUtf8;
begin
  result := BinToBase64(@fCertified, SizeOf(fCertified));
end;

function TEccSignatureCertified.ToBinary: RawByteString;
begin
  FastSetRawByteString(result{%H-}, @fCertified, SizeOf(fCertified));
end;

function TEccSignatureCertified.ToVariant: variant;
begin
  result := _ObjFast([
    'Version',         Version,
    'Date',            Date,
    'AuthoritySerial', AuthoritySerial,
    'AuthorityIssuer', AuthorityIssuer]);
end;

function TEccSignatureCertified.Check: boolean;
begin
  result := (self <> nil) and
            fCertified.Check;
end;

function TEccSignatureCertified.Verify(Authority: TEccCertificate;
  const hash: THash256; TimeUtc: TDateTime): TEccValidity;
begin
  if self = nil then
    result := ecvBadParameter
  else
    result := Authority.Verify(hash, fCertified, TimeUtc);
end;

function TEccSignatureCertified.Verify(Authority: TEccCertificate;
  Data: pointer; Len: integer; TimeUtc: TDateTime): TEccValidity;
begin
  result := Verify(Authority, Sha256Digest(Data, Len), TimeUtc);
end;

function TEccSignatureCertified.SaveToDerBinary: RawByteString;
begin
  if not Check then
    result := ''
  else
    result := EccToDer(fCertified.Signature);
end;

function TEccSignatureCertified.SaveToDERFile(const FileName: TFileName): boolean;
begin
  if not Check then
    result := false
  else
    result := FileFromString(SaveToDerBinary, FileName);
end;

function TEccSignatureCertified.SaveToPemText: RawUtf8;
begin
  if not Check then
    result := ''
  else
    result := DerToPem(EccToDer(fCertified.Signature), pemSynopseSignature);
end;

function TEccSignatureCertified.SaveToPEMFile(const FileName: TFileName): boolean;
begin
  if not Check then
    result := false
  else
    result := FileFromString(SaveToPemText, FileName);
end;


{ TEccSignatureCertifiedFile }

function TEccSignatureCertifiedFile.FromFile(const aFileName: TFileName): boolean;
var
  json: RawUtf8;
begin
  if SameText(ExtractFileExt(aFileName), ECCCERTIFICATESIGN_FILEEXT) then
    json := StringFromFile(aFileName)
  else
    json := StringFromFile(aFileName + ECCCERTIFICATESIGN_FILEEXT);
  result := FromFileJson(json);
end;

function TEccSignatureCertifiedFile.FromFileJson(const aFileContent: RawUtf8): boolean;
begin
  fLowLevelInfo.Clear;
  if not fLowLevelInfo.InitJson(aFileContent, JSON_FAST) then
  begin
    result := false;
    exit;
  end;
  fSize := fLowLevelInfo.I['size'];
  fMetaData := fLowLevelInfo.GetValueOrEmpty('meta'); // Value[] makes GPF
  fMD5 := fLowLevelInfo.U['md5'];
  fSHA256 := fLowLevelInfo.U['sha256'];
  result := (fSize > 0) and
            (_Safe(fMetaData)^.Kind <> dvArray) and
            Md5StringToDigest(fMD5, fMd5Digest) and
            Sha256StringToDigest(fSHA256, fSha256Digest) and
            FromBase64(fLowLevelInfo.U['sign']);
end;

function TEccSignatureCertifiedFile.FromDecryptedFile(
  const aDecryptedContent: RawByteString;
  const Signature: TEccSignatureCertifiedContent;
  const MetaData: RawJson): boolean;
var
  MD5: TMd5;
  SHA256: TSha256;
begin
  result := Signature.Check;
  if not result then
    exit;
  fSize := length(aDecryptedContent);
  MD5.Full(pointer(aDecryptedContent), fSize, fMd5Digest);
  fMD5 := Md5DigestToString(fMd5Digest);
  SHA256.Full(pointer(aDecryptedContent), fSize, fSha256Digest);
  fSHA256 := Sha256DigestToString(fSha256Digest);
  fMetaData := _JsonFast(MetaData);
  fCertified := Signature;
  fLowLevelInfo.Clear;
  fLowLevelInfo.InitObject([
    'size',   fSize,
    'md5',    fMD5,
    'sha256', fSHA256,
    'sign',   ToBase64,
    'meta',   fMetaData]);
end;

constructor TEccSignatureCertifiedFile.CreateFromDecryptedFile(
  const aDecryptedContent: RawByteString;
  const Signature: TEccSignatureCertifiedContent;
  const MetaData: RawJson);
begin
  inherited Create;
  if not FromDecryptedFile(aDecryptedContent, Signature, MetaData) then
    EEccException.RaiseUtf8(
      'Invalid Signature for %.CreateFromDecryptedFile', [self]);
end;


{ TEccCertificateChain }

constructor TEccCertificateChain.CreateVersion(MaxVers: integer);
begin
  inherited Create; // may have been overriden
  fMaxVersion := MaxVers;
end;

constructor TEccCertificateChain.Create;
begin
  CreateVersion(2);
  fIsValidCacheSalt := ToUtf8(RandomGuid); // avoid flooding on forged input
end;

constructor TEccCertificateChain.CreateFromJson(
  const json: RawUtf8; maxvers: integer);
begin
  CreateVersion(maxvers);
  if not LoadFromJson(json) then
    EEccException.RaiseUtf8('Invalid %.CreateFromJson', [self]);
end;

constructor TEccCertificateChain.CreateFromArray(
  const values: TRawUtf8DynArray; maxvers: integer);
begin
  CreateVersion(maxvers);
  if not LoadFromArray(values) then
    EEccException.RaiseUtf8('Invalid %.CreateFromArray', [self]);
end;

destructor TEccCertificateChain.Destroy;
begin
  ObjArrayClear(fItems);
  inherited;
end;

function TEccCertificateChain.IsValid(cert: TEccCertificate;
  TimeUtc: TDateTime): TEccValidity;
begin
  if (self = nil) or
     (cert = nil) then
    result := ecvBadParameter
  else
    result := IsValidRaw(cert.Content, {ignoredate=}false,
      {allowselfnotaddedyet=}false, TimeUtc);
end;

function TEccCertificateChain.IsValidRaw(const content: TEccCertificateContent;
  ignoreDate, allowPlainSelfSigned: boolean; TimeUtc: TDateTime): TEccValidity;
var
  authoritypublickey: TEccPublicKey;
  hash: THash256Rec;
  cached: THash128;
begin
  // check certificate coherency and date before checking the cache
  result := ecvCorrupted;
  if not content.Check then
    exit;
  if not ignoreDate then
  begin
    result := ecvInvalidDate;
    if not content.CheckDate(nil, TimeUtc) then
      exit;
  end;
  // guess which result is to be returned on proper verification
  if content.IsSelfSigned then
    result := ecvValidSelfSigned
  else
    result := ecvValidSigned;
  // compute the sha-256 of this certificate
  content.ComputeHash(hash.b);
  if fIsValidCached then
  begin
    // try to recognize a previous valid certificate in the hash cache
    cached := hash.Lo; // apply fIsValidCacheSalt and maybe AesNiHash128
    DefaultHasher128(@cached, pointer(fIsValidCacheSalt), length(fIsValidCacheSalt));
    fSafe.ReadLock;
    try
      if Hash128Index(pointer(fIsValidCache), fIsValidCacheCount, @cached) >= 0 then
        exit; // 128-bit lower part of sha-256 is very unlikely to collide
    finally
      fSafe.ReadUnlock;
    end;
  end;
  if allowPlainSelfSigned and // = from AddSelfSigned() call
     (result = ecvValidSelfSigned) then
    authoritypublickey := content.Head.Signed.PublicKey
  else
  begin
    // search for the associated authority in the chain
    result := GetKeyBySerial(content.Head.Signed.AuthoritySerial,
                [cuCA, cuKeyCertSign], authoritypublickey, result,
                EccToDateTime(content.Head.Signed.IssueDate));
    if not (result in ECC_VALIDSIGN) then
      exit; // ecvUnknownAuthority/ecvDeprecatedAuthority/ecvRevoked
  end;
  // if we reached here, the context and authority are correct -> check signature
  if Ecc256r1Verify(authoritypublickey, hash.b, content.Head.Signature) then
  begin
    if fIsValidCached then
    begin
      // store the hash of this valid certificate for quick recognition
      fSafe.WriteLock;
      try
        if fIsValidCacheCount > 1024 then
          fIsValidCacheCount := 0; // time to flush the cache once reached 16KB
        AddHash128(fIsValidCache, cached, fIsValidCacheCount);
      finally
        fSafe.WriteUnlock;
      end;
    end;
  end
  else
    result := ecvInvalidSignature;
end;

function TEccCertificateChain.IsRevoked(const Serial: RawUtf8): TCryptCertRevocationReason;
var
  id: TEccCertificateID;
begin
  if (fCrl <> nil) and
     EccID(Serial, id) then
    result := IsRevoked(id)
  else
    result := crrNotRevoked;
end;

function FindRevoked(p: PEccCertificateRevocation; n: integer;
  ser: PHash128Rec; now: cardinal): TCryptCertRevocationReason;
begin
  repeat
    with THash128Rec(p^.Serial) do
      {$ifdef CPU64}
      if (ser.Lo = Lo) and
         (ser.Hi = Hi) and
      {$else}
      if (ser.i0 = i0) and
         (ser.i1 = i1) and
         (ser.i2 = i2) and
         (ser.i3 = i3) and
      {$endif CPU64}
        (p^.Date <= now) then
      begin
        result := TCryptCertRevocationReason(p^.Reason);
        exit;
      end;
    inc(p);
    dec(n);
  until n = 0;
  result := crrNotRevoked;
end;

function TEccCertificateChain.IsRevoked(
  const Serial: TEccCertificateID): TCryptCertRevocationReason;
begin
  result := crrNotRevoked;
  if (self = nil) or
     (fCrl = nil) or
     IsZero(Serial) then
    exit;
  fSafe.ReadLock;
  try
    if fCrl <> nil then
      result := FindRevoked(pointer(fCrl), length(fCrl), @Serial, NowEccDate);
  finally
    fSafe.ReadUnLock;
  end;
end;

function TEccCertificateChain.Revoke(const Serial: RawUtf8;
  RevocationDate: TDateTime; Reason: TCryptCertRevocationReason): boolean;
var
  id: TEccCertificateID;
begin
  result := EccID(Serial, id) and
            Revoke(id, RevocationDate, Reason);
end;

function TEccCertificateChain.Revoke(const Serial: TEccCertificateID;
  RevocationDate: TDateTime; Reason: TCryptCertRevocationReason): boolean;
var
  n, i: PtrInt;
  c: PEccCertificateRevocation;
begin
  result := false;
  if (RevocationDate = 0) and
     (Reason <> crrNotRevoked) then
    RevocationDate := NowUtc;
  if (self = nil) or
     (RevocationDate < 0) or
     IsZero(Serial) then
    exit;
  fSafe.WriteLock;
  try
    fIsValidCacheCount := 0; // revocation disable some certs
    n := length(fCrl);
    if Reason = crrNotRevoked then
    begin
      // remove from CRL list
      c := pointer(fCrl);
      dec(n);
      for i := 0 to n do
        if IsEqual(c^.Serial, Serial) then
        begin
          if n > i then
            MoveFast(fCrl[i + 1], fCrl[i], (n - i) * SizeOf(fCrl[i]));
          SetLength(fCrl, n);
          result := true;
          break;
        end
        else
          inc(c);
    end
    else
    begin
      // add to CRL list
      SetLength(fCrl, n + 1);
      if fCrl[n].From(Serial, RevocationDate, ord(Reason)) then
        result := true
      else
        SetLength(fCrl, n);
    end;
  finally
    fSafe.WriteUnLock;
  end;
end;

function FindBySerial(p: PEccCertificate; n: integer;
  ser: PHash128Rec): TEccCertificate;
begin
  if (p <> nil) and
     ((ser.Lo <> 0) or
      (ser.Hi <> 0)) then
    repeat
        with THash128Rec(p^.Signed.Serial) do
        {$ifdef CPU64}
        if (ser.Lo = Lo) and
           (ser.Hi = Hi) then
        {$else}
        if (ser.i0 = i0) and
           (ser.i1 = i1) and
           (ser.i2 = i2) and
           (ser.i3 = i3) then
        {$endif CPU64}
        begin
          result := p^;
          exit;
        end;
      inc(p);
      dec(n);
    until n = 0;
  result := nil;
end;

function TEccCertificateChain.GetBySerial(
  const Serial: TEccCertificateID): TEccCertificate;
begin
  result := FindBySerial(pointer(fItems), length(fItems), @Serial);
end;

function TEccCertificateChain.GetBySerial(const Serial: RawUtf8): TEccCertificate;
var
  id: TEccCertificateID;
begin
  if EccID(Serial, id) then
    result := FindBySerial(pointer(fItems), length(fItems), @id)
  else
    result := nil;
end;

function TEccCertificateChain.GetKeyBySerial(const Serial: TEccCertificateID;
  Usage: TCryptCertUsages; out PublicKey: TEccPublicKey; Valid: TEccValidity;
  TimeUtc: TDateTime; IgnoreDate: boolean): TEccValidity;
var
  cert: TEccCertificate;
  now: TEccDate;
begin
  fSafe.ReadLock;
  try
    cert := FindBySerial(pointer(fItems), length(fItems), @Serial);
    if cert = nil then
      result := ecvUnknownAuthority
    else if Usage * cert.GetUsage = [] then
      result := ecvWrongUsage
    else if not IgnoreDate and
            not cert.fContent.CheckDate(@now, TimeUtc) then
      result := ecvDeprecatedAuthority
    else if (fCrl <> nil) and
            (FindRevoked(pointer(fCrl), length(fCrl), @Serial, now) <> crrNotRevoked) then
      result := ecvRevoked
    else
    begin
      PublicKey := cert.Content.Head.Signed.PublicKey;
      if valid <> ecvUnknown then
        result := valid
      else if cert.Content.IsSelfSigned then
        result := ecvValidSelfSigned
      else
        result := ecvValidSigned;
    end;
  finally
    fSafe.ReadUnLock;
  end;
end;

procedure TEccCertificateChain.SetIsValidCached(const Value: boolean);
begin
  if fIsValidCached = Value then
    exit;
  fSafe.WriteLock;
  try
    fIsValidCached := Value;
    if not Value then
    begin
      fIsValidCache := nil;
      fIsValidCacheCount := 0;
    end;
  finally
    fSafe.WriteUnLock;
  end;
end;

function TEccCertificateChain.InternalAdd(cert: TEccCertificate;
  expected: TEccValidity): PtrInt;
begin
  result := -1;
  if (self = nil) or
     (cert = nil) or
     (cert.GetUsage * [cuCA, cuKeyCertSign, cuDigitalSignature] = []) or
     (IsValidRaw(cert.fContent, {ignoredate=}true, // dates are checked per use
        {allowselfnotaddedyet=}expected = ecvValidSelfSigned) <> expected) then
    exit;
  fSafe.WriteLock;
  try
    if GetBySerial(cert.Signed.Serial) = nil then
      result := ObjArrayAdd(fItems, cert);
  finally
    fSafe.WriteUnLock;
  end;
end;

function TEccCertificateChain.Add(cert: TEccCertificate): PtrInt;
begin
  result := InternalAdd(cert, ecvValidSigned);
end;

function TEccCertificateChain.AddSelfSigned(cert: TEccCertificate): PtrInt;
begin
  result := InternalAdd(cert, ecvValidSelfSigned);
end;

function TEccCertificateChain.AddFrom(Chain: TEccCertificateChain): TRawUtf8DynArray;
var
  i: PtrInt;
  n: integer;
begin
  result := nil;
  if Chain = nil then
    exit;
  n := 0;
  for i := 0 to length(Chain.fItems) - 1 do
    if Add(Chain.fItems[i]) >= 0 then
      AddRawUtf8(result, n, Chain.fItems[i].GetSerial);
  for i := 0 to length(Chain.fCrl) - 1 do
    with Chain.fCrl[i] do
      Revoke(Serial, Date, TCryptCertRevocationReason(Reason));
  if n <> length(result) then
    SetLength(result, n);
end;

function TEccCertificateChain.AddFromBuffer(
  const Content: RawByteString): TRawUtf8DynArray;
var
  n: integer;
  P: PUtf8Char;
  bin: RawByteString;
  cert: TEccCertificate;
  chain: TEccCertificateChain;
begin
  result := nil;
  n := 0;
  P := pointer(Content);
  case GetNextJsonToken(P) of
    jtArrayStart:
      begin
        // from JSON array serialized TEccCertificateChain
        chain := TEccCertificateChain.CreateVersion(fMaxVersion);
        try
          if chain.LoadFromJson(Content) then
            AddRawUtf8(result, n, AddFrom(chain));
        finally
          chain.Free;
        end;
      end;
    jtObjectStart:
      begin
        // from JSON object serialized TEccCertificate
        cert := TEccCertificate.CreateVersion(fMaxVersion);
        try
          if cert.FromJson(Content) and
             (Add(cert) >= 0) then
            AddRawUtf8(result, n, cert.GetSerial);
        finally
          cert.Free;
        end;
      end;
  else
    begin
      if not Base64ToBinSafe(pointer(Content), length(Content), bin) then
        bin := Content; // try raw input if was not Base64 encoded
      if (length(bin) > 8) and
         (PCardinal(bin)^ = CHAIN_MAGIC) then
      begin
        // from binary TEccCertificateChain
        chain := TEccCertificateChain.CreateVersion(fMaxVersion);
        try
          if chain.LoadFromBinary(bin) then
            AddRawUtf8(result, n, AddFrom(chain));
        finally
          chain.Free;
        end;
      end;
      if (n = 0) and
         (length(bin) >= SizeOf(TEccCertificateContentV1)) and
         (PEccCertificateContentV1(bin)^.Version >= 1) and
         (PEccCertificateContentV1(bin)^.Version <= fMaxVersion) then
      begin
        // from binary TEccCertificate
        cert := TEccCertificate.CreateVersion(fMaxVersion);
        try
          if cert.LoadFromBinary(bin) and
             (Add(cert) >= 0) then
            AddRawUtf8(result, n, cert.GetSerial);
        finally
          cert.Free;
        end;
      end;
    end;
  end;
  SetLength(result, n);
end;

function TEccCertificateChain.GetSerials: TRawUtf8DynArray;
var
  i: PtrInt;
begin
  result := nil;
  if (self = nil) or
     (fItems = nil) then
    exit;
  fSafe.ReadLock;
  try
    SetLength(result, length(fItems));
    for i := 0 to length(result) - 1 do
      result[i] := fItems[i].GetSerial;
  finally
    fSafe.ReadUnLock;
  end;
end;

procedure TEccCertificateChain.LockedClear;
begin
  ObjArrayClear(fItems);
  fCrl := nil;
  fIsValidCacheCount := 0;
  fIsValidCache := nil;
end;

procedure TEccCertificateChain.Clear;
begin
  fSafe.WriteLock;
  try
    LockedClear;
  finally
    fSafe.WriteUnLock;
  end;
end;

function TEccCertificateChain.GetCount: integer;
begin
  if self = nil then
    result := 0
  else
    result := length(fItems);
end;

function TEccCertificateChain.GetCrlCount: integer;
begin
  if self = nil then
    result := 0
  else
    result := length(fCrl);
end;

function TEccCertificateChain.IsAuthorized(sign: TEccSignatureCertified): boolean;
begin
  if (self <> nil) and
     (sign <> nil) then
    result := IsAuthorized(sign.Certified)
  else
    result := false;
end;

function TEccCertificateChain.IsAuthorized(
  const sign: TEccSignatureCertifiedContent): boolean;
var
  cert: TEccCertificate;
begin
  fSafe.ReadLock;
  try
    cert := FindBySerial(pointer(fItems), length(fItems), @sign.AuthoritySerial);
    result := (cert <> nil) and
        IsEqual(cert.Content.Head.Signed.AuthorityIssuer, sign.AuthorityIssuer) and
        ((fCrl = nil) or
         (FindRevoked(pointer(fCrl), length(fCrl),
            @sign.AuthoritySerial, NowEccDate) = crrNotRevoked));
  finally
    fSafe.ReadUnLock;
  end;
end;

function TEccCertificateChain.IsAuthorized(const base64sign: RawUtf8): boolean;
var
  sign: TEccSignatureCertifiedContent;
begin
  if sign.FromBase64(base64sign) then
    result := IsAuthorized(sign)
  else
    result := false;
end;

function TEccCertificateChain.IsSigned(sign: TEccSignatureCertified;
  Data: pointer; Len: integer): TEccValidity;
var
  hash: TSha256Digest;
begin
  if (self <> nil) and
     (sign <> nil) and
     (Data <> nil) and
     (Len > 0) then
  begin
    hash := Sha256Digest(Data, Len);
    if sign.InheritsFrom(TEccSignatureCertifiedFile) then
      with TEccSignatureCertifiedFile(sign) do
        if (Size <> Len) or
           not IsEqual(hash, Sha256Digest) or
           not IsEqual(Md5Buf(Data^, Len), Md5Digest) then
        begin
          result := ecvCorrupted;
          exit;
        end;
    result := IsSigned(sign.Certified, hash);
  end
  else
    result := ecvBadParameter;
end;

function TEccCertificateChain.IsSigned(sign: TEccSignatureCertifiedFile): TEccValidity;
begin
  if (self <> nil) and
     (sign <> nil) then
    result := IsSigned(sign.Certified, sign.Sha256Digest)
  else
    result := ecvBadParameter;
end;

function TEccCertificateChain.IsSigned(sign: TEccSignatureCertified;
  const hash: THash256): TEccValidity;
begin
  if (self <> nil) and
     (sign <> nil) then
    result := IsSigned(sign.Certified, hash)
  else
    result := ecvBadParameter;
end;

function TEccCertificateChain.IsSigned(const sign: TEccSignatureCertifiedContent;
  Data: pointer; Len: integer; TimeUtc: TDateTime): TEccValidity;
begin
  if (Data = nil) or
     (Len <= 0) then
    result := ecvBadParameter
  else
    result := IsSigned(sign, Sha256Digest(Data, Len), TimeUtc);
end;

function TEccCertificateChain.IsSigned(const base64sign: RawUtf8;
  const hash: THash256; TimeUtc: TDateTime): TEccValidity;
var
  sign: TEccSignatureCertifiedContent;
begin
  if sign.FromBase64(base64sign) then
    result := IsSigned(sign, hash, TimeUtc)
  else
    result := ecvBadParameter;
end;

function TEccCertificateChain.IsSigned(const base64sign: RawUtf8;
  Data: pointer; Len: integer; TimeUtc: TDateTime): TEccValidity;
var
  sign: TEccSignatureCertifiedContent;
begin
  if sign.FromBase64(base64sign) then
    result := IsSigned(sign, Data, Len, TimeUtc)
  else
    result := ecvBadParameter;
end;

function TEccCertificateChain.IsSigned(const sign: TEccSignatureCertifiedContent;
  const hash: THash256; TimeUtc: TDateTime): TEccValidity;
var
  authkey: TEccPublicKey;
begin
  if self = nil then
    result := ecvBadParameter
  else
  begin
    result := GetKeyBySerial(sign.AuthoritySerial, [cuDigitalSignature],
      authkey, ecvUnknown, EccToDateTime(sign.Date));
    if result in ECC_VALIDSIGN then
      result := sign.Verify(hash, authkey, result, TimeUtc);
  end;
end;

function TEccCertificateChain.SaveToJson: RawUtf8;
begin
  result := JsonEncodeArrayUtf8(SaveToArray);
end;

function TEccCertificateChain.SaveToArray: TRawUtf8DynArray;
var
  i, n: PtrInt;
begin
  fSafe.ReadLock;
  try
    n := length(fItems);
    SetLength(result{%H-}, n + length(fCrl));
    // store first the certificates
    for i := 0 to n - 1 do
      result[i] := fItems[i].PublicToBase64;
    // then the revocation serials
    for i := 0 to high(fCrl) do
      result[i + n] := fCrl[i].ToBase64;
  finally
    fSafe.ReadUnLock;
  end;
end;

function TEccCertificateChain.LoadFromJson(const json: RawUtf8): boolean;
var
  values: TRawUtf8DynArray;
  tmp: TSynTempBuffer; // private copy
begin
  tmp.Init(json);
  try
    result :=
      (DynArrayLoadJson(values, tmp.buf, TypeInfo(TRawUtf8DynArray)) <> nil) and
      LoadFromArray(values);
  finally
    tmp.Done;
  end;
end;

function TEccCertificateChain.SaveToBinary: RawByteString;
var
  i, n: PtrInt;
  c: TEccCertificate;
  st: TRawByteStringStream;
begin
  st := TRawByteStringStream.Create;
  try
    fSafe.ReadLock;
    try
      // genuine magic header
      n := CHAIN_MAGIC;
      st.WriteBuffer(n, 4);
      // store first the certificates
      n := length(fItems);
      if n > 65535 then
        raise EEccException.Create('Too many items in Chain');
      st.WriteBuffer(n, 2);
      for i := 0 to n - 1 do
      begin
        c := fItems[i];
        c.fStoreOnlyPublicKey := true; // for safety
        c.SaveToStream(st);
        c.fStoreOnlyPublicKey := false;
      end;
      // then the revocation serials
      n := length(fCrl);
      if n > 65535 then
        raise EEccException.Create('Too many CRLs in Chain');
      st.WriteBuffer(n, 2);
      for i := 0 to n - 1 do
        fCrl[i].SaveToStream(st);
    finally
      fSafe.ReadUnLock;
    end;
    result := st.DataString;
  finally
    st.Free;
  end;
end;

function TEccCertificateChain.LoadFromBinary(const binary: RawByteString): boolean;
var
  i, n: PtrInt;
  st: TRawByteStringStream;
begin
  result := false;
  if (self = nil) or
     (length(binary) < 8) or
     (PCardinal(binary)^ <> CHAIN_MAGIC) then
    exit;
  st := TRawByteStringStream.Create(binary);
  fSafe.WriteLock;
  try
    LockedClear;
    // follow SaveToBinary() layout
    if st.Read(n, 4) <> 4 then
      exit; // CHAIN_MAGIC
    n := 0;
    if st.Read(n, 2) <> 2 then
      exit;
    SetLength(fItems, n);
    for i := 0 to n - 1 do
    begin
      fItems[i] := TEccCertificate.CreateVersion(fMaxVersion);
      if not fItems[i].LoadFromStream(st) then
        exit;
    end;
    if st.Read(n, 2) <> 2 then
      exit;
    SetLength(fCrl, n);
    for i := 0 to n - 1 do
      if not fCrl[i].LoadFromStream(st) then
        exit;
    result := true;
  finally
    if not result then
      LockedClear;
    fSafe.WriteUnLock;
    st.Free;
  end;
end;

function TEccCertificateChain.LoadFromArray(const values: TRawUtf8DynArray): boolean;
var
  i, n, crl: PtrInt;
begin
  result := false;
  if self = nil then
    exit;
  fSafe.WriteLock;
  try
    LockedClear;
    SetLength(fItems, length(values));
    n := 0;
    crl := 0;
    for i := 0 to high(values) do
      if values[i] <> '' then
        if PWord(values[i])^ = ord('/') + ord('/') shl 8 then
        begin
          // this is a base64-encoded TEccCertificateRevocation entry
          if crl = length(fCrl) then
            SetLength(fCrl, NextGrow(crl));
          if not fCrl[crl].FromBase64(values[i]) then
            exit;
          inc(crl);
        end
        else
        begin
          // regular TEccCertificate entry
          fItems[i] := TEccCertificate.CreateVersion(fMaxVersion);
          if not fItems[i].FromBase64(values[i]) then
            exit;
          inc(n);
        end;
    SetLength(fItems, n);
    if crl <> 0 then
      DynArrayFakeLength(fCrl, crl);
    result := true;
  finally
    if not result then
      LockedClear;
    fSafe.WriteUnLock;
  end;
end;

function TEccCertificateChain.ValidateItems: TEccCertificateObjArray;
var
  i: PtrInt;
begin
  result := nil;
  if self = nil then
    exit;
  fSafe.ReadLock;
  try
    for i := 0 to high(fItems) do
      if not (IsValid(fItems[i]) in ECC_VALIDSIGN) then
        ObjArrayAdd(result, fItems[i]);
  finally
    fSafe.ReadUnLock;
  end;
end;

constructor TEccCertificateChain.CreateFromFile(const jsonfile: TFileName);
begin
  Create;
  if not LoadFromFile(jsonfile) then
    EEccException.RaiseUtf8(
      'Invalid %.CreateFromFile("%")', [self, jsonfile]);
end;

constructor TEccCertificateChain.CreateFromFiles(
  const files: array of TFileName);
var
  i: PtrInt;
  auth: TEccCertificate;
begin
  Create;
  for i := 0 to high(files) do
  begin
    auth := TEccCertificate.CreateVersion(fMaxVersion);
    try
      if auth.FromFile(files[i]) then
      begin
        ObjArrayAdd(fItems, auth);
        auth := nil;
      end
      else
        EEccException.RaiseUtf8(
          '%.CreateFromFiles: invalid file [%]', [self, files[i]]);
    finally
      auth.Free;
    end;
  end;
end;

function TEccCertificateChain.SaveToFileVariant: variant;
var
  pub64, items, crls: TDocVariantData;
  i, ni, nc: PtrInt;
begin
  fSafe.ReadLock;
  try
    ni := length(fItems);
    nc := length(fCrl);
    pub64.InitFast(ni + nc, dvArray);
    items.InitFast(ni, dvArray);
    crls.InitFast(nc, dvArray);
    for i := 0 to ni - 1 do
    begin
      pub64.AddItemText(fItems[i].PublicToBase64);
      items.AddItem(fItems[i].ToVariant(false));
    end;
    for i := 0 to nc - 1 do
      with fCrl[i] do
      begin
        pub64.AddItemText(ToBase64);
        crls.AddItem(_ObjFast([
          'Version',    Version,
          'Date',       EccText(Date),
          'Reason',     Reason,
          'ReasonText', ToText(TCryptCertRevocationReason(Reason))^,
          'Serial',     EccText(Serial)]));
      end;
  finally
    fSafe.ReadUnLock;
  end;
  result := _ObjFast([
    'PublicBase64', variant(pub64),
    'Items',        variant(items),
    'CRL',          variant(crls)]);
end;

function TEccCertificateChain.SaveToFileContent: RawUtf8;
begin
  _VariantSaveJson(SaveToFileVariant, twJsonEscape, result{%H-});
end;

function TEccCertificateChain.LoadFromFileContent(
  const cajsoncontent: RawUtf8): boolean;
var
  doc: TDocVariantData;
  values: TRawUtf8DynArray;
begin
  result := false;
  if doc.InitJson(cajsoncontent, JSON_FAST) then
  begin
    doc.GetAsDocVariantSafe('PublicBase64')^.ToRawUtf8DynArray(values);
    result := LoadFromArray(values);
  end;
end;

function GetChainFileName(const jsonfile: TFileName): TFileName;
begin
  if ExtractFileExt(jsonfile) = '' then
    result := jsonfile + ECCCERTIFICATES_FILEEXT
  else
    result := jsonfile;
end;

function TEccCertificateChain.SaveToFile(const jsonfile: TFileName): boolean;
var
  json: RawUtf8;
begin
  if (Count = 0) or
     (jsonfile = '') then
    result := false
  else
  begin
    json := SaveToFileContent;
    result := JsonBufferReformatToFile(pointer(json), GetChainFileName(jsonfile));
  end;
end;

function TEccCertificateChain.LoadFromFile(const jsonfile: TFileName): boolean;
var
  json: RawUtf8;
  fn: TFileName;
begin
  fn := GetChainFileName(jsonfile);
  json := StringFromFile(fn);
  if json = '' then
    json := StringFromFile(EccKeyFileFolder + fn);
  if json = '' then
    result := false
  else
    result := LoadFromFileContent(json);
end;



{ ***************** IProtocol Implemented using Public Key Cryptography }

function ToText(algo: TEcdheAuth): PShortString;
begin
  result := GetEnumName(TypeInfo(TEcdheAuth), ord(algo));
end;

function ToText(algo: TEcdheKdf): PShortString;
begin
  result := GetEnumName(TypeInfo(TEcdheKdf), ord(algo));
end;

function ToText(algo: TEcdheEF): PShortString;
begin
  result := GetEnumName(TypeInfo(TEcdheEF), ord(algo));
end;

function ToText(algo: TEcdheMac): PShortString;
begin
  result := GetEnumName(TypeInfo(TEcdheMac), ord(algo));
end;


{ TEcdheProtocol }

var
  _FromKeySetCA: TEccCertificateChain;
  _FromKeySetCARefCount: integer;

constructor TEcdheProtocol.Create(aAuth: TEcdheAuth; aPKI: TEccCertificateChain;
  aPrivate: TEccCertificateSecret; aEF: TEcdheEF; aPrivateOwned: boolean);
var
  res: TEccValidity;
begin
  if (aPKI <> nil) and
     (aPrivate <> nil) then
  begin
    res := aPKI.IsValid(aPrivate);
    if not (res in ECC_VALIDSIGN) then
      EEccException.RaiseUtf8('%.Create failed: aPKI.IsValid(%)=%',
        [self, aPrivate.Serial, ToText(res)^]);
  end;
  inherited Create;
  fAlgo.auth := aAuth;
  fAlgo.ef := aEF;
  fAlgo.mac := ECDHEPROT_EF2MAC[aEF];
  if (aPKI = nil) and
     (_FromKeySetCA <> nil) then
  begin
    fPKI := _FromKeySetCA;
    InterlockedIncrement(_FromKeySetCARefCount);
  end
  else
    fPKI := aPKI;
  fPrivate := aPrivate;
  if aPrivateOwned then
    include(fOwned, ownPrivate);
  fEFSalt := 'ecdhesalt';
  fMacSalt := 'ecdhemac';
end;

constructor TEcdheProtocol.CreateFrom(aAnother: TEcdheProtocol);
begin
  Create(aAnother.fAlgo.auth, aAnother.fPKI, aAnother.fPrivate);
  fAlgo := aAnother.fAlgo; // may have been customized via properties
  fEFSalt := aAnother.fEFSalt;
  fMacSalt := aAnother.fMacSalt;
end;

destructor TEcdheProtocol.Destroy;
begin
  if fAes[true] <> fAes[false] then
    fAes[true].Free; // TAesAbstract.CloneEncryptDecrypt may have set the same
  fAes[false].Free;
  FillZero(fkM[false].b);
  FillZero(fkM[true].b);
  if fPKI <> nil then
    if ownPKI in fOwned then
      fPKI.Free
    else if (fPKI = _FromKeySetCA) and
            (_FromKeySetCARefCount > 0) then
      InterlockedDecrement(_FromKeySetCARefCount);
  if ownPrivate in fOwned then
    fPrivate.Free;
  inherited Destroy;
end;

class procedure TEcdheProtocol.FromKeySetCA(aPKI: TEccCertificateChain);
begin
  if _FromKeySetCA <> nil then
    if _FromKeySetCARefCount > 0 then
      EEccException.RaiseUtf8(
        '%.FromKeySetCA: % is still used by %',
        [self, _FromKeySetCA, Plural('instance', _FromKeySetCARefCount)])
    else
      _FromKeySetCA.Free;
  _FromKeySetCA := aPKI;
end;

class function TEcdheProtocol.FromKey(const aKey: RawUtf8;
  aServer: boolean): TEcdheProtocol;
var
  sw: TSynNameValue;
  pw, c: RawUtf8;
  fn: TFileName;
  algo: TEcdheAlgo;
  ca: TEccCertificateChain;
  chain: TRawUtf8DynArray;
  priv: TEccCertificateSecret;
  i, pr: integer;
begin
  result := nil;
  if not IdemPChar(pointer(aKey), 'A=') then
    exit;
  // a=mutual;k=hmacsha256;e=aescrc128;m=duringef;p=34a2;pw=password;pr=60000;ca=..
  sw.InitFromCsv(pointer(aKey), '=', ';');
  if not sw.ValueEnum('a', TypeInfo(TEcdheAuth), algo.auth) then
    exit; // mandatory parameter
  // e.g. 'a=mutual;e=aesctc128;p=34a2;pw=password;ca=..'
  sw.ValueEnum('k', TypeInfo(TEcdheKdf), algo.kdf);
  sw.ValueEnum('e', TypeInfo(TEcdheEF), algo.ef);
  sw.ValueEnum('m', TypeInfo(TEcdheMac), algo.mac, ord(ECDHEPROT_EF2MAC[algo.ef]));
  // compute ca: TEccCertificateChain
  ca := nil;
  c := sw.Str['ca'];
  if c <> '' then
  begin
    ca := TEccCertificateChain.Create;
    Utf8ToFileName(c, fn);
    if not ca.LoadFromFile(fn) then
    begin
      CsvToRawUtf8DynArray(c, ',', '', chain);
      for i := 0 to high(chain) do
        chain[i] := UnQuoteSqlString(chain[i]);
      if ca.LoadFromArray(chain) then
        ca.IsValidCached := true
      else // for faster Clone process
        FreeAndnil(ca);
    end;
  end;
  if (ca = nil) and
     (_FromKeySetCA <> nil) then
  begin
    ca := _FromKeySetCA;
    InterlockedIncrement(_FromKeySetCARefCount);
  end;
  // compute priv: TEccCertificateSecret
  priv := nil;
  Utf8ToFileName(sw.Str['p'], fn);
  pw := sw.Str['pw'];
  pr := sw.ValueInt('pr', DEFAULT_ECCROUNDS);
  if (fn <> '') and
     (pw <> '') and
     EccKeyFileFind(fn, {privkey=}true) then
    priv := TEccCertificateSecret.CreateFromSecureFile(fn, pw, pr);
  // create a new TEcdheProtocol instance with those parameters
  result := ECDHEPROT_CLASS[aServer].Create(algo.auth, ca, priv);
  result.KDF := algo.kdf;
  result.EF := algo.ef;
  result.MAC := algo.mac;
  if (ca <> nil) and
     (ca <> _FromKeySetCA) then
    include(result.fOwned, ownPKI);
  if priv <> nil then
    include(result.fOwned, ownPrivate);
end;

class function TEcdheProtocol.FromKeyCompute(
  const privkey, privpassword: RawUtf8; privrounds: integer;
  const pki: RawUtf8; auth: TEcdheAuth; kdf: TEcdheKdf; ef: TEcdheEF;
  mac: TEcdheMac; customkey: cardinal): RawUtf8;
begin
  FormatUtf8('a=%', [ord(auth)], result);
  if kdf <> low(kdf) then
    result := result + ';k=' + TrimLeftLowerCaseShort(ToText(kdf));
  if ef <> low(ef) then
    result := result + ';e=' + TrimLeftLowerCaseShort(ToText(ef));
  if mac <> low(mac) then
    result := result + ';m=' + TrimLeftLowerCaseShort(ToText(mac));
  result := lowercase(result);
  if pki <> '' then
    result := result + ';ca=' + pki;
  if privkey <> '' then
  begin
    result := FormatUtf8('%;p=%;pw=%', [result, privkey, privpassword]);
    if privrounds <> DEFAULT_ECCROUNDS {=60000} then
      result := FormatUtf8('%;pr=%', [result, privrounds]);
  end;
  result := TSynPersistentWithPassword.ComputePassword(result, customkey);
end;

class function TEcdheProtocol.FromPasswordSecureFile(
  const aPasswordSecureFile: RawUtf8; aServer: boolean;
  aAuth: TEcdheAuth; aEF: TEcdheEF; aRounds: integer): TEcdheProtocol;
var
  i: PtrInt;
  fn: TFileName;
  priv: TEccCertificateSecret;
begin
  result := nil;
  // did we supply a fully qualified 'password#xxxx.private' key file name?
  if not EndWith(aPasswordSecureFile, '.PRIVATE') then
    exit;
  for i := length(aPasswordSecureFile) - 8 downto 1 do
    // PosExChar() fails if '#' appears within the password -> manual loop
    if aPasswordSecureFile[i] = '#' then
    begin
      Utf8ToFileName( // trim '.private' extension for EccKeyFileFind()
        copy(aPasswordSecureFile, i + 1, length(aPasswordSecureFile) - i - 9), fn);
      if EccKeyFileFind(fn, {privkey=}true) then
      begin
        // try to load the 'xxxx.private' secret key
        priv := nil;
        try
          // note: TEcdheProtocol.FromKeySetCA() should have set the global CA
          priv := TEccCertificateSecret.CreateFromSecureFile(
            fn, {password=}copy(aPasswordSecureFile, 1, i - 1), aRounds);
          result := ECDHEPROT_CLASS[aServer].Create(
            aAuth, nil, priv, aEF, {privowned=}true);
        except
          // error loading the private key file
          priv.Free;
          result := nil;
        end;
      end;
      break;
    end;
end;

const
  ED: array[boolean] of string[7] = (
    'Decrypt', 'Encrypt');

procedure TEcdheProtocol.SetIVAndMacNonce(aEncrypt: boolean);
begin
  if fAes[aEncrypt] = nil then
    EEccException.RaiseUtf8(
      '%.% with no handshake', [self, ED[aEncrypt]]);
  fAes[aEncrypt].IV := fkM[aEncrypt].Lo; // kM is a CTR -> IV unicity
  if fAlgo.mac = macDuringEF then
    if not fAes[aEncrypt].MacSetNonce(aEncrypt, fkM[aEncrypt].b) then
      EEccException.RaiseUtf8(
        '%.%: macDuringEF not available in %/%',
        [self, ED[aEncrypt], ToText(fAlgo.ef)^, fAes[aEncrypt]]);
end;

procedure TEcdheProtocol.IncKM(aEncrypt: boolean);
begin
  with fkM[aEncrypt] do
  begin
    inc(q[0]);
    if q[0] <> 0 then
      exit;
    inc(q[1]);
    if q[1] <> 0 then
      exit;
    inc(q[2]);
    if q[2] = 0 then
      inc(q[3]);
  end;
end;

procedure TEcdheProtocol.ComputeMAC(aEncrypt: boolean;
  aEncrypted: pointer; aLen: integer; out aMAC: THash256Rec);
var
  i: PtrInt;
  c: cardinal;
begin
  // retrieve or compute the MAC
  case fAlgo.mac of
    macDuringEF:
      if aEncrypt and
         not fAes[aEncrypt].MacEncryptGetTag(aMAC.b) then
        // should have been computed during Encryption process
        EEccException.RaiseUtf8('%.%: macDuringEF not available in %/%',
          [self, ED[aEncrypt], ToText(fAlgo.ef)^, fAes[aEncrypt]]);
    macHmacCrc256c:
      HmacCrc256c(@fkM[aEncrypt], aEncrypted, SizeOf(THash256), aLen, aMAC.b);
    macHmacSha256:
      HmacSha256(@fkM[aEncrypt], aEncrypted, SizeOf(THash256), aLen, aMAC.b);
    macHmacCrc32c:
      begin
        c := HmacCrc32c(@fkM[aEncrypt], aEncrypted, SizeOf(THash256), aLen);
        for i := 0 to 7 do
          aMAC.c[i] := c; // naive 256-bit diffusion
      end;
    macXxHash32:
      begin
        c := xxHash32(fkM[aEncrypt].i0, aEncrypted, aLen);
        for i := 0 to 7 do
          aMAC.c[i] := c; // naive 256-bit diffusion
      end;
    macNone:
      crc256c(@fkM[aEncrypt], SizeOf(THash256), aMAC.b); // replay attack only
  else
    EEccException.RaiseUtf8(
      '%.%: ComputeMAC %?', [self, ED[aEncrypt], ToText(fAlgo.mac)^]);
  end;
  // always increase sequence number against replay attacks
  IncKM(aEncrypt);
end;

procedure TEcdheProtocol.Encrypt(const aPlain: RawByteString;
  out aEncrypted: RawByteString);
var
  len: integer;
begin
  fSafe.Lock;
  try
    SetIVAndMacNonce({encrypt=}true);
    len := fAes[true].EncryptPkcs7Length(length(aPlain), false);
    FastNewRawByteString(aEncrypted, len + SizeOf(THash256)); // trailing MAC
    // encrypt the input
    fAes[true].EncryptPkcs7Buffer(
      pointer(aPlain), pointer(aEncrypted), length(aPlain), len, false);
    // compute and store the MAC, calling IncKM(true)
    ComputeMac({encrypt=}true, pointer(aEncrypted), len,
      PHash256Rec(@PByteArray(aEncrypted)[len])^);
  finally
    fSafe.UnLock;
  end;
end;

function TEcdheProtocol.Decrypt(const aEncrypted: RawByteString;
  out aPlain: RawByteString): TProtocolResult;
var
  P: PAnsiChar absolute aEncrypted;
  len: PtrInt;
  mac: THash256Rec;
begin
  result := sprInvalidMAC;
  len := length(aEncrypted) - SizeOf(THash256); // there is a trailing MAC
  if len <= 0 then
    exit;
  fSafe.Lock;
  try
    SetIVAndMacNonce({encrypt=}false);
    // decrypt the input
    aPlain := fAes[false].DecryptPkcs7Buffer(P, len, false, false);
    if aPlain = '' then
    begin
      IncKM(false); // no MAC, but increase sequence on void/invalid message
      exit;
    end;
    // validate with the MAC stored after the input, calling IncKM(false)
    ComputeMac({encrypt=}false, P, len, mac);
    if fAlgo.mac = macDuringEF then
    begin
      // AES-GCM requires to call a specific decryption method
      if fAes[false].MacDecryptCheckTag(PHash256(P + len)^) then
        result := sprSuccess
    end
    else if IsEqual(mac.b, PHash256(P + len)^) then
      result := sprSuccess;
  finally
    fSafe.Unlock;
  end;
end;

function TEcdheProtocol.CheckError(const aEncrypted: RawByteString): TProtocolResult;
begin
  if fAlgo.mac <> macDuringEF then
  begin
    result := sprUnsupported;
    exit;
  end;
  fSafe.Lock;
  try
    SetIVAndMacNonce({encrypt=}false);
    if fAes[false].MacCheckError(pointer(aEncrypted), length(aEncrypted)) then
      result := sprSuccess
    else
      result := sprInvalidMAC;
  finally
    fSafe.Unlock;
  end;
end;

const
  /// how TEcdheProtocol.SharedSecret initialize the AES engines
  // - if you link mormot.core.openssl.pas, CTR and GCM will use its engines
  ECDHEPROT_EF2AES: array[TEcdheEF] of TAesMode = (
  // efAesCrc  efAesCfb  efAesOfb efAesCtr efAesCbc
    mCfc, mCfb, mOfb, mCtr, mCbc, // 128-bit
    mCfc, mCfb, mOfb, mCtr, mCbc, // 256-bit
  // cfAesGcm   cfAesCtc
    mGcm, mGcm, mCtc, mCtc);

procedure TEcdheProtocol.SharedSecret(sA, sB: PHash256);
var
  secret: THash256;

  procedure ComputeSecret(const salt: RawByteString);
  var
    hmac: THmacSha256;
  begin
    hmac.Init(pointer(salt), length(salt));
    if fAlgo.auth <> authServer then
      hmac.Update(sA^);
    if fAlgo.auth <> authClient then
      hmac.Update(sB^);
    hmac.Update(fRndA);
    hmac.Update(fRndB);
    hmac.Done(secret);
  end;

begin
  if fAes[false] <> nil then
    EEccException.RaiseUtf8('%.SharedSecret already called', [self]);
  if fAlgo.kdf <> kdfHmacSha256 then
    EEccException.RaiseUtf8('%.SharedSecret %?', [self, ToText(fAlgo.kdf)^]);
  try
    ComputeSecret(fEFSalt);
    fAes[false] := TAesFast[ECDHEPROT_EF2AES[fAlgo.ef]].Create(
      secret, ECDHEPROT_EF2BITS[fAlgo.ef]);
    fAes[true] := fAes[false].CloneEncryptDecrypt;
    ComputeSecret(fMacSalt);
    fkM[false].b := secret; // first 128-bit also used as AES IV
    fkM[true].b := secret;
  finally
    FillZero(secret);
  end;
end;

function TEcdheProtocol.Verify(frame: PByteArray; len: integer;
  const QC: TEccCertificateContentV1; out res: TProtocolResult): boolean;
var
  hash: TSha256Digest;
  sha: TSha256;
  QC2: TEccCertificateContent absolute QC; // V1 is binary compatible with V2
begin
  result := false;
  res := sprInvalidCertificate;
  if QC.Version <> 1 then
    exit;
  if fPKI <> nil then
  begin
    fCertificateValidity := fPKI.IsValidRaw(QC2);
    if not (fCertificateValidity in ECC_VALIDSIGN) then
      exit;
  end
  else if not QC2.Check then
    exit;
  dec(len, SizeOf(TEccSignature)); // Sign at the latest position
  sha.Full(frame, len, hash);
  res := sprInvalidSignature;
  if not Ecc256r1Verify(QC.Signed.PublicKey, hash, PEccSignature(@frame[len])^) then
    exit;
  res := sprSuccess;
  result := true;
end;

procedure TEcdheProtocol.Sign(frame: PByteArray; len: integer;
  out QC: TEccCertificateContentV1);
var
  hash: TSha256Digest;
  sha: TSha256;
begin
  QC := fPrivate.fContent.Head;
  if QC.Version <> 1 then
    EEccException.RaiseUtf8('%.Sign: require V1 %', [self, fPrivate]);
  dec(len, SizeOf(TEccSignature)); // Sign at the latest position
  sha.Full(frame, len, hash);
  if not Ecc256r1Sign(fPrivate.fPrivateKey, hash, PEccSignature(@frame[len])^) then
    EEccException.RaiseUtf8('%.Sign: Ecc256r1Sign?', [self]);
end;

function TEcdheProtocol.Clone: IProtocol;
begin
  result := TEcdheProtocolClass(ClassType).CreateFrom(self);
end;


{ TEcdheProtocolClient }

constructor TEcdheProtocolClient.Create(aAuth: TEcdheAuth;
  aPKI: TEccCertificateChain; aPrivate: TEccCertificateSecret; aEF: TEcdheEF;
  aPrivateOwned: boolean);
begin
  if (aAuth <> authServer) and
     not aPrivate.CheckCRC then
    EEccException.RaiseUtf8(
      '%.Create: need valid Private Key for %', [self, ToText(aAuth)^])
  else
    inherited;
end;

procedure TEcdheProtocolClient.ComputeHandshake(out aClient: TEcdheFrameClient);
begin
  // setup the algorithms
  if fAes[false] <> nil then
    EEccException.RaiseUtf8(
      '%.ComputeHandshake already called', [self]);
  FillCharFast(aClient, SizeOf(aClient), 0);
  aClient.algo := fAlgo;
  // client-side randomness for ephemeral keys and signatures
  RandomBytes(@fRndA, SizeOf(fRndA)); // Lecuyer is enough for public random
  aClient.RndA := fRndA;
  // generate the client ephemeral key
  if fAlgo.auth <> authClient then
    if not Ecc256r1MakeKey(aClient.QE, fdE) then
      EEccException.RaiseUtf8('%.ComputeHandshake: MakeKey?', [self]);
  // compute the client ephemeral signature
  if fAlgo.auth <> authServer then
    Sign(@aClient, SizeOf(aClient), aClient.QCA);
end;

function TEcdheProtocolClient.ValidateHandshake(const aServer: TEcdheFrameServer):
  TProtocolResult;
var
  sA, sB: THash256;
begin
  // validate the algorithms
  result := sprUnexpectedAlgorithm;
  if cardinal(aServer.algo) <> cardinal(fAlgo) then
    exit;
  // check server-side randomness
  result := sprBadRequest;
  if IsZero(fRndA) or
     not IsEqual(aServer.RndA, fRndA) or
     IsZero(aServer.RndB) or
     IsEqual(aServer.RndA, aServer.RndB) then
    exit;
  fRndB := aServer.RndB;
  // server ephemeral authenticatation from its public key
  if fAlgo.auth <> authClient then
    if not Verify(@aServer, SizeOf(aServer), aServer.QCB, result) then
      exit;
  // compute the ephemeral shared secret keys for AES and MAC+IV
  try
    result := sprInvalidEphemeralKey;
    if fAlgo.auth <> authServer then
      if not Ecc256r1SharedSecret(aServer.QF, fPrivate.fPrivateKey, sA) then
        exit;
    result := sprInvalidPublicKey;
    if fAlgo.auth <> authClient then
      if not Ecc256r1SharedSecret(aServer.QCB.Signed.PublicKey, fdE, sB) then
        exit;
    SharedSecret(@sA, @sB);
  finally
    FillZero(sA);
    FillZero(sB);
    FillZero(fdE);
  end;
  result := sprSuccess;
end;

function TEcdheProtocolClient.ProcessHandshake(const MsgIn: RawUtf8;
  out MsgOut: RawUtf8): TProtocolResult;
var
  out1: TEcdheFrameClient;
  in2: TEcdheFrameServer;
begin
  if MsgIn = '' then
  begin
    ComputeHandshake(out1);
    MsgOut := BinToBase64(@out1, SizeOf(out1));
    result := sprSuccess;
  end
  else if Base64ToBin(pointer(MsgIn), @in2, length(MsgIn), SizeOf(in2)) then
    result := ValidateHandshake(in2)
  else
    result := sprBadRequest;
end;


{ TEcdheProtocolServer }

constructor TEcdheProtocolServer.Create(aAuth: TEcdheAuth;
  aPKI: TEccCertificateChain; aPrivate: TEccCertificateSecret; aEF: TEcdheEF;
  aPrivateOwned: boolean);
begin
  if (aAuth <> authClient) and
     not aPrivate.CheckCRC then
    EEccException.RaiseUtf8(
      '%.Create: need valid Private Key for %', [self, ToText(aAuth)^]);
  inherited;
  include(fAuthorized, aAuth); // conservative default
end;

constructor TEcdheProtocolServer.CreateFrom(aAnother: TEcdheProtocol);
begin
  inherited CreateFrom(aAnother);
  fAuthorized := (aAnother as TEcdheProtocolServer).fAuthorized;
end;

function TEcdheProtocolServer.ComputeHandshake(const aClient: TEcdheFrameClient;
  out aServer: TEcdheFrameServer): TProtocolResult;
var
  dF: TEccPrivateKey;
  sA, sB: THash256;
begin
  // check the algorithms
  result := sprUnexpectedAlgorithm;
  if cardinal(aClient.algo) <> cardinal(fAlgo) then
  begin
    if not (aClient.algo.auth in fAuthorized) or
       (aClient.algo.kdf <> fAlgo.kdf) or
       (aClient.algo.ef <> fAlgo.ef) or
       (aClient.algo.mac <> fAlgo.mac) then
      exit;
    if (aClient.algo.auth <> authClient) and
       not fPrivate.CheckCRC then
      exit;
    fAlgo.auth := aClient.algo.auth; // client forced another mode
  end;
  // client ephemeral authentication from its public key
  result := sprBadRequest;
  if IsZero(aClient.RndA) then
    exit;
  fRndA := aClient.RndA;
  if fAlgo.auth <> authServer then
    if not Verify(@aClient, SizeOf(aClient), aClient.QCA, result) then
      exit;
  // compute the ephemeral shared secret keys for AES and MAC+IV
  FillCharFast(aServer, SizeOf(aServer), 0);
  aServer.algo := fAlgo;
  aServer.RndA := fRndA;
  RandomBytes(@fRndB, SizeOf(fRndB)); // Lecuyer is enough for public random
  aServer.RndB := fRndB;
  if fAlgo.auth <> authServer then
    if not Ecc256r1MakeKey(aServer.QF, dF) then
      EEccException.RaiseUtf8('%.ComputeHandshake: MakeKey?', [self]);
  try
    result := sprInvalidPublicKey;
    if fAlgo.auth <> authServer then
      if not Ecc256r1SharedSecret(aClient.QCA.Signed.PublicKey, dF, sA) then
        exit;
    result := sprInvalidEphemeralKey;
    if fAlgo.auth <> authClient then
      if not Ecc256r1SharedSecret(aClient.QE, fPrivate.fPrivateKey, sB) then
        exit;
    SharedSecret(@sA, @sB);
  finally
    FillZero(sA);
    FillZero(sB);
    FillZero(dF);
  end;
  // compute the server ephemeral signature from its private key
  if fAlgo.auth <> authClient then
    Sign(@aServer, SizeOf(aServer), aServer.QCB);
  result := sprSuccess;
end;

function TEcdheProtocolServer.ProcessHandshake(const MsgIn: RawUtf8;
  out MsgOut: RawUtf8): TProtocolResult;
var
  in1: TEcdheFrameClient;
  out1: TEcdheFrameServer;
begin
  if Base64ToBin(pointer(MsgIn), @in1, length(MsgIn), SizeOf(in1)) then
  begin
    result := ComputeHandshake(in1, out1);
    MsgOut := BinToBase64(@out1, SizeOf(out1));
  end
  else
    result := sprBadRequest;
end;


{ ********* Registration of our ECC Engine to the TCryptAsym/TCryptCert Factories }

type
  TCryptAsymInternal = class(TCryptAsym)
  protected
    fDefaultHasher: TCryptHasher;
  public
    constructor Create(const name: RawUtf8); override;
    procedure GenerateDer(out pub, priv: RawByteString; const privpwd: RawUtf8); override;
    function Sign(hasher: TCryptHasher; msg: pointer; msglen: PtrInt;
      const priv: RawByteString; out sig: RawByteString;
      const privpwd: RawUtf8 = ''): boolean; override;
    function Verify(hasher: TCryptHasher; msg: pointer; msglen: PtrInt;
      const pub, sig: RawByteString): boolean; override;
  end;


{ TCryptAsymInternal }

constructor TCryptAsymInternal.Create(const name: RawUtf8);
begin
  inherited Create(name);
  fDefaultHasher := Hasher('sha256');
  fPemPrivate := ord(pemEcPrivateKey);
  fPemPublic := ord(pemEcPublicKey);
end;

procedure TCryptAsymInternal.GenerateDer(out pub, priv: RawByteString;
  const privpwd: RawUtf8);
var
  rawpub: TEccPublicKey;
  rawpriv: TEccPrivateKey;
begin
  if privpwd <> '' then
    ECrypt.RaiseUtf8('%.GenerateDer: unsupported privpwd', [self]);
  if not Ecc256r1MakeKey(rawpub, rawpriv) then
    exit;
  pub := EccToDer(rawpub);
  priv := EccToDer(rawpriv);
  FillZero(rawpriv);
end;

function TCryptAsymInternal.Sign(hasher: TCryptHasher; msg: pointer;
  msglen: PtrInt; const priv: RawByteString; out sig: RawByteString;
  const privpwd: RawUtf8): boolean;
var
  digest: THash512Rec;
  key: TEccPrivateKey;
  sign: TEccSignature;
begin
  result := false;
  if hasher = nil then
    hasher := fDefaultHasher;
  if (hasher = nil) or
     (priv = '') or
     (privpwd <> '') then
    exit; // invalid or unsupported
  if not PemDerRawToEcc(priv, key) then
    exit; // accept key in raw, PEM or DER format
  FillZero(digest.b); // hasher may not fill all bytes needed by the algorithm
  hasher.Full(msg, msglen, digest);
  result := Ecc256r1Sign(key, digest.Lo, sign);
  if result then
    sig := EccToDer(sign); // return signature in PEM format
  FillZero(key);
end;

function TCryptAsymInternal.Verify(hasher: TCryptHasher; msg: pointer;
  msglen: PtrInt; const pub, sig: RawByteString): boolean;
var
  digest: THash512Rec;
  key: TEccPublicKey;
  sign: TEccSignature;
begin
  result := false;
  if hasher = nil then
    hasher := fDefaultHasher;
  if (hasher = nil) or
     (pub = '') then
    exit; // invalid or unsupported
  if not PemDerRawToEcc(sig, sign) or
     not PemDerRawToEcc(pub, key) then
    exit; // accept signature and public key in raw, PEM or DER format
  FillZero(digest.b); // hasher may not fill all bytes needed by the algorithm
  hasher.Full(msg, msglen, digest);
  result := Ecc256r1Verify(key, digest.Lo, sign);
end;


{ TCryptPublicKeyEcc }

destructor TCryptPublicKeyEcc.Destroy;
begin
  inherited Destroy;
  fEcc.Free;
end;

function TCryptPublicKeyEcc.Load(Algorithm: TCryptKeyAlgo;
  const PublicKeySaved: RawByteString): boolean;
begin
  result := false;
  if (fKeyAlgo <> ckaNone) or
     (PublicKeySaved = '') then
    exit;
  case Algorithm of
    // we only support secp256r1/prime256v1 kind of elliptic curve by now
    ckaEcc256:
      // try raw uncompressed format, as stored in a X509 certificate
      if Ecc256r1CompressAsn1(PublicKeySaved, fEccPub) or
         // try regular ASN1_SEQ format in PEM or DER
         PemDerRawToEcc(PublicKeySaved, fEccPub) then
      begin
        fEcc := TEcc256r1Verify.Create(fEccPub); // OpenSSL or our pascal code
        fKeyAlgo := Algorithm;
        fSubjectPublicKey := PublicKeySaved;
        result := true;
      end;
  else
    ECrypt.RaiseUtf8('%.Create: unsupported %', [self, ToText(fKeyAlgo)^]);
  end;
end;

function TCryptPublicKeyEcc.VerifyDigest(Sig: pointer; Dig: THash512Rec;
  SigLen, DigLen: integer; Hash: THashAlgo): boolean;
var
  eccsig: TEccSignature;
begin
  result := false;
  if (self <> nil) and
     (DigLen <> 0) then
    case fKeyAlgo of
      ckaEcc256:
        begin
          if SigLen = SizeOf(eccsig) then                // e.g. in JWT
            MoveFast(Sig^, eccsig, SigLen)
          else if not DerToEcc(Sig, SigLen, eccsig) then // e.g. in X.509 Cert
            exit;
          // secp256r1 digital signature verification
          result := fEcc.Verify(Dig.Lo, eccsig); // thread-safe
        end;
    end;
end;

function TCryptPublicKeyEcc.GetParams(out x, y: RawByteString): boolean;
var
  k: TEccPublicKeyUncompressed;
begin
  result := false;
  if self <> nil then
    case fKeyAlgo of
      ckaEcc256:
        // for ECC, returns the x,y uncompressed coordinates from stored ASN.1
        if Ecc256r1ExtractAsn1(fSubjectPublicKey, k) then
        begin
          FastNewRawByteString(x, ECC_BYTES);;
          FastNewRawByteString(y, ECC_BYTES);;
          bswap256(@PHash512Rec(@k)^.Lo, pointer(x));
          bswap256(@PHash512Rec(@k)^.Hi, pointer(y));
          result := true;
        end;
    end;
end;

function TCryptPublicKeyEcc.Seal(const Message: RawByteString;
  const Cipher: RawUtf8): RawByteString;
begin
  result  := '';
  if self <> nil then
    case fKeyAlgo of
      ckaEcc256:
        result := EciesSeal(Cipher, fEcc.PublicKey, Message);
    end;
end;


{ TCryptPrivateKeyEcc }

function TCryptPrivateKeyEcc.FromDer(algo: TCryptKeyAlgo;
  const der: RawByteString; pub: TCryptPublicKey): boolean;
begin
  result := false;
  if IsZero(fEcc) then
    case algo of
      // this unit supports only secp256r1/prime256v1 kind of elliptic curve
      ckaEcc256:
        if PemDerRawToEcc(der, fEcc) and
           ((pub = nil) or
            Ecc256r1MatchKeys(fEcc,
              (pub as TCryptPublicKeyEcc).fEcc.PublicKey)) then
          result := true
        else
          FillZero(fEcc);
    end;
end;

function TCryptPrivateKeyEcc.Generate(Algorithm: TCryptAsymAlgo): RawByteString;
var
  eccpub: TEccPublicKey;
begin
  result := '';
  if (self = nil) or
     (fKeyAlgo <> ckaNone) then
    exit;
  fKeyAlgo := CAA_CKA[Algorithm];
  if Algorithm = caaES256 then
    if IsZero(fEcc) and
       Ecc256r1MakeKey(eccpub, fEcc) then
      result := Ecc256r1UncompressAsn1(eccpub);
end;

destructor TCryptPrivateKeyEcc.Destroy;
begin
  inherited Destroy;
  FillZero(fEcc);
end;

function TCryptPrivateKeyEcc.ToDer: RawByteString;
var
  rawecc: RawByteString;
begin
  if self = nil then
    result := ''
  else if IsZero(fEcc) then
    result := ''
  else
  begin
    // EccToDer() raw encoding is not standard as PEM -> use PKCS#8 format
    FastSetRawByteString(rawecc, @fEcc, SizeOf(fEcc));
    // see PemDerRawToEcc() secp256r1/prime256v1 PKCS#8 PrivateKeyInfo
    result := EccPrivKeyToSeq(ckaEcc256, rawecc);
    FillZero(rawecc);
  end;
end;

function TCryptPrivateKeyEcc.ToSubjectPublicKey: RawByteString;
var
  eccpub: TEccPublicKey;
begin
  result := '';
  if self <> nil then
    if not IsZero(fEcc) then
    begin
      Ecc256r1PublicFromPrivate(fEcc, eccpub);
      result := Ecc256r1UncompressAsn1(eccpub);
    end;
end;

function TCryptPrivateKeyEcc.SignDigest(const Dig: THash512Rec; DigLen: integer;
  DigAlgo: TCryptAsymAlgo): RawByteString;
var
  eccsig: TEccSignature;
begin
  result := '';
  if (self <> nil) and
     (CAA_CKA[DigAlgo] = fKeyAlgo) and
     (HASH_SIZE[CAA_HF[DigAlgo]] = DigLen) then
    case fKeyAlgo of
      ckaEcc256:
        if Ecc256r1Sign(fEcc, Dig.Lo, eccsig) then // thread-safe
          result := EccToDer(eccsig); // standard output as from OpenSSL
    end;
end;
function TCryptPrivateKeyEcc.Open(const Message: RawByteString;
  const Cipher: RawUtf8): RawByteString;
begin
  result := '';
  if (self <> nil) and
     (fKeyAlgo = ckaEcc256) then
    result := EciesOpen(Cipher, fEcc, Message);
end;

function TCryptPrivateKeyEcc.SharedSecret(
  const PeerKey: ICryptPublicKey): RawByteString;
var
  sec: TEccSecretKey;
  pub: TCryptPublicKeyEcc;
begin
  result := '';
  if PeerKey = nil then
    exit;
  pub := PeerKey.Instance as TCryptPublicKeyEcc;
  if (self <> nil) and
     (pub.fKeyAlgo = fKeyAlgo) then
    case fKeyAlgo of
      ckaEcc256:
        try
          if Ecc256r1SharedSecret(pub.fEccPub, fEcc, sec) then
            FastSetRawByteString(result{%H-}, @sec, SizeOf(sec));
        finally
          FillZero(sec);
        end;
    end;
end;


type
  /// 'syn-es256' ICryptCert algorithm
  TCryptCertAlgoInternal = class(TCryptCertAlgo)
  protected
    fMaxVersion: integer;
  public
    constructor Create(const name: RawUtf8); override;
    function New: ICryptCert; override; // = TCryptCertInternal.Create(self)
    function FromHandle(Handle: pointer): ICryptCert; override;
  end;

  /// class implementing ICryptCert using our ECC Public Key Cryptography
  // - V2 TEccCertificate will store Subjects Baudot-encoded, and as issuer in V1
  TCryptCertInternal = class(TCryptCert)
  protected
    fEcc: TEccCertificate; // TEccCertificate or TEccCertificateSecret
    fEccByRef: boolean;
    fMaxVersion: integer;
    fPrivateKeyOnly: TEccPrivateKey;
    function GetEccPrivateKey(checkZero: boolean): PEccPrivateKey;
  public
    constructor CreateFrom(aEcc: TEccCertificate);
    destructor Destroy; override;
    // ICryptCert methods
    function Generate(Usages: TCryptCertUsages; const Subjects: RawUtf8;
      const Authority: ICryptCert; ExpireDays, ValidDays: integer;
      Fields: PCryptCertFields): ICryptCert; override;
    function GetSerial: RawUtf8; override;
    function GetSubjectName: RawUtf8; override;
    function GetSubject(const Rdn: RawUtf8): RawUtf8; override;
    function GetSubjects: TRawUtf8DynArray; override;
    function GetIssuerName: RawUtf8; override;
    function GetIssuer(const Rdn: RawUtf8): RawUtf8; override;
    function GetIssuers: TRawUtf8DynArray; override;
    function GetSubjectKey: RawUtf8; override;
    function GetAuthorityKey: RawUtf8; override;
    function IsSelfSigned: boolean; override;
    function IsAuthorizedBy(const Authority: ICryptCert): boolean; override;
    function GetNotBefore: TDateTime; override;
    function GetNotAfter: TDateTime; override;
    function IsValidDate(date: TDateTime): boolean; override;
    function IsVoid: boolean; override;
    function GetUsage: TCryptCertUsages; override;
    function GetPeerInfo: RawUtf8; override;
    function GetSignatureInfo: RawUtf8; override;
    function Load(const Saved: RawByteString; Content: TCryptCertContent;
      const PrivatePassword: SpiUtf8): boolean; override;
    function Save(Content: TCryptCertContent; const PrivatePassword: SpiUtf8;
      Format: TCryptCertFormat): RawByteString; override;
    function HasPrivateSecret: boolean; override;
    function GetPublicKey: RawByteString; override;
    function GetPrivateKey: RawByteString; override;
    function SetPrivateKey(const saved: RawByteString): boolean; override;
    function IsEqual(const another: ICryptCert): boolean; override;
    function Sign(Data: pointer; Len: integer;
      Usage: TCryptCertUsage): RawByteString; override;
    procedure Sign(const Authority: ICryptCert); override;
    function Verify(Sign, Data: pointer; SignLen, DataLen: integer;
      IgnoreError: TCryptCertValidities; TimeUtc: TDateTime): TCryptCertValidity; override;
    function Verify(const Authority: ICryptCert;
      IgnoreError: TCryptCertValidities; TimeUtc: TDateTime): TCryptCertValidity; override;
    function Encrypt(const Message: RawByteString;
      const Cipher: RawUtf8): RawByteString; override;
    function Decrypt(const Message: RawByteString;
      const Cipher: RawUtf8): RawByteString; override;
    function SharedSecret(const pub: ICryptCert): RawByteString; override;
    function Handle: pointer; override;
    function PrivateKeyHandle: pointer; override; // nil or a PEccPrivateKey
    /// low-level access to internal TEccCertificate or TEccCertificateSecret
    property Ecc: TEccCertificate
      read fEcc;
  end;


{ TCryptCertAlgoInternal }

constructor TCryptCertAlgoInternal.Create(const name: RawUtf8);
begin
  fCaa := caaES256;
  inherited Create(name);
  if name = 'syn-es256-v1' then
    fMaxVersion := 1
  else
    fMaxVersion := 20;
end;

function TCryptCertAlgoInternal.New: ICryptCert;
var
  cert: TCryptCertInternal;
begin
  cert := TCryptCertInternal.Create(self);
  cert.fMaxVersion := fMaxVersion;
  result := cert;
end;

function TCryptCertAlgoInternal.FromHandle(Handle: pointer): ICryptCert;
begin
  if (Handle <> nil) and
     TObject(Handle).InheritsFrom(TEccCertificate) then
    result := TCryptCertInternal.CreateFrom(Handle)
  else
    result := nil;
end;


{ TCryptCertInternal }

constructor TCryptCertInternal.CreateFrom(aEcc: TEccCertificate);
begin
  fEcc := aEcc;
  fEccByRef := true;
  fMaxVersion := (CryptCertSyn as TCryptCertAlgoInternal).fMaxVersion;
  Create(CryptCertSyn);
end;

destructor TCryptCertInternal.Destroy;
begin
  if not fEccByRef then
    fEcc.Free;
  FillZero(fPrivateKeyOnly);
  inherited Destroy;
end;

function TCryptCertInternal.Generate(Usages: TCryptCertUsages;
  const Subjects: RawUtf8; const Authority: ICryptCert;
  ExpireDays, ValidDays: integer; Fields: PCryptCertFields): ICryptCert;
var
  sub: RawUtf8;
  start: TDateTime;
  a: TCryptCert;
  auth: TEccCertificateSecret;
begin
  // note: only Fields^.CommonName is supported if no Subjects is set (yet)
  if fEcc <> nil then
    RaiseErrorGenerate('duplicated call');
  // ValidDays and ExpireDays are relative to the current time
  if ValidDays = 0 then
    start := 0
  else
  begin
    start := NowUtc + ValidDays;
    dec(ExpireDays, ValidDays); // TEccCertificateSecret.CreateNew is from start
  end;
  auth := nil;
  if Authority <> nil then
  begin
    a := Authority.Instance;
    if not a.InheritsFrom(TCryptCertInternal) then
      RaiseError('Generate: Authority is a % which is unsupported', [a]);
    if not a.HasPrivateSecret then
      RaiseError('Generate: Authority holds % which has no private key', [a]);
    auth := TCryptCertInternal(a).fEcc as TEccCertificateSecret;
  end;
  sub := Subjects;
  if (sub = '') and
     (Fields <> nil) then
    sub := Fields^.CommonName; // like TCryptCertOpenSsl.Generate()
  fEcc := TEccCertificateSecret.CreateNew(
    auth, '', ExpireDays, start, true, Usages, sub, fMaxVersion);
  result := self;
end;

function TCryptCertInternal.GetSerial: RawUtf8;
begin
  if fEcc <> nil then
    result := fEcc.Serial
  else
    result := '';
end;

function TCryptCertInternal.GetSubjectName: RawUtf8;
begin
  if fEcc <> nil then
    result := fEcc.Content.GetSubject
  else
    result := '';
end;

function TCryptCertInternal.GetSubject(const Rdn: RawUtf8): RawUtf8;
var
  h: THashAlgo;
begin
  result := '';
  if (Rdn <> '') and
     (fEcc <> nil) then
    if IsCN(Rdn) then // only 'CN' and hash are supported with syn-ecc
      result := GetFirstCsvItem(fEcc.Content.GetSubject)
    else if TextToHashAlgo(Rdn, h) then
      result := HashFull(h, @fEcc.Signed.Serial, SizeOf(fEcc.Signed.Serial));
end;

function TCryptCertInternal.GetSubjects: TRawUtf8DynArray;
begin
  result := nil;
  if fEcc <> nil then
    CsvToRawUtf8DynArray(pointer(fEcc.Content.GetSubject), result);
end;

function TCryptCertInternal.GetIssuerName: RawUtf8;
begin
  if fEcc <> nil then
    result := fEcc.AuthorityIssuer
  else
    result := '';
end;

function TCryptCertInternal.GetIssuer(const Rdn: RawUtf8): RawUtf8;
var
  h: THashAlgo;
begin
  result := '';
  if (Rdn <> '') and
     (fEcc <> nil) then
    if IsCN(Rdn) then
      result := GetFirstCsvItem(fEcc.AuthorityIssuer)
    else if TextToHashAlgo(Rdn, h) then
      result := HashFull(h,
        @fEcc.Signed.AuthoritySerial, SizeOf(fEcc.Signed.AuthoritySerial));
end;

function TCryptCertInternal.GetIssuers: TRawUtf8DynArray;
begin
  result := nil; // not suppported by our syn-ecc certificates
end;

function TCryptCertInternal.GetSubjectKey: RawUtf8;
begin
  if fEcc <> nil then
    result := fEcc.Serial
  else
    result := '';
end;

function TCryptCertInternal.GetAuthorityKey: RawUtf8;
begin
  if fEcc <> nil then
    result := fEcc.AuthoritySerial
  else
    result := '';
end;

function TCryptCertInternal.IsSelfSigned: boolean;
begin
  result := fEcc.IsSelfSigned;
end;

function TCryptCertInternal.IsAuthorizedBy(const Authority: ICryptCert): boolean;
var
  a: TCryptCertInternal;
begin
  result := false;
  if (fEcc = nil) or
     (Authority = nil) then
    exit;
  a := pointer(Authority.Instance);
  if (PClass(a)^ = PClass(self)^) and
     (a.fEcc <> nil) and
     // fast binary comparison with no memory allocation
     mormot.crypt.ecc256r1.IsEqual(fEcc.fContent.Head.Signed.AuthoritySerial,
                                   a.fEcc.fContent.Head.Signed.Serial) then
    result := true;
end;

function TCryptCertInternal.GetNotBefore: TDateTime;
begin
  if fEcc <> nil then
    result := EccToDateTime(fEcc.Content.Head.Signed.ValidityStart)
  else
    result := 0;
end;

function TCryptCertInternal.GetNotAfter: TDateTime;
begin
  if fEcc <> nil then
    result := EccToDateTime(fEcc.Content.Head.Signed.ValidityEnd)
  else
    result := 0;
end;

function TCryptCertInternal.IsValidDate(date: TDateTime): boolean;
begin
  result := (fEcc <> nil) and
            fEcc.Content.CheckDate(nil, date);
end;

function TCryptCertInternal.IsVoid: boolean;
begin
  result := not fEcc.CheckCRC;
end;

function TCryptCertInternal.GetUsage: TCryptCertUsages;
begin
  if fEcc <> nil then
    result := fEcc.GetUsage
  else
    result := [];
end;

function TCryptCertInternal.GetPeerInfo: RawUtf8;
begin
  if fEcc <> nil then
    JsonBufferReformat(pointer(fEcc.ToJson({withbase64=}false)), result)
  else
    result := '';
end;

function TCryptCertInternal.GetSignatureInfo: RawUtf8;
begin
  if fEcc = nil then
    result := ''
  else
    result := '128 syn-es256';
end;

function EccPrivateKeyEncrypt(const Input: TEccPrivateKey;
  const PrivatePassword: SpiUtf8): RawByteString;
var
  pk: RawByteString;
begin
  FastSetRawByteString(pk{%H-}, @Input, SizeOf(Input));
  if PrivatePassword = '' then
    result := pk
  else
  begin
    result := PrivateKeyEncrypt(pk, 'synecc', PrivatePassword, 31, 1000);
    FillZero(pk);
  end;
end;

function EccPrivateKeyDecrypt(const Input: RawByteString;
  const PrivatePassword: SpiUtf8): RawByteString;
begin
  result := PrivateKeyDecrypt(Input, 'synecc', PrivatePassword, 31, 1000);
end;

function TCryptCertInternal.GetEccPrivateKey(checkZero: boolean): PEccPrivateKey;
begin
  result := nil;
  if fEcc = nil then
    result := @fPrivateKeyOnly
  else if fEcc.InheritsFrom(TEccCertificateSecret) then
    result := @TEccCertificateSecret(fEcc).PrivateKey
  else
    exit;
  if checkZero and
     IsZero(result^) then
    result := nil;
end;

function TCryptCertInternal.Save(Content: TCryptCertContent;
  const PrivatePassword: SpiUtf8; Format: TCryptCertFormat): RawByteString;
var
  pk: PEccPrivateKey;
  der: RawByteString;
begin
  result := '';
  if not (Format in [ccfBinary, ccfPem]) then
    // hexa or base64 encoding of the ccfBinary output is handled by TCryptCert
    result := inherited Save(Content, PrivatePassword, Format)
  else
  // we implement ccfPem and ccfBinary here
  case Content of
    cccCertOnly:
      if fEcc <> nil then
      begin
        result := fEcc.SaveToBinary({publickeyonly=}true);
        if Format = ccfPem then
          result := DerToPem(result, pemSynopseCertificate);
      end;
    cccCertWithPrivateKey:
      if fEcc <> nil then
      begin
        if not fEcc.InheritsFrom(TEccCertificateSecret) then
          RaiseError('Save(cccCertWithPrivateKey) with no Private Key');
        result := TEccCertificateSecret(fEcc).SaveToSecureBinary(PrivatePassword);
        if Format = ccfPem then
        begin
          der := result;
          result := DerToPem(der, pemSynopsePrivateKeyAndCertificate);
          FillZero(der);
        end;
      end;
    cccPrivateKeyOnly:
      begin
        pk := GetEccPrivateKey({checkZero=}true);
        if pk = nil then
          RaiseError('Save(cccPrivateKeyOnly) with no Private Key');
        if (Format = ccfPem) and
           (PrivatePassword = '') then
        begin
          // -----BEGIN SYNECC PRIVATE KEY----- has a real DER encoding
          der := EccToDer(pk^);
          result := DerToPem(der, pemSynopseUnencryptedPrivateKey);
          FillZero(der);
        end
        else
        begin
          // other formats use our encrypted TEccPrivateKey binary
          result := EccPrivateKeyEncrypt(pk^, PrivatePassword);
         if Format = ccfPem then
         begin
           der := result;
           result := DerToPem(der, pemSynopseEccEncryptedPrivateKey);
           FillZero(der);
         end;
        end;
      end;
  end;
end;

function TCryptCertInternal.Load(const Saved: RawByteString;
  Content: TCryptCertContent; const PrivatePassword: SpiUtf8): boolean;
var
  bin: RawByteString;
  k: TPemKind;
begin
  try
    if content <> cccPrivateKeyOnly then
    begin
      EnsureCanWrite('Load');
      FreeAndNil(fEcc);
      FillZero(fPrivateKeyOnly);
    end;
    if IsPem(Saved) then
    begin
      bin := PemToDer(Saved, @k);
      if not (k in PEM_SYNECC) then
        bin := '';
    end
    else
      bin := Saved;
    result := false;
    if bin = '' then
      exit;
    case Content of
      cccCertOnly:
        begin
          fEcc := TEccCertificate.CreateVersion(fMaxVersion);
          result := fEcc.LoadFromBinary(bin); // plain public key only
        end;
      cccPrivateKeyOnly:
        begin
          // PEM/DER input encoded with our encrypted TEccPrivateKey binary
          bin := EccPrivateKeyDecrypt(bin, PrivatePassword);
          if fEcc <> nil then
            result := SetPrivateKey(bin)
          else if length(bin) = SizeOf(fPrivateKeyOnly) then
          begin
            fPrivateKeyOnly := PEccPrivateKey(bin)^;
            result := true;
          end;
          exit; // don't free the main fEcc instance below
        end;
      cccCertWithPrivateKey:
        begin
          fEcc := TEccCertificateSecret.CreateVersion(fMaxVersion);
          result := TEccCertificateSecret(fEcc). // encrypted and with private key
            LoadFromSecureBinary(bin, PrivatePassword);
        end;
    end;
    if not result then
      FreeAndNil(fEcc);
  finally
    FillZero(bin);
  end;
end;

function TCryptCertInternal.HasPrivateSecret: boolean;
begin
  result := GetEccPrivateKey({checkZero=}true) <> nil;
end;

function TCryptCertInternal.GetPublicKey: RawByteString;
begin
  if fEcc <> nil then
    FastSetRawByteString(result{%H-}, @fEcc.Content.Head.Signed.PublicKey,
      SizeOf(TEccPublicKey))
end;

function TCryptCertInternal.GetPrivateKey: RawByteString;
var
  pk: PEccPrivateKey;
begin
  pk := GetEccPrivateKey({checkZero=}true);
  if pk <> nil then
    FastSetRawByteString(result{%H-}, pk, SizeOf(pk^))
  else
    result := '';
end;

function TCryptCertInternal.SetPrivateKey(const saved: RawByteString): boolean;
var
  ecc: TEccPrivateKey;
  pk, dst: PEccPrivateKey;
begin
  dst := GetEccPrivateKey({checkZero=}false);
  if length(saved) = SizeOf(TEccPrivateKey) then
    pk := pointer(saved)
  else if DerToEcc(pointer(saved), length(saved), ecc) then
    pk := @ecc
  else
  begin
    if dst <> nil then
      FillZero(dst^);
    result := false;
    exit;
  end;
  if dst <> nil then
    dst^ := pk^
  else
    fEcc := TEccCertificateSecret.CreateFrom(fEcc, pk, {freefEcc=}true);
  FillZero(ecc);
  result := true;
end;

function TCryptCertInternal.IsEqual(const another: ICryptCert): boolean;
var
  a: TCryptCert;
begin
  result := false;
  // check same exact implementation class
  if another = nil then
    exit;
  a := another.Instance;
  if PClass(a)^ = PClass(self)^ then
    // compare all fields at once
    result := fEcc.IsEqual(TCryptCertInternal(a).fEcc);
end;

function TCryptCertInternal.Sign(Data: pointer; Len: integer;
  Usage: TCryptCertUsage): RawByteString;
begin
  if HasPrivateSecret then
    result := TEccCertificateSecret(fEcc).SignToBinary(Data, Len)
  else
    result := '';
end;

procedure TCryptCertInternal.Sign(const Authority: ICryptCert);
begin
  EnsureCanWrite('Sign');
  if (fEcc <> nil) and
     Authority.Instance.InheritsFrom(TCryptCertInternal) and
     Authority.HasPrivateSecret then
    TEccCertificateSecret(Authority.Handle).SignCertificate(fEcc)
  else
    RaiseError('Sign: invalid CA');
end;

function TCryptCertInternal.Verify(Sign, Data: pointer; SignLen, DataLen: integer;
  IgnoreError: TCryptCertValidities; TimeUtc: TDateTime): TCryptCertValidity;
var
  s: PEccSignatureCertifiedContent absolute Sign;
begin
  if IgnoreError <> [] then
    RaiseError('Verify: unsupported IgnoreError');
  if (fEcc = nil) or
     (SignLen <> SizeOf(s^)) or
     (DataLen <= 0) then
    result := cvBadParameter
  else
    result := TCryptCertValidity(
      fEcc.Verify(Sha256Digest(Data, DataLen), s^, TimeUtc));
end;

function TCryptCertInternal.Verify(const Authority: ICryptCert;
  IgnoreError: TCryptCertValidities; TimeUtc: TDateTime): TCryptCertValidity;
var
  auth: TEccCertificate;
begin
  if IgnoreError <> [] then
    RaiseError('Verify: unsupported IgnoreError');
  result := cvBadParameter;
  if fEcc = nil then
    exit;
  auth := nil;
  if Assigned(Authority) then
    if Authority.Instance.InheritsFrom(TCryptCertInternal) then
      auth := Authority.handle
    else
      exit;
  result := TCryptCertValidity(fEcc.VerifyCertificate(auth, TimeUtc));
end;

function TCryptCertInternal.Encrypt(const Message: RawByteString;
  const Cipher: RawUtf8): RawByteString;
begin
  if (fEcc <> nil) and
     (fEcc.Usage * [cuDataEncipherment, cuEncipherOnly] <> []) then
    result := EciesSeal(Cipher, fEcc.Content.Head.Signed.PublicKey, Message)
  else
    result := '';
end;

function TCryptCertInternal.Decrypt(const Message: RawByteString;
  const Cipher: RawUtf8): RawByteString;
var
  pk: PEccPrivateKey;
begin
  pk := GetEccPrivateKey({checkzero=}true);
  if (pk <> nil) and
     ((fEcc = nil) or
      (fEcc.Usage * [cuDataEncipherment, cuDecipherOnly] <> [])) then
    result := EciesOpen(Cipher, pk^, Message)
  else
    result := '';
end;

function TCryptCertInternal.SharedSecret(const pub: ICryptCert): RawByteString;
var
  pk: PEccPrivateKey;
  sec: TEccSecretKey;
begin
  pk := GetEccPrivateKey({checkzero=}true);
  if (pk <> nil) and
     Assigned(pub) and
     pub.Instance.InheritsFrom(TCryptCertInternal) and
     (pub.Handle <> nil) and
     (cuKeyAgreement in TEccCertificate(pub.Handle).Usage) and
     ((fEcc = nil) or
      (cuKeyAgreement in fEcc.Usage)) and
     Ecc256r1SharedSecret(
        TEccCertificate(pub.Handle).Content.Head.Signed.PublicKey, pk^, sec) then
       FastSetRawByteString(result{%H-}, @sec, SizeOf(sec))
     else
       result := '';
  FillZero(sec);
end;

function TCryptCertInternal.Handle: pointer;
begin
  result := fEcc; // TEccCertificate or TEccCertificateSecret
end;

function TCryptCertInternal.PrivateKeyHandle: pointer;
begin
  if (fEcc = nil) or
     not fEcc.InheritsFrom(TEccCertificateSecret) then
    result := nil
  else
    result := @TEccCertificateSecret(fEcc).fPrivateKey;
end;

type
  /// 'syn-store' / 'syn-store-nocache' ICryptStore algorithm
  TCryptStoreAlgoInternal = class(TCryptStoreAlgo)
  public
    function New: ICryptStore; override; // = TCryptStoreInternal.Create(self)
  end;

  /// class implementing ICryptStore using our ECC Public Key Cryptography
  TCryptStoreInternal = class(TCryptStore)
  protected
    fEcc: TEccCertificateChain;
  public
    constructor Create(algo: TCryptAlgo); override;
    destructor Destroy; override;
    // ICryptStore methods
    procedure Clear; override;
    function Load(const Saved: RawByteString): boolean; override;
    function Save: RawByteString; override;
    function GetBySerial(const Serial: RawUtf8): ICryptCert; override;
    function GetBySubjectKey(const Key: RawUtf8): ICryptCert; override;
    function IsRevoked(const cert: ICryptCert): TCryptCertRevocationReason; override;
    function Add(const cert: ICryptCert): boolean; override;
    function AddFromBuffer(const Content: RawByteString): TRawUtf8DynArray; override;
    function Revoke(const Cert: ICryptCert; Reason: TCryptCertRevocationReason;
       RevocationDate: TDateTime): boolean; override;
    function IsValid(const cert: ICryptCert;
      date: TDateTime): TCryptCertValidity; override;
    function Verify(const Signature: RawByteString; Data: pointer; Len: integer;
      IgnoreError: TCryptCertValidities; TimeUtc: TDateTime): TCryptCertValidity; override;
    function Count: integer; override;
    function CrlCount: integer; override;
    function DefaultCertAlgo: TCryptCertAlgo; override;
  end;

  /// maintain a cache of ICryptCert instances, from their DER/binary
  TCryptCertCacheInternal = class(TCryptCertCache)
  protected
    // overidden to call EccDerLoad() and return a TCryptCertInternal
    function InternalLoad(const Cert: RawByteString): ICryptCert; override;
  end;


{ TCryptStoreInternal }

constructor TCryptStoreInternal.Create(algo: TCryptAlgo);
begin
  inherited Create(algo);
  fEcc := TEccCertificateChain.Create;
  fCache := TCryptCertCacheInternal.Create;
  TCryptCertCacheInternal(fCache).SetCryptCertClass(TCryptCertInternal);
end;

destructor TCryptStoreInternal.Destroy;
begin
  inherited Destroy;
  fEcc.Free;
end;

procedure TCryptStoreInternal.Clear;
begin
  fEcc.Clear;
end;

function TCryptStoreInternal.Load(const Saved: RawByteString): boolean;
begin
  result := fEcc.LoadFromBinary(Saved);
end;

function TCryptStoreInternal.Save: RawByteString;
begin
  result := fEcc.SaveToBinary;
end;

function TCryptStoreInternal.GetBySerial(const Serial: RawUtf8): ICryptCert;
var
  c: TEccCertificate;
begin
  c := fEcc.GetBySerial(Serial);
  if c = nil then
    result := nil
  else
    result := TCryptCertInternal.CreateFrom(c);
end;

function TCryptStoreInternal.GetBySubjectKey(const Key: RawUtf8): ICryptCert;
begin
  result := GetBySerial(Key); // no SKID with syn-ecc
end;

function TCryptStoreInternal.IsRevoked(
  const cert: ICryptCert): TCryptCertRevocationReason;
begin
  if Assigned(cert) then
    result := fEcc.IsRevoked(cert.GetSerial) // global CRL with no issuer link
  else
    result := crrNotRevoked;
end;

function TCryptStoreInternal.Add(const cert: ICryptCert): boolean;
var
  a: TCryptCertInternal;
begin
  result := false;
  if cert <> nil  then
  begin
    a := cert.Instance as TCryptCertInternal;
    if a.fEcc.IsSelfSigned then
      result := fEcc.AddSelfSigned(a.fEcc) >= 0 // specific method
    else
      result := fEcc.Add(a.fEcc) >= 0;
    a.fEccByRef := result; // will be owned by fEcc chain
  end;
end;

function TCryptStoreInternal.AddFromBuffer(const Content: RawByteString): TRawUtf8DynArray;
begin
  result := fEcc.AddFromBuffer(Content);
end;

function TCryptStoreInternal.Revoke(const Cert: ICryptCert;
  Reason: TCryptCertRevocationReason; RevocationDate: TDateTime): boolean;
begin
  result := (Cert <> nil) and
            fEcc.Revoke(Cert.GetSerial, RevocationDate, Reason);
end;

function TCryptStoreInternal.IsValid(const cert: ICryptCert;
  date: TDateTime): TCryptCertValidity;
begin
  if cert = nil then
    result := cvBadParameter
  else if cert.Instance.InheritsFrom(TCryptCertInternal) then
    result := TCryptCertValidity(fEcc.IsValid(
                TCryptCertInternal(cert.Instance).fEcc, date))
  else
    result := cvUnknownAuthority;
end;

function TCryptStoreInternal.Verify(const Signature: RawByteString;
  Data: pointer; Len: integer; IgnoreError: TCryptCertValidities;
  TimeUtc: TDateTime): TCryptCertValidity;
var
  s: PEccSignatureCertifiedContent absolute Signature;
begin
  if IgnoreError <> [] then
    ECryptCert.RaiseUtf8('%.Verify: unsupported IgnoreError', [self]);
  if length(Signature) <> SizeOf(s^) then
    result := cvBadParameter
  else
    result := TCryptCertValidity(fEcc.IsSigned(s^, Data, Len));
end;

function TCryptStoreInternal.Count: integer;
begin
  result := length(fEcc.fItems);
end;

function TCryptStoreInternal.CrlCount: integer;
begin
  result := length(fEcc.fCrl);
end;

function TCryptStoreInternal.DefaultCertAlgo: TCryptCertAlgo;
begin
  result := CryptCertSyn;
end;


{ TCryptStoreAlgoInternal }

function TCryptStoreAlgoInternal.New: ICryptStore;
var
  a: TCryptStoreInternal;
begin
  a := TCryptStoreInternal.Create(self);
  a.fEcc.IsValidCached := AlgoName <> 'syn-store-nocache';
  result := a;
end;


{ TCryptCertCacheInternal }

function TCryptCertCacheInternal.InternalLoad(const Cert: RawByteString): ICryptCert;
begin
  result := SynEccLoad(Cert);
end;


function SynEccLoad(const Cert: RawByteString): ICryptCert;
begin
  result := CryptCertSyn.New; // we have a single algorithm and class
  if not result.Load(Cert) then
    result := nil;
end;


procedure InitializeUnit;
var
  a: TCryptCertUsages;
begin
  {$ifndef HASDYNARRAYTYPE}
  Rtti.RegisterObjArray(TypeInfo(TEccCertificateObjArray), TEccCertificate);
  {$endif HASDYNARRAYTYPE}
  // binary headers should be consistent on all platforms/compilers
  assert(SizeOf(TEciesHeader) = 228);
  assert(SizeOf(TEcdheFrameClient) = 290);
  assert(SizeOf(TEcdheFrameServer) = 306);
  a := CU_ALL;
  assert(word(a) = ECCV1_USAGE_ALL);
  assert(ord(High(TCryptCertValidity)) = ord(High(TEccValidity)));
  assert(ord(cvRevoked) = ord(ecvRevoked));
  // register this unit methods to our high-level cryptographic catalog
  CryptAsym[caaES256] := TCryptAsymInternal.Implements([
    'ES256', 'secp256r1', 'NISTP-256', 'prime256v1']);
  CryptPublicKey[ckaEcc256]  := TCryptPublicKeyEcc;
  CryptPrivateKey[ckaEcc256] := TCryptPrivateKeyEcc;
  CryptCertSyn := TCryptCertAlgoInternal.Implements([
    'syn-es256-v1', 'syn-es256']);
  TCryptStoreAlgoInternal.Implements('syn-store,syn-store-nocache');
  CryptStoreSyn := StoreAlgo('syn-store');
  CryptStoreSynNoCache := StoreAlgo('syn-store-nocache');
end;



initialization
  InitializeUnit;

end.


