unit ECCProcess;

(*
    This file is part of Synopse framework.

    Synopse framework. Copyright (C) 2022 Arnaud Bouchez
      Synopse Informatique - https://synopse.info

  *** BEGIN LICENSE BLOCK *****
  Version: MPL 1.1/GPL 2.0/LGPL 2.1

  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL

  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the License.

  The Original Code is Synopse framework.

  The Initial Developer of the Original Code is Arnaud Bouchez.

  Portions created by the Initial Developer are Copyright (C) 2022
  the Initial Developer. All Rights Reserved.

  Contributor(s):
   - Nicolas Marchand (MC)

  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
  use your version of this file under the terms of the MPL, indicate your
  decision by deleting the provisions above and replace them with the notice
  and other provisions required by the GPL or the LGPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the MPL, the GPL or the LGPL.

  ***** END LICENSE BLOCK *****

*)

interface

{$I Synopse.inc}

uses
  SysUtils,
  Classes,
  SynCommons,
  SynTable,
  SynEcc,
  SynCrypto;

/// end-user command to create a new private/public key file
// - as used in the ECC.dpr command-line sample project
function ECCCommandNew(const AuthPrivKey: TFileName;
  const AuthPassword: RawUTF8; AuthPasswordRounds: integer;
  const Issuer: RawUTF8; StartDate: TDateTime; ExpirationDays: integer;
  const SavePassword: RawUTF8; SavePassordRounds, SplitFiles: integer): TFileName;

/// end-user command to create a renew a .private file password
// - as used in the ECC.dpr command-line sample project
function ECCCommandRekey(const AuthPrivKey: TFileName;
  const AuthPassword: RawUTF8; AuthPasswordRounds: integer;
  const SavePassword: RawUTF8; SavePassordRounds: integer): TFileName;

/// end-user command to sign a file using a private key file
// - as used in the ECC.dpr command-line sample project
function ECCCommandSignFile(const FileToSign, AuthPrivKey: TFileName;
  const AuthPassword: RawUTF8; AuthPasswordRounds: integer;
  const MetaNameValuePairs: array of const): TFileName;

/// end-user command to verify a file signature
// - as used in the ECC.dpr command-line sample project
function ECCCommandVerifyFile(const FileToVerify, AuthPubKey: TFileName;
  const AuthBase64: RawUTF8): TECCValidity;

/// end-user command to create a .inc pascal source file from a private key file
// - ready to be included within the executable binary as private secret
// - as used in the ECC.dpr command-line sample project
function ECCCommandSourceFile(const AuthPrivKey: TFileName;
  const AuthPassword: RawUTF8; AuthPasswordRounds: integer;
  const ConstName, Comment, PassWord: RawUTF8): TFileName;

/// end-user command to create a .json base-64 text array from a set of public key files
// - ready to be included e.g. as settings of any server
// - ECCCommandChainCertificates(['*']) will create a 'chain.ca' of all
// public key files in the current folder
// - as used in the ECC.dpr command-line sample project
function ECCCommandChainCertificates(const CertFiles: array of RawUTF8): TFileName;

/// end-user command to display the json information from a .private file
// - as used in the ECC.dpr command-line sample project
function ECCCommandInfoPrivFile(const AuthPrivKey: TFileName;
  const AuthPassword: RawUTF8; AuthPasswordRounds: integer): RawUTF8;

/// end-user command to encrypt a file with the .synecc format
// - as used in the ECC.dpr command-line sample project
procedure ECCCommandCryptFile(const FileToCrypt, DestFile, AuthPubKey: TFileName;
  const AuthBase64, AuthSerial, Password: RawUTF8; PasswordRounds: integer;
  Algo: TECIESAlgo=ecaUnknown);

/// end-user command to decrypt a .synecc file
// - as used in the ECC.dpr command-line sample project
// - if AuthPrivKey is not set, it will search for the stored TECCCertificate.Serial
function ECCCommandDecryptFile(const FileToDecrypt, DestFile, AuthPrivKey: TFileName;
  const AuthPassword: RawUTF8; AuthPasswordRounds: integer;
  const DecryptPassword: RawUTF8; DecryptPasswordRounds: integer;
  Signature: PECCSignatureCertifiedContent; MetaData: PRawJSON): TECCDecrypt;

/// end-user command to verify a .synecc file signature, after decryption
// - as used in the ECC.dpr command-line sample project
// - the supplied signature can be retrieved from ECCCommandDecryptFile()
function ECCCommandVerifyDecryptedFile(const FileToVerify: TFileName;
  const Signature: TECCSignatureCertifiedContent): TECCValidity;

/// end-user command to initialize local cheat.private/.public files
// - as used in the ECC.dpr command-line sample project
// - those files will let new/rekey commands create a .cheat file, encrypted
// using the master cheat.public key, so that the password of the generated
// .private key file could be retrieved using the cheat.private key and its
// password/round, via ECCCommandCheat (ECC cheat)
// - it may be convenient to remember a single password instead of several,
// and security will be enhanced by the fact that cheat.private stays hidden
function ECCCommandCheatInit(const Issuer, CheatPassword: RawUTF8;
  CheatRounds: integer): TFileName;

/// end-user command to display a .private password as stored in its .cheat file
// - as used in the ECC.dpr command-line sample project
// - using the master password/rounds of the local cheat.private key, as
// generated by ECCCommandCheatInit (ECC cheatinit)
function ECCCommandCheat(const PrivateFile: TFileName; const CheatPassword: RawUTF8;
  CheatRounds: integer; out authpass: RawUTF8; out authround: integer): RawUTF8;

/// end-user command to encrypt a file with the symetric .synaead format
// - will use symetric encryption via AES-256-CFB/PKCS7 over PBKDF2_HMAC_SHA256
// - as used in the ECC.dpr command-line sample project
procedure AEADCommandCryptFile(const FileToCrypt, DestFile: TFileName;
  const Password, PasswordSalt: RawUTF8; PasswordRounds: integer);

/// end-user command to decrypt a symetric .synaead file
// - will use symetric encryption via AES-256-CFB/PKCS7 over PBKDF2_HMAC_SHA256
// - as used in the ECC.dpr command-line sample project
procedure AEADCommandDecryptFile(const FileToDecrypt, DestFile: TFileName;
  const Password, PasswordSalt: RawUTF8; PasswordRounds: integer);

type
  /// the actions implemented by ECCCommand()
  // - as used in the ECC.dpr command-line sample project
  // - retrieved from the command line as first parameter
  TECCCommand = (
    ecHelp, ecNew, ecRekey, ecSign, ecVerify, ecSource, ecInfoPriv,
    ecChain, ecChainAll,
    ecCrypt, ecDecrypt, ecInfoCrypt,
    ecAeadCrypt, ecAeadDecrypt,
    ecCheatInit, ecCheat);
  /// the result code returned by ECCCommand()
  // - as used in the ECC.dpr command-line sample project
  TECCCommandError = (
    eccSuccess, eccUnknownCommand, eccValidationError, eccError, eccException);


/// execute the encryption process corresponding to the command line options
// - as used in the ECC.dpr sample project
// - returns the ExitCode expected value (0=eccSuccess)
function ECCCommand(cmd: TECCCommand; const sw: ICommandLine): TECCCommandError;

const
  CHEAT_FILEEXT = '.cheat';
  CHEAT_FILEMASTER = 'cheat';
  CHEAT_ROUNDS = 100000;
  CHEAT_SPLIT = 100;

  AEAD_FILEEXT = '.synaead';
  DEFAULT_AEADROUNDS = 60000;


implementation

procedure CreateCheatFile(secret: TECCCertificateSecret;
  const SavePassword: RawUTF8; SavePasswordRounds: integer);
var json,bin: RawByteString;
    master: TECCCertificate;
    fn: TFileName;
begin
  master := TECCCertificate.Create;
  try
    if master.FromFile(CHEAT_FILEMASTER) then
      try
        if SavePasswordRounds=DEFAULT_ECCROUNDS then
          json := SavePassword else
          json := JSONEncode(['pass',SavePassword,'rounds',SavePasswordRounds]);
        bin := TAESPRNG.Main.AFSplit(pointer(json)^,length(json),CHEAT_SPLIT);
        fn := UTF8ToString(secret.Serial)+CHEAT_FILEEXT;
        FileFromString(master.Encrypt(bin),fn);
      finally
        FillZero(json);
        FillZero(bin);
      end;
  finally
    master.Free;
  end;
end;

function ECCCommandNew(const AuthPrivKey: TFileName;
  const AuthPassword: RawUTF8; AuthPasswordRounds: integer;
  const Issuer: RawUTF8; StartDate: TDateTime; ExpirationDays: integer;
  const SavePassword: RawUTF8; SavePassordRounds, SplitFiles: integer): TFileName;
var auth,new: TECCCertificateSecret;
begin
  if AuthPrivKey='' then
    auth := nil else
    auth := TECCCertificateSecret.CreateFromSecureFile(AuthPrivKey,AuthPassword,AuthPasswordRounds);
  try
    // generate pair
    new := TECCCertificateSecret.CreateNew(auth,Issuer,ExpirationDays,StartDate);
    try
      // save private key as .private password-protected binary file
      new.SaveToSecureFiles(SavePassword,'.',SplitFiles,64,SavePassordRounds);
      CreateCheatFile(new,SavePassword,SavePassordRounds);
      // save public key as .public JSON file
      result := ChangeFileExt(new.SaveToSecureFileName,ECCCERTIFICATEPUBLIC_FILEEXT);
      new.ToFile(result);
    finally
      new.Free;
    end;
  finally
    auth.Free;
  end;
end;

function ECCCommandRekey(const AuthPrivKey: TFileName;
  const AuthPassword: RawUTF8; AuthPasswordRounds: integer;
  const SavePassword: RawUTF8; SavePassordRounds: integer): TFileName;
var auth: TECCCertificateSecret;
begin
  auth := TECCCertificateSecret.CreateFromSecureFile(AuthPrivKey,AuthPassword,AuthPasswordRounds);
  try
    auth.SaveToSecureFile(SavePassword,'.',64,SavePassordRounds);
    CreateCheatFile(auth,SavePassword,SavePassordRounds);
    result := auth.SaveToSecureFileName;
  finally
    auth.Free;
  end;
end;

function ECCCommandSignFile(const FileToSign, AuthPrivKey: TFileName;
  const AuthPassword: RawUTF8; AuthPasswordRounds: integer;
  const MetaNameValuePairs: array of const): TFileName;
var auth: TECCCertificateSecret;
begin
  auth := TECCCertificateSecret.CreateFromSecureFile(AuthPrivKey,AuthPassword,AuthPasswordRounds);
  try
    result := auth.SignFile(FileToSign,MetaNameValuePairs);
  finally
    auth.Free;
  end;
end;

function ECCCommandSourceFile(const AuthPrivKey: TFileName;
  const AuthPassword: RawUTF8; AuthPasswordRounds: integer;
  const ConstName, Comment, PassWord: RawUTF8): TFileName;
var auth: TECCCertificateSecret;
begin
  auth := TECCCertificateSecret.CreateFromSecureFile(AuthPrivKey,AuthPassword,AuthPasswordRounds);
  try
    result := AuthPrivKey+'.inc';
    FileFromString(auth.SaveToSource(ConstName,Comment,Password),result);
  finally
    auth.Free;
  end;
end;

function ECCCommandVerifyFile(const FileToVerify, AuthPubKey: TFileName;
  const AuthBase64: RawUTF8): TECCValidity;
var content: RawByteString;
    auth: TECCCertificate;
    cert: TECCSignatureCertified;
begin
  content := StringFromFile(FileToVerify);
  if content='' then
    raise EECCException.CreateUTF8('File not found: %',[FileToVerify]);
  cert := TECCSignatureCertified.CreateFromFile(FileToVerify);
  try
    if not cert.Check then begin
      result := ecvInvalidSignature;
      exit;
    end;
    auth := TECCCertificate.Create;
    try
      if auth.FromAuth(AuthPubKey,AuthBase64,cert.AuthoritySerial) then
        result := cert.Verify(auth,pointer(content),length(content)) else
        result := ecvUnknownAuthority;
    finally
      auth.Free;
    end;
  finally
    cert.Free;
  end;
end;

function ECCCommandVerifyDecryptedFile(const FileToVerify: TFileName;
  const Signature: TECCSignatureCertifiedContent): TECCValidity;
var content: RawByteString;
    auth: TECCCertificate;
    cert: TECCSignatureCertified;
begin
  content := StringFromFile(FileToVerify);
  if content='' then
    raise EECCException.CreateUTF8('File not found: %',[FileToVerify]);
  cert := TECCSignatureCertified.CreateFrom(Signature);
  try
    auth := TECCCertificate.Create;
    try
      result := ecvUnknownAuthority;
      if auth.FromAuth('','',cert.AuthoritySerial) then
        result := cert.Verify(auth,pointer(content),length(content));
    finally
      auth.Free;
    end;
  finally
    cert.Free;
  end;
end;

procedure ECCCommandCryptFile(const FileToCrypt, DestFile, AuthPubKey: TFileName;
  const AuthBase64, AuthSerial, Password: RawUTF8; PasswordRounds: integer;
  Algo: TECIESAlgo);
var content: RawByteString;
    auth: TECCCertificate;
begin
  content := StringFromFile(FileToCrypt);
  if content='' then
    raise EECCException.CreateUTF8('File not found: %',[FileToCrypt]);
  auth := TECCCertificate.Create;
  try
    if not auth.FromAuth(AuthPubKey,AuthBase64,AuthSerial) then
      raise EECCException.Create('No public key');
    if not auth.EncryptFile(FileToCrypt,DestFile,Password,PasswordRounds,Algo,true) then
      raise EECCException.CreateUTF8('EncryptFile failed for %',[FileToCrypt]);
  finally
    auth.Free;
    FillZero(content);
  end;
end;

function ECCCommandDecryptFile(const FileToDecrypt, DestFile, AuthPrivKey: TFileName;
  const AuthPassword: RawUTF8; AuthPasswordRounds: integer;
  const DecryptPassword: RawUTF8; DecryptPasswordRounds: integer;
  Signature: PECCSignatureCertifiedContent; MetaData: PRawJSON): TECCDecrypt;
var auth: TECCCertificateSecret;
    head: TECIESHeader;
    priv: TFileName;
begin
  auth := TECCCertificateSecret.Create;
  try
    result := ecdNoPrivateKey;
    if FileExists(AuthPrivKey) then
      priv := AuthPrivKey else begin
      if not ECIESHeaderFile(FileToDecrypt,head) then
        exit;
      priv := UTF8ToString(ECCText(head.recid));
      if not ECCKeyFileFind(priv,true) then
        exit; // not found local .private from header 
    end;
    if not auth.LoadFromSecureFile(priv,AuthPassword,AuthPasswordRounds) then
      exit;
    result := auth.DecryptFile(FileToDecrypt,DestFile,
      DecryptPassword,DecryptPasswordRounds,Signature,MetaData);
  finally
    auth.Free;
  end;
end;

function ECCCommandChainCertificates(const CertFiles: array of RawUTF8): TFileName;
var n,i: integer;
    files: TFileNameDynArray;
begin
  result := '';
  n := length(CertFiles);
  if n=0 then
    exit;
  if (n=1) and (CertFiles[0]='*') then begin
    files := FindFilesDynArrayToFileNames(
      FindFiles('.','*'+ECCCERTIFICATEPUBLIC_FILEEXT));
    result := 'chain'+ECCCERTIFICATES_FILEEXT;
  end else begin
    SetLength(files,n);
    for i := 0 to n-1 do begin
      files[i] := UTF8ToString(CertFiles[i]);
      if not ECCKeyFileFind(files[i],false) then
        exit;
     end;
    result := format('chain%d'+ECCCERTIFICATES_FILEEXT,[GetTickCount64]);
  end;
  with TECCCertificateChain.CreateFromFiles(files) do
  try
    if ValidateItems<>nil then begin
      result := '';
      raise EECCException.Create('Some of the certificates are invalid');
    end;
    SaveToFile(result);
  finally
    Free;
  end;
end;

function ECCCommandInfoPrivFile(const AuthPrivKey: TFileName;
  const AuthPassword: RawUTF8; AuthPasswordRounds: integer): RawUTF8;
var auth: TECCCertificateSecret;
begin
  auth := TECCCertificateSecret.CreateFromSecureFile(AuthPrivKey,AuthPassword,AuthPasswordRounds);
  try
    result := JSONReformat(VariantSaveJSON(auth.ToVariant))
  finally
    auth.Free;
  end;
end;

function ECCCommandCheatInit(const Issuer, CheatPassword: RawUTF8;
  CheatRounds: integer): TFileName;
var new: TECCCertificateSecret;
    priv: RawByteString;
begin
  if FileExists(CHEAT_FILEMASTER+ECCCERTIFICATEPUBLIC_FILEEXT) or
     FileExists(CHEAT_FILEMASTER+ECCCERTIFICATESECRET_FILEEXT) then
    raise EECCException.Create(CHEAT_FILEMASTER+' file already exist');
  // generate pair
  new := TECCCertificateSecret.CreateNew(nil,Issuer);
  try
    // save private key as cheat.private password-protected binary file
    priv := new.SaveToSecureBinary(CheatPassword,128,CheatRounds);
    FileFromString(priv,CHEAT_FILEMASTER+ECCCERTIFICATESECRET_FILEEXT);
    // save public key as mastercheat.public JSON file
    result := CHEAT_FILEMASTER+ECCCERTIFICATEPUBLIC_FILEEXT;
    JSONReformatToFile(VariantSaveJSON(new.ToVariant),result);
  finally
    new.Free;
    FillZero(priv);
  end;
end;

function ECCCommandCheat(const PrivateFile: TFileName; const CheatPassword: RawUTF8;
  CheatRounds: integer; out authpass: RawUTF8; out authround: integer): RawUTF8;
var bin,split,json: RawByteString;
    master: TECCCertificateSecret;
    doc: TDocVariantData;
    fn: TFileName;
    res: TECCDecrypt;
begin
  fn := ChangeFileExt(PrivateFile,CHEAT_FILEEXT);
  bin := StringFromFile(fn);
  if bin='' then
    raise EECCException.CreateUTF8('Unknown file %',[fn]);
  master := TECCCertificateSecret.CreateFromSecureFile(
    CHEAT_FILEMASTER,CheatPassword,CheatRounds);
  try
    res := master.Decrypt(bin,split);
    if res<>ecdDecrypted then
      raise EECCException.CreateUTF8('% on %',[ToText(res)^,fn]);
    json := TAESPRNG.AFUnsplit(split,CHEAT_SPLIT);
    if json='' then
      raise EECCException.CreateUTF8('Incorrect file %',[fn]);
    if not doc.InitJSON(json) then
      doc.InitObject(['pass',json,'rounds',DEFAULT_ECCROUNDS],JSON_OPTIONS_FAST);
    authpass := doc.U['pass'];
    authround := doc.I['rounds'];
    result := doc.ToJSON('','',jsonHumanReadable);
  finally
    master.Free;
    FillZero(json);
  end;
end;

procedure AEADProcess(Encrypt: boolean; var Source: RawByteString;
  const DestFileName: TFileName; const Password, PasswordSalt: RawUTF8; PasswordRounds: integer);
var
  aeskey: THash256;
  dst: RawByteString;
begin
  try
    PBKDF2_HMAC_SHA256(Password,PasswordSalt,PasswordRounds,aeskey,'salt');
    try
      dst := TAESCFBCRC.MACEncrypt(Source,aeskey,Encrypt);
      try
        if dst='' then
          raise EECCException.CreateUTF8('MACEncrypt failed for %',[DestFileName]);
        if not FileFromString(dst,DestFileName) then
          raise EECCException.CreateUTF8('FileFromString failed for %',[DestFileName]);
      finally
        FillZero(dst);
      end;
    finally
      FillZero(aeskey);
    end;
  finally
    FillZero(Source);
  end;
end;

procedure AEADCommandCryptFile(const FileToCrypt, DestFile: TFileName;
  const Password, PasswordSalt: RawUTF8; PasswordRounds: integer);
var plain: RawByteString;
    dest: TFileName;
begin
  plain := StringFromFile(FileToCrypt);
  if plain='' then
    raise EECCException.CreateUTF8('File not found: %',[FileToCrypt]);
  if DestFile='' then
    dest := FileToCrypt+AEAD_FILEEXT else
    dest := DestFile;
  AEADProcess({encrypt=}true,plain,dest,Password,PasswordSalt,PasswordRounds);
end;


procedure AEADCommandDecryptFile(const FileToDecrypt, DestFile: TFileName;
  const Password, PasswordSalt: RawUTF8; PasswordRounds: integer);
var encrypted: RawByteString;
    dest: TFileName;
begin
  encrypted := StringFromFile(FileToDecrypt);
  if encrypted='' then
    raise EECCException.CreateUTF8('File not found: %',[FileToDeCrypt]);
  if DestFile='' then
    dest := GetFileNameWithoutExt(FileToDecrypt) else
    dest := DestFile;
  AEADProcess({encrypt=}false,encrypted,dest,Password,PasswordSalt,PasswordRounds);
end;



function ECCCommand(cmd: TECCCommand; const sw: ICommandLine): TECCCommandError;

  procedure WriteVerif(verif: TECCValidity; const filename: TFileName; const sw: ICommandLine);
  var res: string;
  begin
    res := SysUtils.LowerCase(GetCaptionFromEnum(TypeInfo(TECCValidity),ord(verif)));
    if verif in ECC_VALIDSIGN then
      sw.Text(' % file verified as %.',[filename,res],ccLightGreen) else begin
      sw.Text(' % file verification failure: % (%).',[filename,res,ord(verif)],ccLightRed);
      result := eccValidationError;
    end;
  end;
  procedure WritePassword(const privfile: TFileName;
    const pass: RawUTF8; rounds: integer);
  var a: TECDHEAuth;
      privkey: RawUTF8;
  begin
    if privfile='' then
      exit;
    sw.Text('Corresponding TSynPersistentWithPassword.ComputePassword:',[]);
    sw.Text(' encryption %',[TSynPersistentWithPassword.ComputePassword(pass)],ccLightBlue);
    privkey := StringToUTF8(copy(GetFileNameWithoutExt(privfile),1,8));
    for a := low(a) to high(a) do
      sw.Text(' % %',[ToText(a)^,TECDHEProtocol.FromKeyCompute(
        privkey,pass,rounds,'',a)],ccLightBlue);
    sw.Text('',[]);
  end;

var issuer, authpass, savepass, constname, comment, json: RawUTF8;
    meta: RawJSON;
    start: TDateTime;
    authrounds, days, saverounds, splitfiles: integer;
    msg: string;
    origfile,auth,newfile,jsonfile: TFileName;
    algo: TECIESAlgo;
    decrypt: TECCDecrypt;
    decryptsign: TECCSignatureCertifiedContent;
begin
  result := eccSuccess;
  if sw=nil then
    raise EECCException.Create('ECCCommand(nil)');
  try
  try
    case cmd of
    ecNew: begin
      repeat
        auth := sw.AsString('Auth','',
          'Enter the first chars of the .private file name of the signing authority.'#13#10+
          'Will create a self-signed certificate if left void.');
      until (auth='') or ECCKeyFileFind(auth,true) or sw.NoPrompt;
      if auth<>'' then begin
        sw.Text('Will use: %'#13#10,[ExtractFileName(auth)]);
        authpass := sw.AsUTF8('AuthPass','',
          'Enter the PassPhrase of this .private file.');
        authrounds := sw.AsInt('AuthRounds',DEFAULT_ECCROUNDS,
          'Enter the PassPhrase iteration rounds of this .private file.');
      end else
        authrounds := 0;
      issuer := sw.AsUTF8('Issuer',ExeVersion.User,
        'Enter Issuer identifier text.'#13#10'Will be truncated to 15-20 ascii-7 chars.');
      start := sw.AsDate('Start',NowUTC,
        'Enter the YYYY-MM-DD start date of its validity.'#13#10+
        '0 will create a never-expiring certificate.');
      if start<=0 then
        days := 0 else
        days := sw.AsInt('Days',365,'Enter the number of days of its validity.');
      repeat
        savepass := sw.AsUTF8('NewPass',TAESPRNG.Main.RandomPassword(12),
          'Enter a private PassPhrase for the new key (at least 8 chars long).'#13#10+
          'Save this in a safe place: if you forget it, the key will be useless!');
      until (length(savepass)>=8) or sw.NoPrompt;
      repeat
        saverounds := sw.AsInt('NewRounds',DEFAULT_ECCROUNDS,
          'Enter the PassPhrase iteration rounds for the new key (at least 1000).'#13#10+
          'The higher, the safer, but will demand more computation time.');
      until (saverounds>=1000) or sw.NoPrompt;
      splitfiles := 1;
      {repeat
        splitfiles := sw.AsInt('SplitFiles',1,
          'Into how many files the private key should be parceled out.');
      until (splitfiles>0) or sw.NoPrompt;}
      newfile := EccCommandNew(
        auth,authpass,authrounds,issuer,start,days,savepass,saverounds,splitfiles);
      WritePassword(newfile,savepass,saverounds);
      if newfile<>'' then begin
        newfile := newfile+'/.private';
        if FileExists(ChangeFileExt(newfile, CHEAT_FILEEXT)) then
          newfile := newfile+('/'+CHEAT_FILEEXT);
      end;
    end;
    ecRekey: begin
      repeat
        auth := sw.AsString('Auth','',
          'Enter the first chars of the .private certificate file name.');
      until ECCKeyFileFind(auth,true) or sw.NoPrompt;
      sw.Text('Will use: %'#13#10,[ExtractFileName(auth)]);
      authpass := sw.AsUTF8('AuthPass','',
        'Enter the PassPhrase of this .private file.');
      authrounds := sw.AsInt('AuthRounds',DEFAULT_ECCROUNDS,
        'Enter the PassPhrase iteration rounds of this .private file.');
      repeat
        savepass := sw.AsUTF8('NewPass',TAESPRNG.Main.RandomPassword(12),
          'Enter a NEW private PassPhrase for the key (at least 8 chars long).'#13#10+
          'Save this in a safe place: if you forget it, the key will be useless!');
      until (length(savepass)>=8) or sw.NoPrompt;
      repeat
        saverounds := sw.AsInt('NewRounds',DEFAULT_ECCROUNDS,
          'Enter the NEW PassPhrase iteration rounds for the key (at least 1000).'#13#10+
          'The higher, the safer, but will demand more computation time.');
      until (saverounds>=1000) or sw.NoPrompt;
      newfile := EccCommandRekey(auth,authpass,authrounds,savepass,saverounds);
      WritePassword(newfile,savepass,saverounds);
      if FileExists(ChangeFileExt(newfile,CHEAT_FILEEXT)) then
        newfile := newfile+('/'+CHEAT_FILEEXT);
    end;
    ecSign: begin
      repeat
        origfile := sw.AsString('File','','Enter the name of the file to be signed.');
      until FileExists(origfile) or sw.NoPrompt;
      repeat
        auth := sw.AsString('Auth','',
          'Enter the first chars of the .private file name of the signing authority.');
      until ECCKeyFileFind(auth,true) or sw.NoPrompt;
      sw.Text('Will use: %'#13#10,[ExtractFileName(auth)]);
      authpass := sw.AsUTF8('Pass','',
        'Enter the PassPhrase of this .private file.');
      authrounds := sw.AsInt('Rounds',DEFAULT_ECCROUNDS,
        'Enter the PassPhrase iteration rounds of this .private file.');
      newfile := EccCommandSignFile(origfile,auth,authpass,authrounds,[]);
    end;
    ecVerify: begin
      repeat
        origfile := sw.AsString('File','','Enter the name of the file to be verified.');
      until sw.NoPrompt or
        (FileExists(origfile) and FileExists(origfile+ECCCERTIFICATESIGN_FILEEXT));
      WriteVerif(ECCCommandVerifyFile(origfile,sw.AsString('auth','',''),''),origfile,sw);
    end;
    ecSource: begin
      repeat
        auth := sw.AsString('Auth','',
          'Enter the first chars of the .private certificate file name.');
      until ECCKeyFileFind(auth,true) or sw.NoPrompt;
      sw.Text('Will use: %'#13#10,[ExtractFileName(auth)]);
      authpass := sw.AsUTF8('Pass','',
        'Enter the PassPhrase of this .private file.');
      authrounds := sw.AsInt('Rounds',DEFAULT_ECCROUNDS,
        'Enter the PassPhrase iteration rounds of this .private file.');
      constname := sw.AsUTF8('Const','',
        'Enter the variable name to define the const in source.');
      comment := sw.AsUTF8('Comment','',
        'Enter some optional comment to identify this private key.');
      newfile := EccCommandSourceFile(auth,authpass,authrounds,constname,comment,
        TAESPRNG.Main.RandomPassword(24));
    end;
    ecInfoPriv: begin
      repeat
        auth := sw.AsString('Auth','',
          'Enter the first chars of the .private certificate file name.');
      until ECCKeyFileFind(auth,true) or sw.NoPrompt;
      if not sw.NoPrompt then
        sw.Text('Will use: %'#13#10,[ExtractFileName(auth)]);
      authpass := sw.AsUTF8('Pass','',
        'Enter the PassPhrase of this .private file.');
      authrounds := sw.AsInt('Rounds',DEFAULT_ECCROUNDS,
        'Enter the PassPhrase iteration rounds of this .private file.');
      json := ECCCommandInfoPrivFile(auth,authpass,authrounds);
      sw.Text('%',[json]);
      jsonfile := sw.AsString('Json','','');
      if jsonfile <> '' then
        FileFromString(json,jsonfile);
      if not sw.NoPrompt then
        WritePassword(auth,authpass,authrounds);
    end;
    ecChain:
      newfile := ECCCommandChainCertificates(sw.AsArray);
    ecChainAll:
      newfile := ECCCommandChainCertificates(['*']);
    ecCrypt: begin
      repeat
        origfile := sw.AsString('File','','Enter the name of the file to be encrypted.');
      until FileExists(origfile) or sw.NoPrompt;
      repeat
        newfile := SysUtils.Trim(sw.AsString('Out',origfile+ENCRYPTED_FILEEXT,
          'Enter the name of the encrypted file'));
      until (newfile <> '') or sw.NoPrompt;
      repeat
        auth := sw.AsString('Auth','',
          'Enter the first chars of the .public file name of the encryption authority.');
      until ECCKeyFileFind(auth,false) or sw.NoPrompt;
      sw.Text('Will use: %'#13#10,[ExtractFileName(auth)]);
      authpass := sw.AsUTF8('SaltPass','salt','Enter the optional PassPhrase to be used for encryption.');
      authrounds := sw.AsInt('SaltRounds',DEFAULT_ECCROUNDS, 'Enter the PassPhrase iteration rounds.');
      algo := TECIESAlgo(sw.AsEnum('Algo', '0', TypeInfo(TECIESAlgo), ''));
      ECCCommandCryptFile(origfile,newfile,auth,'','',authpass,authrounds,algo);
    end;
    ecInfoCrypt: begin
      repeat
        origfile := sw.AsString('File','','Enter the name of the encrypted file.');
      until FileExists(origfile) or sw.NoPrompt;
      newfile := sw.AsString('RawFile','','');
      json := JSONReformat(ECIESHeaderText(origfile,newfile));
      sw.Text('%',[json]);
      jsonfile := sw.AsString('Json','','');
      if jsonfile <> '' then
        FileFromString(json,jsonfile);
    end;
    ecDecrypt: begin
      repeat
        origfile := sw.AsString('File','','Enter the name of the file to be decrypted.');
      until FileExists(origfile) or sw.NoPrompt;
      repeat
        newfile := SysUtils.Trim(sw.AsString('Out',GetFileNameWithoutExt(origfile)+'.2',
          'Enter the name of the decrypted file'));
      until (newfile <> '') or sw.NoPrompt;
      authpass := sw.AsUTF8('AuthPass','',
        'Enter the PassPhrase of the associated .private file.');
      authrounds := sw.AsInt('AuthRounds',DEFAULT_ECCROUNDS,
        'Enter the PassPhrase iteration rounds of this .private file.');
      savepass := sw.AsUTF8('SaltPass','salt','Enter the optional PassPhrase to be used for decryption.');
      saverounds := sw.AsInt('SaltRounds',DEFAULT_ECCROUNDS, 'Enter the PassPhrase iteration rounds.');
      decrypt := ECCCommandDecryptFile(origfile,newfile,
        sw.AsString('Auth','',''),authpass,authrounds,savepass,saverounds,@decryptsign,@meta);
      msg := SysUtils.LowerCase(GetCaptionFromEnum(TypeInfo(TECCDecrypt),ord(decrypt)));
      if decrypt in ECC_VALIDDECRYPT then begin
        if decrypt=ecdDecryptedWithSignature then
          WriteVerif(ECCCommandVerifyDecryptedFile(newfile,decryptsign),newfile,sw);
        if meta<>'' then
          sw.Text(' % file meta = %',[origfile,meta],ccGreen);
        sw.Text(' % file %.',[origfile,msg],ccLightGreen);
      end else begin
        sw.Text(' % file decryption failure: % (%).',[origfile,msg,ord(decrypt)],ccLightRed);
        result := eccError;
      end;
    end;
    ecAeadCrypt: begin
      repeat
        origfile := sw.AsString('File','','Enter the name of the file to be encrypted.');
      until FileExists(origfile) or sw.NoPrompt;
      repeat
        newfile := SysUtils.Trim(sw.AsString('Out',origfile+AEAD_FILEEXT,
          'Enter the name of the encrypted file'));
      until (newfile <> '') or sw.NoPrompt;
      authpass := sw.AsUTF8('Pass','', 'Enter the PassPhrase to be used for encryption.');
      savepass := sw.AsUTF8('Salt','salt','Enter the optional PassPhrase to be used for encryption.');
      authrounds := sw.AsInt('Rounds',DEFAULT_AEADROUNDS, 'Enter the PassPhrase iteration rounds.');
      AEADCommandCryptFile(origfile,newfile,authpass,savepass, authrounds);
    end;
    ecAeadDecrypt: begin
      repeat
        origfile := sw.AsString('File','','Enter the name of the file to be decrypted.');
      until FileExists(origfile) or sw.NoPrompt;
      repeat
        newfile := SysUtils.Trim(sw.AsString('Out',GetFileNameWithoutExt(origfile)+'.2',
          'Enter the name of the decrypted file'));
      until (newfile <> '') or sw.NoPrompt;
      authpass := sw.AsUTF8('Pass','','Enter the PassPhrase to be used for decryption.');
      savepass := sw.AsUTF8('Salt','salt','Enter the optional PassPhrase to be used for decryption.');
      authrounds := sw.AsInt('Rounds',DEFAULT_AEADROUNDS,'Enter the PassPhrase iteration rounds.');
      AEADCommandDecryptFile(origfile,newfile,authpass,savepass,authrounds);
      sw.Text(' % file decrypted.',[origfile],ccLightGreen);
    end;
    ecCheatInit: begin
      issuer := sw.AsUTF8('Issuer',ExeVersion.User,
        'Enter Issuer identifier text of the master cheat keys.'#13#10+
        'Will be truncated to 15-20 ascii-7 chars.');
      repeat
        savepass := sw.AsUTF8('NewPass',TAESPRNG.Main.RandomPassword(12),
          'Enter a private PassPhrase for the master cheat.private key (at least 8 chars).'#13#10+
          'Save this in a safe place: if you forget it, the key will be useless!');
      until (length(savepass)>=8) or sw.NoPrompt;
      repeat
        saverounds := sw.AsInt('NewRounds',CHEAT_ROUNDS,
          'Enter iteration rounds for the mastercheat.private key (at least 100000).');
      until (saverounds>=CHEAT_ROUNDS) or sw.NoPrompt;
      newfile := ECCCommandCheatInit(issuer,savepass,saverounds);
      if newfile<>'' then
        newfile := newfile+'/.private';
    end;
    ecCheat: begin
      repeat
        auth := sw.AsString('Auth','',
          'Enter the first chars of the .private certificate file name.');
      until ECCKeyFileFind(auth,true) or sw.NoPrompt;
      if not sw.NoPrompt then
        sw.Text('Will use: %'#13#10,[ExtractFileName(auth)]);
      savepass := sw.AsUTF8('AuthPass','',
        'Enter the PassPhrase of the master cheat.private file.');
      saverounds := sw.AsInt('AuthRounds',CHEAT_ROUNDS,
        'Enter the PassPhrase iteration rounds of the cheat.private file.');
      sw.Text('%',[ECCCommandCheat(auth,savepass,saverounds,authpass,authrounds)]);
      if not sw.NoPrompt then
        WritePassword(auth,authpass,authrounds);
    end;
    else
      result := eccUnknownCommand;
    end;
  except
    on E: Exception do begin
      if not sw.NoPrompt then
        ConsoleShowFatalException(E,false);
      newfile := '';
      result := eccException;
    end;
  end;
  finally
    if not sw.NoPrompt then begin
      FillcharFast(pointer(authpass)^,length(authpass),0);
      FillcharFast(pointer(savepass)^,length(savepass),0);
    end;
  end;
  if (newfile<>'') and (result=eccSuccess) and not(cmd in [ecInfoCrypt]) then
    sw.Text(' % file created.',[newfile],ccWhite);
  sw.TextColor(ccLightGray);
end;

end.
