Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
5529 lines (5086 sloc) 213 KB
/// certificate-based public-key cryptography using ECC-secp256r1
// - this unit is a part of the freeware Synopse mORMot framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynEcc;
(*
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2018 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) 2018
the Initial Developer. All Rights Reserved.
Contributor(s):
- Kenneth MacKay (easy-ecc source code)
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 *****
Using secp256r1 curve from "simple and secure ECDH and ECDSA library"
Copyright (c) 2013, Kenneth MacKay - BSD 2-clause license
https://github.com/esxgx/easy-ecc
*** BEGIN LICENSE BLOCK *****
Copyright (c) 2013, Kenneth MacKay
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
***** END LICENSE BLOCK *****
Version 1.18
- first public release, corresponding to mORMot Framework 1.18
TODO:
- secure sign-then-crypt by signing the destination name with the plain content
to avoid "Surreptitious Forwarding" (reuse of the plain content to another
recipier) - see http://world.std.com/~dtd/sign_encrypt/sign_encrypt7.html
*)
{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64 OWNNORMTOUPPER
interface
uses
{$ifdef MSWINDOWS}
Windows, // for CriticalSection API inling
{$else} // for GetFileSize emulated API
{$ifdef KYLIX3}
SynKylix,
{$endif}
{$ifdef FPC}
SynFPCLinux,
{$endif}
{$endif MSWINDOWS}
SysUtils,
Classes,
Contnrs,
SynCommons,
SynCrypto;
{ *********** low-level ECC secp256r1 ECDSA and ECDH functions *********** }
{$ifdef CPUINTEL}
{$ifdef CPUX86}
{$ifdef KYLIX3}
{$define ECC_STATICLIB_AVAILABLE}
{$define ECC_32ASM} // gcc -g -O1 -c ecc.c
{$else}
{$ifndef BSD}
{$define ECC_STATICLIB_AVAILABLE}
{.$define ECC_32ASM} // gcc -g -O1 -c ecc.c
{.$define ECC_O1} // gcc -g -O1 -c ecc.c
{$define ECC_O2} // gcc -g -O2 -c ecc.c
{.$define ECC_O3} // gcc -g -O3 -c ecc.c
{$endif}
{$endif KYLIX}
{$endif CPUX86}
{$ifdef CPUX64}
{$ifndef BSD}
{$define ECC_STATICLIB_AVAILABLE}
{.$define ECC_O1} // gcc -g -O1 -c ecc.c
{$define ECC_O2} // gcc -g -O2 -c ecc.c
{.$define ECC_O3} // gcc -g -O3 -c ecc.c
{$endif}
{$endif CPUX64}
{$endif CPUINTEL}
const
/// the size of the 256-bit memory structure used for secp256r1
// - map 32 bytes of memory
ECC_BYTES = sizeof(THash256);
type
/// store a public key for ECC secp256r1 cryptography
// - use ecc_make_key() to generate such a key
// - stored in compressed form with its standard byte header, i.e. each
// public key consumes 33 bytes of memory
TECCPublicKey = array[0..ECC_BYTES] of byte;
/// store a public key for ECC secp256r1 cryptography
// - use ecc_uncompress_key_pas() to compute such a key from a TECCPublicKey
// - stored in uncompressed form, consuming 64 bytes of memory
TECCPublicKeyUncompressed = array[0..(ECC_BYTES*2)-1] of byte;
/// store a private key for ECC secp256r1 cryptography
// - use ecc_make_key() to generate such a key
// - stored in compressed form, i.e. each private key consumes 32 bytes of memory
TECCPrivateKey = array[0..ECC_BYTES-1] of byte;
/// store a 256-bit hash, as expected by ECC secp256r1 cryptography
// - see e.g. ecdsa_sign() and ecdsa_verify() functions
TECCHash = THash256;
/// store a signature, as generated by ECC secp256r1 cryptography
// - see e.g. ecdsa_sign() and ecdsa_verify() functions
// - contains ECDSA's R and S integers
// - each ECC signature consumes 64 bytes of memory
TECCSignature = array[0..(ECC_BYTES*2)-1] of byte;
/// store an encryption key, as generated by ECC secp256r1 cryptography
// - use ecdh_shared_secret() to compute such a key from public/private keys
// - 256-bit / 32 bytes derivation from secp256r1 ECDH is expected to have at
// least 247 bits of entropy so could better be derivated via a KDF before used
// as encryption secret - see @http://crypto.stackexchange.com/a/9428/40200
TECCSecretKey = THash256;
PECCPublicKey = ^TECCPublicKey;
PECCPrivateKey = ^TECCPrivateKey;
PECCHash = ^TECCHash;
PECCSignature = ^TECCSignature;
PECCSecretKey = ^TECCSecretKey;
{$ifdef ECC_32ASM}
var
/// create a public/private key pair for further ECC cryptographic process
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - returns true if the key pair was generated successfully in pub/priv
// - returns false if an error occurred
ecc_make_key: function(out pub: TECCPublicKey; out priv: TECCPrivateKey): boolean; cdecl;
/// compute an ECDH shared secret given your secret key and someone else's public key
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - note: it is recommended that you hash the result of ecdh_shared_secret
// before using it for symmetric encryption or HMAC (via an intermediate KDF)
// - returns true if the shared secret was generated successfully in secret
// - returns false if an error occurred
ecdh_shared_secret: function(const pub: TECCPublicKey; const priv: TECCPrivateKey;
out secret: TECCSecretKey): boolean; cdecl;
/// generate an ECDSA signature for a given hash value
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - returns true if the signature generated successfully in sign
// - returns false if an error occurred
ecdsa_sign: function(const priv: TECCPrivateKey; const hash: TECCHash;
out sign: TECCSignature): boolean; cdecl;
/// verify an ECDSA signature
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - returns true if the signature is valid
// - returns false if it is invalid
ecdsa_verify: function(const pub: TECCPublicKey; const hash: TECCHash;
const sign: TECCSignature): boolean; cdecl;
{$else}
/// create a public/private key pair
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - directly low-level access to the statically linked easy-ecc library function
// - returns true if the key pair was generated successfully in pub/priv
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
function ecc_make_key(out pub: TECCPublicKey; out priv: TECCPrivateKey): boolean;
{$ifdef ECC_STATICLIB_AVAILABLE}cdecl;{$else}{$ifdef HASINLINE}inline;{$endif}{$endif}
/// compute a shared secret given your secret key and someone else's public key
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - directly low-level access to the statically linked easy-ecc library function
// - note: it is recommended that you hash the result of ecdh_shared_secret
// before using it for symmetric encryption or HMAC (via an intermediate KDF)
// - returns true if the shared secret was generated successfully in secret
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
function ecdh_shared_secret(const pub: TECCPublicKey; const priv: TECCPrivateKey;
out secret: TECCSecretKey): boolean;
{$ifdef ECC_STATICLIB_AVAILABLE}cdecl;{$else}{$ifdef HASINLINE}inline;{$endif}{$endif}
/// generate an ECDSA signature for a given hash value
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - directly low-level access to the statically linked easy-ecc library function
// - returns true if the signature generated successfully in sign
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
function ecdsa_sign(const priv: TECCPrivateKey; const hash: TECCHash;
out sign: TECCSignature): boolean;
{$ifdef ECC_STATICLIB_AVAILABLE}cdecl;{$else}{$ifdef HASINLINE}inline;{$endif}{$endif}
/// verify an ECDSA signature
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - directly low-level access to the statically linked easy-ecc library function
// - returns true if the signature is valid
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
function ecdsa_verify(const pub: TECCPublicKey; const hash: TECCHash;
const sign: TECCSignature): boolean;
{$ifdef ECC_STATICLIB_AVAILABLE}cdecl;{$else}{$ifdef HASINLINE}inline;{$endif}{$endif}
{$endif ECC_32ASM}
/// pascal function to create a secp256r1 public/private key pair
// - used only on targets (e.g. ARM/PPC) when the static .o version is not available
function ecc_make_key_pas(out PublicKey: TECCPublicKey; out PrivateKey: TECCPrivateKey): boolean;
/// pascal function to compute a secp256r1 shared secret given your secret key
// and someone else's public key (in compressed format)
// - used only on targets (e.g. ARM/PPC) when the static .o version is not available
function ecdh_shared_secret_pas(const PublicKey: TECCPublicKey;
const PrivateKey: TECCPrivateKey; out Secret: TECCSecretKey): boolean; overload;
/// pascal function to compute a secp256r1 shared secret given your secret key
// and someone else's public key (in uncompressed/flat format)
// - this overloaded function is slightly faster than the one using TECCPublicKey,
// since public key doesn't need to be uncompressed
function ecdh_shared_secret_pas(const PublicPoint: TECCPublicKeyUncompressed;
const PrivateKey: TECCPrivateKey; out Secret: TEccSecretKey): boolean; overload;
/// pascal function to generate an ECDSA secp256r1 signature for a given hash value
// - used only on targets (e.g. ARM/PPC) when the static .o version is not available
function ecdsa_sign_pas(const PrivateKey: TECCPrivateKey; const Hash: TECCHash;
out Signature: TECCSignature): boolean;
/// pascal function to verify an ECDSA secp256r1 signature from someone else's
// public key (in compressed format)
// - used only on targets (e.g. ARM/PPC) when the static .o version is not available
function ecdsa_verify_pas(const PublicKey: TECCPublicKey; const Hash: TECCHash;
const Signature: TECCSignature): boolean; overload;
/// pascal function to verify an ECDSA secp256r1 signature from someone else's
// public key (in uncompressed/flat format)
// - this overloaded function is slightly faster than the one using TECCPublicKey,
// since public key doesn't need to be uncompressed
function ecdsa_verify_pas(const PublicKey: TECCPublicKeyUncompressed;
const Hash: TECCHash; const Signature: TECCSignature): boolean; overload;
/// uncompress a public key for ECC secp256r1 cryptography
// - convert from its compressed form with its standard byte header
// (33 bytes of memory) into uncompressed/flat form (64 bytes of memory)
procedure ecc_uncompress_key_pas(const Compressed: TECCPublicKey;
out Uncompressed: TECCPublicKeyUncompressed);
{ *********** middle-level certificate-based public-key cryptography *********** }
type
/// used to identify a TECCCertificate
// - could be generated by TAESPRNG.Fill() method
TECCCertificateID = type THash128;
/// used to identify a TECCCertificate issuer
// - could be generated by AsciiToBaudot(), with truncation to 16 bytes
// (up to 25 Ascii-7 characters)
TECCCertificateIssuer = type THash128;
/// used to store a date in a TECCCertificate
// - i.e. 16-bit number of days since 1 August 2016
// - use NowECCDate, ECCDate(), ECCToDateTime() or ECCText() functions
TECCDate = word;
PECCCertificateID = ^TECCCertificateID;
PECCCertificateIssuer = ^TECCCertificateIssuer;
PECCDate = ^TECCDate;
/// the certification information of a TECCCertificate
// - as stored in TECCCertificateContent.Signed
// - defined in a separate record, to be digitaly signed in the Signature field
// - map TECCCertificate.Version 1 of the binary format
// - "self-signed" certificates may be used as "root" certificates in the
// TECCCertificateChain list
TECCCertificateSigned = packed record
/// when this certificate was generated
IssueDate: TECCDate;
/// certificate valid not before
ValidityStart: TECCDate;
/// certificate valid not after
ValidityEnd: TECCDate;
/// a genuine identifier for this certificate
// - is used later on to validate other certificates in chain
Serial: TECCCertificateID;
/// identify the certificate issuer
// - is either geniune random bytes, or some Baudot-encoded text
Issuer: TECCCertificateIssuer;
/// genuine identifier of the authority certificate used for signing
// - should be used to retrieve the associated PublicKey used to compute
// the Signature field
// - may equal Serial, if was self-signed
AuthoritySerial: TECCCertificateID;
/// identify the authoritify issuer used for signing
// - is either geniune random bytes, or some Baudot-encoded text
// - may equal Issuer, if was self-signed
AuthorityIssuer: TECCCertificateIssuer;
/// the ECDSA secp256r1 public key of this certificate
// - may be used later on for signing or key derivation
PublicKey: TECCPublicKey;
end;
/// points to certification information of a TECCCertificate
PECCCertificateSigned = ^TECCCertificateSigned;
/// store a TECCCertificate binary buffer for ECC secp256r1 cryptography
// - i.e. a certificate public key, with its ECDSA signature
// - would be stored in 173 bytes
TECCCertificateContent = packed record
/// the TECCCertificate format version
Version: word;
/// the certification information, digitaly signed in the Signature field
Signed: TECCCertificateSigned;
/// SHA-256 + ECDSA secp256r1 signature of the Certificate record
Signature: TECCSignature;
/// FNV-1a checksum of all previous fields
// - we use fnv32 and not crc32c here to avoid colision with crc64c hashing
// - avoiding to compute slow ECDSA verification in case of corruption,
// due e.g. to unexpected transmission/bug/fuzzing
// - should be the very last field in the record
CRC: cardinal;
end;
/// points to a TECCCertificate binary buffer for ECC secp256r1 cryptography
PECCCertificateContent = ^TECCCertificateContent;
/// store a TECCSignatureCertified binary buffer for ECDSA secp256r1 signature
// - i.e. the digital signature of some content
TECCSignatureCertifiedContent = packed record
/// the TECCSignatureCertificated format version
Version: word;
/// when this signature was generated
Date: TECCDate;
/// genuine identifier of the authority certificate used for signing
// - should be used to retrieve the associated PublicKey used to compute
// the Signature field
AuthoritySerial: TECCCertificateID;
/// identify the authoritify issuer used for signing
// - is either geniune random bytes, or some Baudot-encoded text
AuthorityIssuer: TECCCertificateIssuer;
/// SHA-256 + ECDSA secp256r1 digital signature of the content
Signature: TECCSignature;
end;
/// points to a TECCSignatureCertified buffer for ECDSA secp256r1 signature
PECCSignatureCertifiedContent = ^TECCSignatureCertifiedContent;
/// 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 Message Authentication Code algorithm
// - 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);
/// 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 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;
/// indicate the validity state of a ECDSA signature against a certificate
// - as returned by low-level ECCVerify() function, and
// TECCSignatureCertified.Verify, TECCCertificateChain.IsValid or
// TECCCertificateChain.IsSigned methods
// - see also ECC_VALIDSIGN constant
TECCValidity = (
ecvUnknown,
ecvValidSigned, ecvValidSelfSigned,
ecvNotSupported, ecvBadParameter, ecvCorrupted,
ecvInvalidDate, ecvUnknownAuthority, ecvDeprecatedAuthority,
ecvInvalidSignature);
/// the error codes returned by TECCCertificateSecret.Decrypt()
// - see also ECC_VALIDDECRYPT constant
TECCDecrypt = (
ecdDecrypted, ecdDecryptedWithSignature,
ecdNoContent, ecdCorrupted, ecdInvalidSerial, ecdNoPrivateKey,
ecdInvalidMAC, ecdDecryptError, ecdWriteFileError);
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
// - used to compute the EF secret and MAC secret from shared ephemeral secret
// - 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 will use the dedicated TAESCFBCRC 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
// - other values will define TAESCFB/TAESOFB/TAESCTR/TAESCBC in 128-bit or
// 256-bit mode, in conjunction with a TECDHEMAC setting
// - AES-NI hardware acceleration will be used, if available - under x86-64,
// efAesOfb128 will potentially give the best performance
// - of course, weack ECB mode is not available
TECDHEEF = (efAesCrc128, efAesCfb128, efAesOfb128, efAesCtr128, efAesCbc128,
efAesCrc256, efAesCfb256, efAesOfb256, efAesCtr256, efAesCbc256);
/// 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:
// only supported by efAesCrc128 or efAesCrc256 (may be a future AES-GCM)
// - 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: TECCCertificateContent;
/// 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: TECCCertificateContent;
/// server-generated ephemeral public key
// - may be zero, in case of unilateral authentication (algo=authServer)
QF: TECCPublicKey;
/// SHA-256 + ECDSA secp256r1 signature of the previous 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;
const
/// TECCValidity results indicating a valid digital signature
ECC_VALIDSIGN = [ecvValidSigned, ecvValidSelfSigned];
/// TECCDecrypt results indicating a valid decryption process
ECC_VALIDDECRYPT = [ecdDecrypted, ecdDecryptedWithSignature];
/// fill all bytes of this ECC private key buffer with zero
// - may be used to cleanup stack-allocated content
// ! ... finally FillZero(PrivateKey); end;
procedure FillZero(out Priv: TECCPrivateKey); overload;
/// returns the current UTC date, as a TECCDate integer value
// - i.e. 16-bit number of days since 1 August 2016
function NowECCDate: TECCDate;
{$ifdef HASINLINE}inline;{$endif}
/// convert a supplied TDateTime value into a TECCDate integer value
// - i.e. 16-bit number of days since 1 August 2016
// - returns 0 if the supplied value is invalid, i.e. out of range
function ECCDate(const DateTime: TDateTime): TECCDate;
/// convert a supplied a TECCDate integer value into a TDateTime value
// - i.e. 16-bit number of days since 1 August 2016
function ECCToDateTime(ECCDate: TECCDate): TDateTime;
{$ifdef HASINLINE}inline;{$endif}
/// convert a supplied a TECCDate integer value into a ISO-8601 text value
// - i.e. 16-bit number of days since 1 August 2016
function ECCText(ECCDate: TECCDate; Expanded: boolean=true): RawUTF8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// compare two TECCCertificateIssuer binary buffer values
function IsEqual(const issuer1,issuer2: TECCCertificateIssuer): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// compare two TECCCertificateID binary buffer values
function IsEqual(const id1,id2: TECCCertificateID): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// ensure a TECCCertificateIssuer binary buffer is not void, i.e. filled with 0
function IsZero(const issuer: TECCCertificateIssuer): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// ensure a TECCCertificateID binary buffer is not void, i.e. filled with 0
function IsZero(const id: TECCCertificateID): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert a supplied TECCCertificateIssuer binary buffer into proper text
// - returns Ascii-7 text if was stored using Baudot encoding
// - or returns hexadecimal values, if it was 16 bytes of random binary
function ECCText(const Issuer: TECCCertificateIssuer): RawUTF8; overload;
/// convert some Ascii-7 text into a TECCCertificateIssuer binary buffer
// - using Emile Baudot encoding
// - returns TRUE on Text truncation to fit into the 16 bytes
function ECCIssuer(const Text: RawUTF8; out Issuer: TECCCertificateIssuer): boolean;
/// convert a supplied TECCCertificateID binary buffer into proper text
// - returns hexadecimal values, or '' if the ID is filled with zeros
function ECCText(const ID: TECCCertificateID): RawUTF8; overload;
/// convert a supplied hexadecimal buffer into a TECCCertificateID binary buffer
// - returns TRUE if the supplied Text was a valid hexadecimal buffer
function ECCID(const Text: RawUTF8; out ID: TECCCertificateID): boolean;
/// fast check of the binary buffer storage of a 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 ECCCheck(const content: TECCCertificateContent): boolean; overload;
/// fast check of the dates stored in a certificate binary buffer
// - could be validated against ECCCheck()
function ECCCheckDate(const content: TECCCertificateContent): boolean;
/// fast check if the binary buffer storage of a certificate was self-signed
// - a self-signed certificate will have its AuthoritySerial/AuthorityIssuer
// fields matching Serial/Issuer
function ECCSelfSigned(const content: TECCCertificateContent): boolean;
/// fast check of the binary buffer storage of a signature
// - just check that the date and authority are set
function ECCCheck(const content: TECCSignatureCertifiedContent): boolean; overload;
/// convert a supplied base-64 text into a TECCSignatureCertifiedContent binary buffer
function ECCSign(const base64: RawUTF8; out content: TECCSignatureCertifiedContent): boolean;
/// convert a supplied TECCSignatureCertifiedContent binary buffer into proper text
// - returns base-64 encoded text, or '' if the signature was filled with zeros
function ECCText(const sign: TECCSignatureCertifiedContent): RawUTF8; overload;
/// convert a supplied TECCSignature binary buffer into proper text
// - returns base-64 encoded text, or '' if the signature was filled with zeros
function ECCText(const sign: TECCSignature): RawUTF8; overload;
/// low-level verification of a TECCSignatureCertifiedContent binary buffer
// - will verify all internal signature fields according to a supplied authority,
// then will perform the ECDSA verification of the supplied 256-bit hash with
// the authority public key
// - as used by TECCSignatureCertified.Verify and TECCCertificateChain.IsValid
function ECCVerify(const sign: TECCSignatureCertifiedContent;
const hash: THash256; const auth: TECCCertificateContent): TECCValidity;
/// 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;
{ *********** high-level certificate-based public-key cryptography *********** }
const
DEFAULT_ECCROUNDS = 60000;
type
/// exception class associated with this SynEcc 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;
function GetAuthorityIssuer: RawUTF8;
function GetAuthoritySerial: RawUTF8;
function GetIssueDate: RawUTF8;
function GetIssuer: RawUTF8;
function GetSerial: RawUTF8;
function GetValidityEnd: RawUTF8;
function GetValidityStart: RawUTF8;
function GetIsSelfSigned: boolean;
function InternalLoad(const data: RawByteString): boolean; virtual;
function InternalSave: RawByteString; virtual;
procedure SetBase64(const base64: RawUTF8);
public
/// initialize this certificate
constructor Create; override;
/// initialize this certificate from a supplied certificate binary
// - will raise an EECCException if the supplied binary is incorrect
constructor CreateFrom(const binary: TECCCertificateContent); virtual;
/// initialize this certificate from a supplied base-64 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, base-64 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.Signed;
/// SHA-256 + ECDSA secp256r1 signature of the Certificate record
property Signature: TECCSignature read fContent.Signature;
/// persist the certificate as some base-64 encoded binary
// - will use SaveToStream serialization
function ToBase64: RawUTF8;
/// retrieve the certificate from some base-64 encoded binary
// - will use LoadFromStream serialization
// - returns true on success, false otherwise
function FromBase64(const base64: RawUTF8): 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, base-64 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;
/// persist only the public certificate as some base-64 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;
/// 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 base-64 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;
/// encrypt using the ECIES scheme, using this public certificate as key,
// via AES-256-CFB/PKCS7 over PBKDF2_HMAC_SHA256, and HMAC_SHA256
// - 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 over PBKDF2_HMAC_SHA256, and HMAC_SHA256
// - 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;
{$ifndef NOVARIANTS}
/// 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 .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;
{$endif}
/// low-level access to the binary buffer used ECC secp256r1 cryptography
// - 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.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;
/// 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;
/// base-64 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;
/// used to store a list of TECCCertificate instances
// - e.g. in TECCCertificateChain.Items
// - TJSONSerializer.RegisterObjArrayForJSON done in dddInfraApps and not
// in this unit to avoid dependency to mORMot.pas
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;
function InternalLoad(const data: RawByteString): boolean; override;
function InternalSave: 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 ecdsa_verify() to ensure its usefulness
// - would take around 4 ms under a 32-bit compiler, and 1 ms under 64-bit
constructor CreateNew(Authority: TECCCertificateSecret; const IssuerText: RawUTF8='';
ExpirationDays: integer=0; StartDate: TDateTime=0; ParanoidVerify: boolean=true);
/// 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;
PBKDF2Rounds: 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;
PBKDF2Rounds: 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;
PBKDF2Rounds: 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; PBKDF2Rounds: integer=DEFAULT_ECCROUNDS;
AES: TAESAbstractClass=nil); overload;
/// 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 from PBKDF2_HMAC_SHA256 derivation of an user-supplied password
function SaveToSecureFile(const PassWord: RawUTF8; const DestFolder: TFileName;
AFStripes: integer=64; PBKDF2Rounds: 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 from PBKDF2_HMAC_SHA256 derivation of an user-supplied password
function SaveToSecureFiles(const PassWord: RawUTF8; const DestFolder: TFileName;
DestFileCount: integer; AFStripes: integer=64; PBKDF2Rounds: 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;
PBKDF2Rounds: 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 from PBKDF2_HMAC_SHA256 derivation of an user-supplied password
function SaveToSecureBinary(const PassWord: RawUTF8; AFStripes: integer=64;
PBKDF2Rounds: 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; PBKDF2Rounds: 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;
PBKDF2Rounds: 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;
PBKDF2Rounds: integer=DEFAULT_ECCROUNDS; AES: TAESAbstractClass=nil): boolean; overload;
public
/// compute a base-64 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 base-64 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;
{$ifndef NOVARIANTS}
/// 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;
{$endif}
/// decrypt using the ECIES scheme, using this private certificate as key,
// via AES-256-CFB/PKCS7 over PBKDF2_HMAC_SHA256, and HMAC_SHA256
// - 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 over PBKDF2_HMAC_SHA256, and HMAC_SHA256
// - 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;
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;
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: RawUTF8 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
fContent: 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 base-64 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): 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): TECCValidity; overload;
/// persist the signature as some base-64 encoded binary
function ToBase64: RawUTF8;
{$ifndef NOVARIANTS}
/// returns a TDocVariant object of all published properties of this instance
function ToVariant: variant; virtual;
{$endif}
/// retrieve the signature from some base-64 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;
/// low-level access to the binary buffer used ECDSA secp256r1 cryptography
// - you should not use this property, but other methods
property Content: TECCSignatureCertifiedContent read fContent write fContent;
published
/// the TECCSignatureCertified format version
// - currently equals 1
property Version: word read fContent.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;
{$ifndef NOVARIANTS}
/// 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;
{$endif NOVARIANTS}
/// manage PKI certificates using ECC secp256r1 cryptography
// - will implement a simple and efficient public-key infrastructure (PKI),
// based on JSON objects or even plain base-64 encoded JSON strings
// - consider using TECCCertificateChainFile from mORMot.pas if you want
// to use convenient human-readable JSON serialization in files
TECCCertificateChain = class(TSynPersistentLocked)
protected
fItems: TECCCertificateObjArray;
fIsValidCached: boolean;
fIsValidCacheCount: integer;
fIsValidCache: TInt64DynArray;
function GetCount: integer;
function InternalAdd(cert: TECCCertificate; expected: TECCValidity): integer;
procedure SetIsValidCached(const Value: boolean);
function IndexBySerial(const Serial: TECCCertificateID): integer;
public
/// initialize the certificate store from some JSON array of strings
// - the serialization format is just a JSON array of base-64 encoded
// certificates (with only public keys) - so diverse from CreateFromFile()
// - will call LoadFromJson(), and raise EECCException on any error
constructor CreateFromJson(const json: RawUTF8);
/// initialize the certificate store from an array of base-64 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);
/// finalize the certificate store
destructor Destroy; override;
/// delete all stored certificates
// - this method is thread-safe, calling Safe.Lock/Unlock
procedure Clear;
/// search for a certificate from its hexadecimal text identifier
// - this method is not thread-safe, unless you use Safe.Lock/Unlock
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.Lock/Unlock
function GetBySerial(const Serial: TECCCertificateID): TECCCertificate; overload;
/// search for a certificate binary content from its binary identifier
// - returns TRUE if the Serial identifier was found, FALSE otherwise
// - this method is thread-safe, since it will make a private copy of the content
function GetBySerial(const Serial: TECCCertificateID;
out Content: TECCCertificateContent): boolean; overload;
/// search for a certificate public key from its binary identifier
// - returns TRUE if the Serial identifier was found, FALSE otherwise
// - this method is thread-safe, since it will make a private copy of the key
function GetBySerial(const Serial: TECCCertificateID;
out PublicKey: TECCPublicKey): 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): TECCValidity; overload;
/// check if the 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[])
// - consider setting IsValidCached property to TRUE to reduce resource use
// - this method is thread-safe, and not blocking
function IsValid(const content: TECCCertificateContent;
ignoreDate: boolean=false): TECCValidity; overload;
/// 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
// - will check that sign.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(sign: TECCSignatureCertified): boolean; overload;
/// check if the digital signature is recognized by the stored certificates
// - will check that sign.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 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;
{$ifndef NOVARIANTS}
/// 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;
{$endif NOVARIANTS}
/// 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): 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): TECCValidity; overload;
/// verify the base-64 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): TECCValidity; overload;
/// verify the base-64 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): 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, 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): integer;
/// 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
// - returns -1 on error, e.g. if the certificate was not valid,
// not self-signed or its serial was already part of the internal list
// - this method is thread-safe
function AddSelfSigned(cert: TECCCertificate): integer;
/// save the whole certificates chain as an array of base-64 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 base-64 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. base-64 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;
{$ifndef NOVARIANTS}
public
/// initialize the certificate store from some JSON-serialized .ca file
// - the file would store plain verbose information of all certificates,
// i.e. base-64 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. base-64 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. base-64 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. base-64 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;
{$endif NOVARIANTS}
{$ifndef DELPHI5OROLDER}
published
{$endif}
/// 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;
/// if the IsValid() calls should maintain a cache of all valid certificates
// - will use a naive but very efficient crc64c hashing of previous contents
// - since ecdsa_verify() is very 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;
/// 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
// - 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-CFB 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;
fAES: array[boolean] of TAESAbstract;
fkM: array[boolean] of THash256Rec;
// contains inc(PInt64(@aKey)^) to maintain RX/TX sequence number
procedure SetKey(aEncrypt: boolean);
procedure ComputeMAC(aEncrypt: boolean; aEncrypted: pointer; aLen: integer;
out aMAC: THash256Rec);
function Verify(frame: PByteArray; len: integer; const QC: TECCCertificateContent;
out res: TProtocolResult): boolean;
procedure Sign(frame: PByteArray; len: integer; out QC: TECCCertificateContent);
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
// - aPrivate should always be set for mutual or unilateral authentication
// - will implement unilateral authentication if aPrivate=nil for this end
constructor Create(aAuth: TECDHEAuth; aPKI: TECCCertificateChain;
aPrivate: TECCCertificateSecret); 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 a=... pair
// - 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=..
class function FromKey(const aKey: RawUTF8; aServer: boolean): TECDHEProtocol;
/// defines the default PKI instance to be used by FromKey
// - used if the ca=... property is not set in the aKey value
class procedure FromKeySetCA(aPKI: TECCCertificateChain);
/// computes 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); 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); 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;
{$ifndef NOVARIANTS}
/// implements JSON Web Tokens using 'ES256' algorithm
// - i.e. ECDSA using the P-256 curve and the SHA-256 hash algorithm
// - as defined in http://tools.ietf.org/html/rfc7518 paragraph 3.4
// - since ECDSA signature and verification is CPU consumming (under x86, it
// takes 2.5 ms, but only 0.3 ms on x64) you may enable CacheTimeoutSeconds
TJWTES256 = class(TJWTAbstract)
protected
fCertificate: TECCCertificate;
fOwnCertificate: boolean;
function ComputeSignature(const headpayload: RawUTF8): RawUTF8; override;
procedure CheckSignature(const headpayload: RawUTF8; const signature: RawByteString;
var JWT: TJWTContent); override;
public
/// initialize the JWT processing instance using ECDSA P-256 algorithm
// - the supplied set of claims are expected to be defined in the JWT payload
// - the supplied ECC certificate should be a TECCCertificate storing the
// public key needed for Verify(), or a TECCCertificateSecret storing also
// the private key required by Compute()
// - aCertificate is owned by this instance if property OwnCertificate is true
// - aAudience are the allowed values for the jrcAudience claim
// - aExpirationMinutes is the deprecation time for the jrcExpirationTime claim
// - aIDIdentifier and aIDObfuscationKey are passed to a
// TSynUniqueIdentifierGenerator instance used for jrcJwtID claim
constructor Create(aCertificate: TECCCertificate; aClaims: TJWTClaims;
const aAudience: array of RawUTF8; aExpirationMinutes: integer=0;
aIDIdentifier: TSynUniqueIdentifierProcess=0; aIDObfuscationKey: RawUTF8=''); reintroduce;
/// finalize the instance
destructor Destroy; override;
/// access to the associated TECCCertificate instance
// - which may be a TECCCertificateSecret for Compute() private key
property Certificate: TECCCertificate read fCertificate;
/// if the associated TECCCertificate is to be owned by this instance
property OwnCertificate: boolean read fOwnCertificate write fOwnCertificate;
end;
{$endif NOVARIANTS}
const
/// file extension of the JSON file storing a TECCCertificate public key
ECCCERTIFICATEPUBLIC_FILEEXT = '.public';
/// file extension of the binary encrypted file storing a private key
// - as generated by TECCCertificateSecret.SaveToSecureFile method
ECCCERTIFICATESECRET_FILEEXT = '.private';
/// 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 mORMot.pas TECCCertificateChainFile.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';
/// 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;
function ToText(val: TECCValidity): PShortString; overload;
function ToText(res: TECCDecrypt): PShortString; overload;
function ToText(algo: TECIESAlgo): PShortString; overload;
function ToText(algo: TECDHEAuth): PShortString; overload;
function ToText(algo: TECDHEKDF): PShortString; overload;
function ToText(algo: TECDHEEF): PShortString; overload;
function ToText(algo: TECDHEMAC): PShortString; overload;
implementation
{ *********** low-level ECC secp256r1 ECDSA and ECDH functions *********** }
function getRandomNumber(out dest: THash256): integer;
{$ifdef ECC_STATICLIB_AVAILABLE} cdecl;
{$ifdef FPC}public name{$ifdef Win32}'_getRandomNumber'{$else}'getRandomNumber'{$endif};{$endif}
{$endif}
begin
TAESPRNG.Fill(dest);
result := 1;
end;
{
Benchmark of all available x86/32-bit variants, compiled with MinGW-W64 5.2.0
gcc -g -O2 -c ecc.c
d:\dev\tools\objconv.exe -fomf -nd -nu- ecc.o
del eccwin32O2.o
ren ecc.o SynEccWin32O2.o
del eccwin32O2.obj
ren ecc.obj SynEccWin32O2.obj
Win32 ECC_32ASM
- ecc_make_key: 1,000 assertions passed 2.38s
- ecdsa_sign: 1,000 assertions passed 2.44s
- ecdsa_verify: 1,000 assertions passed 2.96s
- ecdh_shared_secret: 2,997 assertions passed 5.08s
Total failed: 0 / 5,997 - ECC cryptography PASSED 12.88s
Linux32 (Kylix) ECC_32ASM
- ecc_make_key: 1,000 assertions passed 2.36s
- ecdsa_sign: 1,000 assertions passed 2.44s
- ecdsa_verify: 1,000 assertions passed 2.95s
- ecdh_shared_secret: 2,997 assertions passed 5.07s
Total failed: 0 / 5,997 - ECC cryptography PASSED 12.84s
Win32 ECC_O1 (eccwin32O1.obj = 10480 bytes)
- ecc_make_key: 1,000 assertions passed 2.34s
- ecdsa_sign: 1,000 assertions passed 2.42s
- ecdsa_verify: 1,000 assertions passed 2.91s
- ecdh_shared_secret: 2,997 assertions passed 4.98s
Total failed: 0 / 5,997 - ECC cryptography PASSED 12.67s
Win32 ECC_O2 (eccwin32O2.obj = 16700 bytes)
- ecc_make_key: 1,000 assertions passed 2.16s
- ecdsa_sign: 1,000 assertions passed 2.20s
- ecdsa_verify: 1,000 assertions passed 2.66s
- ecdh_shared_secret: 2,997 assertions passed 4.58s
Total failed: 0 / 5,997 - ECC cryptography PASSED 11.63s
Win32 ECC_O3 (eccwin32O3.obj = 66798 bytes)
- ecc_make_key: 1,000 assertions passed 2.17s
- ecdsa_sign: 1,000 assertions passed 2.20s
- ecdsa_verify: 1,000 assertions passed 2.65s
- ecdh_shared_secret: 2,997 assertions passed 4.59s
Total failed: 0 / 5,997 - ECC cryptography PASSED 11.64s
-> conclusion: under Win32, ECC_O2 is used, and ECC_32ASM for Kylix+FPC
time is around 2-3 ms for each operation (i.e. 400-500/sec)
Benchmark of all available x64/64-bit variants, compiled with MinGW-W64 5.2.0
Win64 ECC_O1 (eccwin64O1.o = 45765 bytes)
- ecc_make_key: 1,000 assertions passed 601.37ms
- ecdsa_sign: 1,000 assertions passed 622.23ms
- ecdsa_verify: 1,000 assertions passed 758.28ms
- ecdh_shared_secret: 2,997 assertions passed 1.26s
Total failed: 0 / 5,997 - ECC cryptography PASSED 3.32s
Win64 ECC_O2 (eccwin64O2.o = 84779 bytes)
- ecc_make_key: 1,000 assertions passed 573.09ms
- ecdsa_sign: 1,000 assertions passed 588.86ms
- ecdsa_verify: 1,000 assertions passed 712.31ms
- ecdh_shared_secret: 2,997 assertions passed 1.20s
Total failed: 0 / 5,997 - ECC cryptography PASSED 3.16s
Win64 ECC_O3 (eccwin64O3.o = 204775 bytes)
- access violation at startup (due to .o linking error by Delphi)
-> conclusion: under Win64, ECC_O2 is used
time is around 0.5-0.6 ms for each operation (i.e. 2000/sec)
x64 is four time faster than x86 for such arithmetic tasks :)
}
{$ifdef ECC_STATICLIB_AVAILABLE}
{$ifdef ECC_32ASM}
{$I SynEcc32asm.inc}
{$else}
{$ifdef CPUX86}
{$ifdef FPC}
{$ifdef MSWINDOWS}
{$ifdef ECC_O1}
{$L static\i386-win32\eccwin32O1.o}
{$endif}
{$ifdef ECC_O2}
{$L static\i386-win32\eccwin32O2.o}
{$endif}
{$ifdef ECC_O3}
{$L static\i386-win32\eccwin32O3.o}
{$endif}
{$else}
{$ifdef ECC_O1}
{$L static/i386-linux/ecclin32O1.o}
{$endif}
{$ifdef ECC_O2}
{$L static/i386-linux/ecclin32O2.o}
{$endif}
{$ifdef ECC_O3}
{$L static/i386-linux/ecclin32O3.o}
{$endif}
{$endif MSWINDOWS}
{$else}
{$ifdef ECC_O1}
{$L SynEcc32O1.obj}
{$endif}
{$ifdef ECC_O2}
{$L SynEcc32O2.obj}
{$endif}
{$ifdef ECC_O3}
{$L SynEcc32O3.obj}
{$endif}
{$endif FPC}
{$endif CPUX86}
{$ifdef CPUX64}
{$ifdef MSWINDOWS} // same .o format under Win64 for Delphi and FPC :)
{$ifdef ECC_O1}
{$L SynEcc64O1.o}
{$endif}
{$ifdef ECC_O2}
{$L SynEcc64O2.o}
{$endif}
{$ifdef ECC_O3}
{$L SynEcc64O3.o}
{$endif}
{$else}
{$ifdef FPC}
{$ifdef ECC_O1}
{$L static/x86_64-linux/ecclin64O1.o}
{$endif}
{$ifdef ECC_O2}
{$L static/x86_64-linux/ecclin64O2.o}
{$endif}
{$ifdef ECC_O3}
{$L static/x86_64-linux/ecclin64O3.o}
{$endif}
{$endif FPC}
{$endif MSWINDOWS}
{$endif CPUX64}
function ecc_make_key; external;
function ecdh_shared_secret; external;
function ecdsa_sign; external;
function ecdsa_verify; external;
{$endif ECC_32ASM}
{$else ECC_STATICLIB_AVAILABLE}
// currently no .o file available under ARM/PPC -> stub calls of pascal functions
function ecc_make_key(out pub: TECCPublicKey; out priv: TECCPrivateKey): boolean;
begin
result := ecc_make_key_pas(pub, priv);
end;
function ecdh_shared_secret(const pub: TECCPublicKey; const priv: TECCPrivateKey;
out secret: TECCSecretKey): boolean;
begin
result := ecdh_shared_secret_pas(pub, priv, secret);
end;
function ecdsa_sign(const priv: TECCPrivateKey; const hash: TECCHash;
out sign: TECCSignature): boolean;
begin
result := ecdsa_sign_pas(priv, hash, sign);
end;
function ecdsa_verify(const pub: TECCPublicKey; const hash: TECCHash;
const sign: TECCSignature): boolean;
begin
result := ecdsa_verify_pas(pub, hash, sign);
end;
{$endif ECC_STATICLIB_AVAILABLE}
{$ifdef HASUINT64}
{ Pure Pascal Version of low-level ECC process (adapted from easy-ecc.c code)
Some numbers (on another slower computer than the previous values above),
which is quite acceptable, since it is faster than gcc -O1 mode :)
(of course, UInt128 support in gcc -O2 is still preferred on x86_64)
Delphi 7 pascal
- ecc_make_key: 1,000 assertions passed 2.75s
- ecdsa_sign: 1,000 assertions passed 2.80s
- ecdsa_verify: 1,000 assertions passed 3.39s
- ecdh_shared_secret: 2,997 assertions passed 5.68s
Total failed: 0 / 529,825 - ECC cryptography PASSED 18.77s
Delphi 7 ECC_32ASM
- ecc_make_key: 1,000 assertions passed 2.86s
- ecdsa_sign: 1,000 assertions passed 2.92s
- ecdsa_verify: 1,000 assertions passed 3.75s
- ecdh_shared_secret: 2,997 assertions passed 6.11s
Total failed: 0 / 529,825 - ECC cryptography PASSED 19.89s
FPC Win32 pascal
- ecc_make_key: 1,000 assertions passed 2.78s
- ecdsa_sign: 1,000 assertions passed 2.85s
- ecdsa_verify: 1,000 assertions passed 3.46s
- ecdh_shared_secret: 2,997 assertions passed 5.89s
Total failed: 0 / 529,825 - ECC cryptography PASSED 19.72s
Delphi 7 ECC_O2
- ecc_make_key: 1,000 assertions passed 2.58s
- ecdsa_sign: 1,000 assertions passed 2.64s
- ecdsa_verify: 1,000 assertions passed 3.19s
- ecdh_shared_secret: 2,997 assertions passed 5.43s
Total failed: 0 / 529,825 - ECC cryptography PASSED 17.86s
Delphi XE7-32 pascal
- ecc_make_key: 1,000 assertions passed 2.74s
- ecdsa_sign: 1,000 assertions passed 2.79s
- ecdsa_verify: 1,000 assertions passed 3.40s
- ecdh_shared_secret: 2,997 assertions passed 5.84s
Total failed: 0 / 529,825 - ECC cryptography PASSED 18.82s
Delphi XE7-32 ECC_O2
- ecc_make_key: 1,000 assertions passed 2.43s
- ecdsa_sign: 1,000 assertions passed 2.52s
- ecdsa_verify: 1,000 assertions passed 3.06s
- ecdh_shared_secret: 2,997 assertions passed 5.27s
Total failed: 0 / 529,825 - ECC cryptography PASSED 17.12s
Delphi XE7-64 pascal
- ecc_make_key: 1,000 assertions passed 1.55s
- ecdsa_sign: 1,000 assertions passed 1.57s
- ecdsa_verify: 1,000 assertions passed 1.86s
- ecdh_shared_secret: 2,997 assertions passed 3.23s
Total failed: 0 / 529,825 - ECC cryptography PASSED
Delphi XE7-64 ECC_O2
- ecc_make_key: 1,000 assertions passed 729.49ms
- ecdsa_sign: 1,000 assertions passed 740.38ms
- ecdsa_verify: 1,000 assertions passed 914.80ms
- ecdh_shared_secret: 2,997 assertions passed 1.55s
Total failed: 0 / 529,825 - ECC cryptography PASSED
FPC Win64 pascal
- ecc_make_key: 1,000 assertions passed 1.43s
- ecdsa_sign: 1,000 assertions passed 1.45s
- ecdsa_verify: 1,000 assertions passed 1.76s
- ecdh_shared_secret: 2,997 assertions passed 3.03s
Total failed: 0 / 529,825 - ECC cryptography PASSED
FPC Linux x86-64 pascal
- ecc_make_key: 1,000 assertions passed 1.42s
- ecdsa_sign: 1,000 assertions passed 1.48s
- ecdsa_verify: 1,000 assertions passed 1.80s
- ecdh_shared_secret: 2,997 assertions passed 3.11s
Total failed: 0 / 529,825 - ECC cryptography PASSED
}
const
NUM_ECC_DIGITS = ECC_BYTES div 8; // =4
MAX_TRIES = 16;
type // we use UInt64 instead of QWord
TVLI = array[0..NUM_ECC_DIGITS-1] of UInt64;
PVLI = ^TVLI;
TVLI2 = array[0..(2*NUM_ECC_DIGITS)-1] of UInt64;
UInt128 = record
m_low: UInt64;
m_high: UInt64;
end;
TEccPoint = record
x, y: TVLI;
end;
PEccPoint = ^TEccPoint;
const
Curve_P_32: TVLI = (
UInt64($FFFFFFFFFFFFFFFF), UInt64($00000000FFFFFFFF),
UInt64($0000000000000000), UInt64($FFFFFFFF00000001));
Curve_B_32: TVLI = (
UInt64($3BCE3C3E27D2604B), UInt64($651D06B0CC53B0F6),
UInt64($B3EBBD55769886BC), UInt64($5AC635D8AA3A93E7));
Curve_G_32: TEccPoint = (
x: (UInt64($F4A13945D898C296), UInt64($77037D812DEB33A0),
UInt64($F8BCE6E563A440F2), UInt64($6B17D1F2E12C4247));
y: (UInt64($CBB6406837BF51F5), UInt64($2BCE33576B315ECE),
UInt64($8EE7EB4A7C0F9E16), UInt64($4FE342E2FE1A7F9B)));
Curve_N_32: TVLI = (
UInt64($F3B9CAC2FC632551), UInt64($BCE6FAADA7179E84),
UInt64($FFFFFFFFFFFFFFFF), UInt64($FFFFFFFF00000000));
_1: TVLI = (1, 0, 0, 0);
_3: TVLI = (3, 0, 0, 0);
_11: TVLI = (UInt64($0101010101010101), UInt64($0101010101010101),
UInt64($0101010101010101), UInt64($0101010101010101));
procedure _clear(out VLI: TVLI); {$ifdef HASINLINE}inline;{$endif}
begin
VLI[0] := 0;
VLI[1] := 0;
VLI[2] := 0;
VLI[3] := 0;
end;
function _isZero(const VLI: TVLI): boolean; {$ifdef HASINLINE}inline;{$endif}
begin
result := (VLI[0]=0) and (VLI[1]=0) and (VLI[2]=0) and (VLI[3]=0);
end;
// counts the number of bits required for VLI
function _numBits(const VLI: TVLI): integer;
var i: integer;
digit: UInt64;
begin
if VLI[3]<>0 then
result := 4 else
if VLI[2]<>0 then
result := 3 else
if VLI[1]<>0 then
result := 2 else
if VLI[0]<>0 then
result := 1 else begin
result := 0;
exit;
end;
digit := VLI[result-1];
i := 0;
while digit > 0 do begin
inc(i);
digit := digit shr 1;
end;
result := (result-1)*64 + i
end;
// returns sign of Left - Right
function _cmp(const Left, Right: TVLI): integer; {$ifdef HASINLINE}inline;{$endif}
begin
if Left[3] > Right[3] then
result := 1 else
if Left[3] < Right[3] then
result := -1 else
if Left[2] > Right[2] then
result := 1 else
if Left[2] < Right[2] then
result := -1 else
if Left[1] > Right[1] then
result := 1 else
if Left[1] < Right[1] then
result := -1 else
if Left[0] > Right[0] then
result := 1 else
if Left[0] < Right[0] then
result := -1 else
result := 0;
end;
// computes Output = Input shl Shift, returning carry
// can modify in place (if Output == Input). 0 < Shift < 64
function _lshift(var Output: TVLI; const Input: TVLI; Shift: integer): UInt64;
var temp: UInt64;
revShift: integer;
begin
revShift := 64 - Shift;
result := Input[0] shr revShift;
Output[0] := Input[0] shl Shift;
temp := Input[1];
Output[1] := (temp shl Shift) or result;
result := temp shr revShift;
temp := Input[2];
Output[2] := (temp shl Shift) or result;
result := temp shr revShift;
temp := Input[3];
Output[3] := (temp shl Shift) or result;
result := temp shr revShift;
end;
{$ifdef CPU32}
procedure _rshift1(var VLI64: TVLI);
var VLI: TCardinalArray absolute VLI64;
carry, temp: cardinal;
begin
carry := VLI[7] shl 31;
VLI[7] := VLI[7] shr 1;
temp := VLI[6];
VLI[6] := (temp shr 1) or carry;
carry := temp shl 31;
temp := VLI[5];
VLI[5] := (temp shr 1) or carry;
carry := temp shl 31;
temp := VLI[4];
VLI[4] := (temp shr 1) or carry;
carry := temp shl 31;
temp := VLI[3];
VLI[3] := (temp shr 1) or carry;
carry := temp shl 31;
temp := VLI[2];
VLI[2] := (temp shr 1) or carry;
carry := temp shl 31;
temp := VLI[1];
VLI[1] := (temp shr 1) or carry;
carry := temp shl 31;
temp := VLI[0];
VLI[0] := (temp shr 1) or carry;
end;
{$else}
procedure _rshift1(var VLI: TVLI);
{$ifdef HASINLINE}inline;{$endif}
var carry, temp: UInt64;
begin
carry := VLI[3] shl 63;
VLI[3] := VLI[3] shr 1;
temp := VLI[2];
VLI[2] := (temp shr 1) or carry;
carry := temp shl 63;
temp := VLI[1];
VLI[1] := (temp shr 1) or carry;
carry := temp shl 63;
temp := VLI[0];
VLI[0] := (temp shr 1) or carry;
end;
{$endif}
{$ifdef CPU32}
function _lshift1(var VLI64: TVLI): cardinal;
var VLI: TCardinalArray absolute VLI64;
temp: cardinal;
begin
result := VLI[0] shr 31;
VLI[0] := VLI[0] shl 1;
temp := VLI[1];
VLI[1] := (temp shl 1) or result;
result := temp shr 31;
temp := VLI[2];
VLI[2] := (temp shl 1) or result;
result := temp shr 31;
temp := VLI[3];
VLI[3] := (temp shl 1) or result;
result := temp shr 31;
temp := VLI[4];
VLI[4] := (temp shl 1) or result;
result := temp shr 31;
temp := VLI[5];
VLI[5] := (temp shl 1) or result;
result := temp shr 31;
temp := VLI[6];
VLI[6] := (temp shl 1) or result;
result := temp shr 31;
temp := VLI[7];
VLI[7] := (temp shl 1) or result;
result := temp shr 31;
end;
{$else}
function _lshift1(var VLI: TVLI): UInt64;
{$ifdef HASINLINE}inline;{$endif}
var temp: UInt64;
begin
result := VLI[0] shr 63;
VLI[0] := VLI[0] shl 1;
temp := VLI[1];
VLI[1] := (temp shl 1) or result;
result := temp shr 63;
temp := VLI[2];
VLI[2] := (temp shl 1) or result;
result := temp shr 63;
temp := VLI[3];
VLI[3] := (temp shl 1) or result;
result := temp shr 63;
end;
{$endif}
// computes Output = Left + Right, returning carry. Can modify in place
{$ifdef CPU32}
function _add(var Output64: TVLI; const Left64, Right64: TVLI): PtrUInt;
var Output: TCardinalArray absolute Output64;
Left: TCardinalArray absolute Left64;
Right: TCardinalArray absolute Right64;
{$else}
function _add(var Output: TVLI; const Left, Right: TVLI): PtrUInt;
{$ifdef HASINLINE}inline;{$endif}
{$endif}
var sum: PtrUInt;
begin
result := 0;
sum := Left[0] + Right[0];
if sum <> Left[0] then
if sum < Left[0] then
result := 1;
Output[0] := sum;
sum := Left[1] + Right[1] + result;
if sum <> Left[1] then
if sum < Left[1] then
result := 1 else
result := 0;
Output[1] := sum;
sum := Left[2] + Right[2] + result;
if sum <> Left[2] then
if sum < Left[2] then
result := 1 else
result := 0;
Output[2] := sum;
sum := Left[3] + Right[3] + result;
if sum <> Left[3] then
if sum < Left[3] then
result := 1 else
result := 0;
Output[3] := sum;
{$ifdef CPU32}
sum := Left[4] + Right[4] + result;
if sum <> Left[4] then
if sum < Left[4] then
result := 1 else
result := 0;
Output[4] := sum;
sum := Left[5] + Right[5] + result;
if sum <> Left[5] then
if sum < Left[5] then
result := 1 else
result := 0;
Output[5] := sum;
sum := Left[6] + Right[6] + result;
if sum <> Left[6] then
if sum < Left[6] then
result := 1 else
result := 0;
Output[6] := sum;
sum := Left[7] + Right[7] + result;
if sum <> Left[7] then
if sum < Left[7] then
result := 1 else
result := 0;
Output[7] := sum;
{$endif}
end;
// computes Output = Left + Right, returning borrow. Can modify in place.
{$ifdef CPU32}
function _sub(var Output64: TVLI; const Left64, Right64: TVLI): PtrUInt;
var Output: TCardinalArray absolute Output64;
Left: TCardinalArray absolute Left64;
Right: TCardinalArray absolute Right64;
{$else}
function _sub(var Output: TVLI; const Left, Right: TVLI): PtrUInt;
{$ifdef HASINLINE}inline;{$endif}
{$endif}
var diff: PtrUInt;
begin
result := 0;
diff := Left[0] - Right[0] - result;
if diff <> Left[0] then
if diff > Left[0] then
result := 1;
Output[0] := diff;
diff := Left[1] - Right[1] - result;
if diff <> Left[1] then
if diff > Left[1] then
result := 1 else
result := 0;
Output[1] := diff;
diff := Left[2] - Right[2] - result;
if diff <> Left[2] then
if diff > Left[2] then
result := 1 else
result := 0;
Output[2] := diff;
diff := Left[3] - Right[3] - result;
if diff <> Left[3] then
if diff > Left[3] then
result := 1 else
result := 0;
Output[3] := diff;
{$ifdef CPU32}
diff := Left[4] - Right[4] - result;
if diff <> Left[4] then
if diff > Left[4] then
result := 1 else
result := 0;
Output[4] := diff;
diff := Left[5] - Right[5] - result;
if diff <> Left[5] then
if diff > Left[5] then
result := 1 else
result := 0;
Output[5] := diff;
diff := Left[6] - Right[6] - result;
if diff <> Left[6] then
if diff > Left[6] then
result := 1 else
result := 0;
Output[6] := diff;
diff := Left[7] - Right[7] - result;
if diff <> Left[7] then
if diff > Left[7] then
result := 1 else
result := 0;
Output[7] := diff;
{$endif}
end;
procedure _mult(out Output: TVLI2; const Left, Right: TVLI);
var i, k, l_min: integer;
Product, r01: UInt128;
carry, prev: UInt64;
l, r: ^UInt64;
begin
r01.m_low := 0;
r01.m_high := 0;
carry := 0;
// Compute each digit of Output in sequence, maintaining the carries
for k := 0 to 2 * NUM_ECC_DIGITS - 2 do begin
if k < NUM_ECC_DIGITS then
l_min := 0 else
l_min := (k + 1) - NUM_ECC_DIGITS;
l := @Left[l_min];
r := @Right[k-l_min];
for i := l_min to k do begin
if i >= NUM_ECC_DIGITS then
break;
mul64x64(l^, r^, THash128Rec(Product));
prev := r01.m_low;
inc(r01.m_low, Product.m_low);
inc(r01.m_high, Product.m_high);
if r01.m_low < prev then
inc(r01.m_high);
if r01.m_high < Product.m_high then
inc(carry);
inc(l);
dec(r);
end;
Output[k] := r01.m_low;
r01.m_low := r01.m_high;
r01.m_high := carry;
carry := 0;
end;
Output[NUM_ECC_DIGITS * 2 - 1] := r01.m_low;
end;
procedure _square(out Output: TVLI2; const Left: TVLI);
var i, k, l_min: integer;
Product, r01: UInt128;
carry, prev: UInt64;
begin
r01.m_low := 0;
r01.m_high := 0;
carry := 0;
for k := 0 to 2 * NUM_ECC_DIGITS - 2 do begin
if k < NUM_ECC_DIGITS then
l_min := 0 else
l_min := (k + 1) - NUM_ECC_DIGITS;
for i := l_min to k do begin
if i > k-i then
break;
mul64x64(Left[i], Left[k-i], THash128Rec(Product));
if i < k-i then begin
inc(carry, Product.m_high shr 63);
Product.m_high := (Product.m_high shl 1) or (Product.m_low shr 63);
Product.m_low := Product.m_low shl 1;
end;
prev := r01.m_low;
inc(r01.m_low, Product.m_low);
inc(r01.m_high, Product.m_high);
if r01.m_low < prev then
inc(r01.m_high);
if r01.m_high < Product.m_high then
inc(carry);
end;
Output[k] := r01.m_low;
r01.m_low := r01.m_high;
r01.m_high := carry;
carry := 0;
end;
Output[NUM_ECC_DIGITS*2-1] := r01.m_low;
end;
// computes result = (Left + Right) mod Modulo
// assumes that p_left < p_mod and p_right < p_mod, p_result != p_mod
procedure _modAdd(var Output: TVLI; const Left, Right, Modulo: TVLI); {$ifdef HASINLINE}inline;{$endif}
begin
if (_add(Output, Left, Right) <> 0) or (_cmp(Output, Modulo) >= 0) then
// result > Modulo (result = Modulo + Remainder), so subtract Modulo to get remainder
_sub(Output, Output, Modulo);
end;
// computes result = (Left - Right) mod Modulo.
// assumes that Left < Modulo and Right < Modulo, result != Modulo
procedure _modSub(out Output: TVLI; const Left, Right, Modulo: TVLI); {$ifdef HASINLINE}inline;{$endif}
begin
if _sub(Output, Left, Right) > 0 then
// In this case, Output == -diff == (max int) - diff.
// Since -x mod d == d - x, we can get the correct result from Output + Modulo (with overflow)
_add(Output, Output, Modulo);
end;
// computes result = Product mod Curve
// from http://www.nsa.gov/ia/_files/nist-routines.pdf
procedure _mmod_fast(out Output: TVLI; var p_product: TVLI2);
var carry: integer;
l_tmp: TVLI;
begin
// t
Output := PVLI(@p_product)^;
// s1
l_tmp[0] := 0;
l_tmp[1] := p_product[5] and $FFFFFFFF00000000;
l_tmp[2] := p_product[6];
l_tmp[3] := p_product[7];
carry := _lshift1(l_tmp);
inc(carry, _add(Output, Output, l_tmp));
// s2
l_tmp[1] := p_product[6] shl 32;
l_tmp[2] := (p_product[6] shr 32) or (p_product[7] shl 32);
l_tmp[3] := p_product[7] shr 32;
inc(carry, _lshift1(l_tmp));
inc(carry, _add(Output, Output, l_tmp));
// s3
l_tmp[0] := p_product[4];
l_tmp[1] := p_product[5] and $FFFFFFFF;
l_tmp[2] := 0;
l_tmp[3] := p_product[7];
inc(carry, _add(Output, Output, l_tmp));
// s4
l_tmp[0] := (p_product[4] shr 32) or (p_product[5] shl 32);
l_tmp[1] := (p_product[5] shr 32) or (p_product[6] and $FFFFFFFF00000000);
l_tmp[2] := p_product[7];
l_tmp[3] := (p_product[6] shr 32) or (p_product[4] shl 32);
inc(carry, _add(Output, Output, l_tmp));
// d1
l_tmp[0] := (p_product[5] shr 32) or (p_product[6] shl 32);
l_tmp[1] := (p_product[6] shr 32);
l_tmp[2] := 0;
l_tmp[3] := (p_product[4] and $FFFFFFFF) or (p_product[5] shl 32);
dec(carry, _sub(Output, Output, l_tmp));
// d2
l_tmp[0] := p_product[6];
l_tmp[1] := p_product[7];
l_tmp[2] := 0;
l_tmp[3] := (p_product[4] shr 32) or (p_product[5] and $FFFFFFFF00000000);
dec(carry, _sub(Output, Output, l_tmp));
// d3
l_tmp[0] := (p_product[6] shr 32) or (p_product[7] shl 32);
l_tmp[1] := (p_product[7] shr 32) or (p_product[4] shl 32);
l_tmp[2] := (p_product[4] shr 32) or (p_product[5] shl 32);
l_tmp[3] := (p_product[6] shl 32);
dec(carry, _sub(Output, Output, l_tmp));
// d4
l_tmp[0] := p_product[7];
l_tmp[1] := p_product[4] and $FFFFFFFF00000000;
l_tmp[2] := p_product[5];
l_tmp[3] := p_product[6] and $FFFFFFFF00000000;
dec(carry, _sub(Output, Output, l_tmp));
if carry < 0 then
repeat
inc(carry, _add(Output, Output, Curve_P_32));
until carry >= 0 else
while (carry <> 0) or (_cmp(Curve_P_32, Output) <> 1) do
dec(carry, _sub(Output, Output, Curve_P_32));
end;
// computes result = (Left * Right) mod Curve
procedure _modMult_fast(out Output: TVLI; const Left, Right: TVLI); {$ifdef HASINLINE}inline;{$endif}
var Product: TVLI2;
begin
_mult(Product, Left, Right);
_mmod_fast(Output, Product);
end;
// computes result = Left^2 mod Curve
procedure _modSquare_fast(out Output: TVLI; const Left: TVLI; const Debug: boolean); {$ifdef HASINLINE}inline;{$endif}
var Product: TVLI2;
begin
_square(Product, Left);
_mmod_fast(Output, Product);
end;
// computes result = (1 / p_input) mod Modulo. All VLIs are the same size
// See "From Euclid's GCD to Montgomery Multiplication to the Great Divide"
// https://labs.oracle.com/techrep/2001/smli_tr-2001-95.pdf
procedure _modInv(out Output: TVLI; const Input, Modulo: TVLI);
var a, b, u, v: TVLI;
carry: PtrUInt;
cmp: integer;
begin
if _isZero(Input) then begin
_clear(Output);
exit;
end;
a := Input;
b := Modulo;
u := _1;
_clear(v);
while true do begin
cmp := _cmp(a, b);
if cmp = 0 then
break;
carry := 0;
if (byte(a[0]) and 1) = 0 then begin
_rshift1(a);
if (byte(u[0]) and 1) = 1 then
carry := _add(u, u, Modulo);
_rshift1(u);
if carry <> 0 then
THash256(u)[ECC_BYTES-1] := THash256(u)[ECC_BYTES-1] or $80;
end else
if (byte(b[0]) and 1) = 0 then begin
_rshift1(b);
if (byte(v[0]) and 1) = 1 then
carry := _add(v, v, Modulo);
_rshift1(v);
if carry <> 0 then
THash256(v)[ECC_BYTES-1] := THash256(v)[ECC_BYTES-1] or $80;
end else
if cmp > 0 then begin
_sub(a, a, b);
_rshift1(a);
if _cmp(u, v) < 0 then
_add(u, u, Modulo);
_sub(u, u, v);
if (byte(u[0]) and 1) = 1 then
carry := _add(u, u, Modulo);
_rshift1(u);
if carry <> 0 then
THash256(u)[ECC_BYTES-1] := THash256(u)[ECC_BYTES-1] or $80;
end else begin
_sub(b, b, a);
_rshift1(b);
if _cmp(v, u) < 0 then
_add(v, v, Modulo);
_sub(v, v, u);
if (byte(v[0]) and 1) = 1 then
carry := _add(v, v, Modulo);
_rshift1(v);
if carry > 0 then
THash256(v)[ECC_BYTES-1] := THash256(v)[ECC_BYTES-1] or $80;
end;
end;
Output := u;
end;
// Point multiplication algorithm using Montgomery's ladder with co-Z coordinates.
// From http://eprint.iacr.org/2011/338.pdf
// Double in place
procedure EccPointDoubleJacobian(var X1, Y1, Z1: TVLI);
var carry: UInt64;
t4, t5: TVLI;
begin
// t1 = X, t2 = Y, t3 = Z
if _isZero(Z1) then
exit;
_modSquare_fast(t4, Y1, false); // t4 = y1^2
_modMult_fast(t5, X1, t4); // t5 = x1*y1^2 = A
_modSquare_fast(t4, t4, false); // t4 = y1^4
_modMult_fast(Y1, Y1, Z1); // t2 = y1*z1 = z3
_modSquare_fast(Z1, Z1, false); // t3 = z1^2
_modAdd(X1, X1, Z1, Curve_P_32); // t1 = x1 + z1^2
_modAdd(Z1, Z1, Z1, Curve_P_32); // t3 = 2*z1^2
_modSub(Z1, X1, Z1, Curve_P_32); // t3 = x1 - z1^2
_modMult_fast(X1, X1, Z1); // t1 = x1^2 - z1^4
_modAdd(Z1, X1, X1, Curve_P_32); // t3 = 2*(x1^2 - z1^4)
_modAdd(X1, X1, Z1, Curve_P_32); // t1 = 3*(x1^2 - z1^4)
if GetBit(X1, 0) then begin
carry := _add(X1, X1, Curve_P_32);
_rshift1(X1);
X1[NUM_ECC_DIGITS-1] := X1[NUM_ECC_DIGITS-1] or (carry shl 63);
end else
_rshift1(X1);
// t1 = 3/2*(x1^2 - z1^4) = B
_modSquare_fast(Z1, X1, false); // t3 = B^2
_modSub(Z1, Z1, t5, Curve_P_32); // t3 = B^2 - A
_modSub(Z1, Z1, t5, Curve_P_32); // t3 = B^2 - 2A = x3
_modSub(t5, t5, Z1, Curve_P_32); // t5 = A - x3
_modMult_fast(X1, X1, t5); // t1 = B * (A - x3)
_modSub(t4, X1, t4, Curve_P_32); // t4 = B * (A - x3) - y1^4 = y3
X1 := Z1;
Z1 := Y1;
Y1 := t4;
end;
// Modify (x1, y1) => (x1 * z^2, y1 * z^3)
procedure _apply_z(var X1, Y1, Z: TVLI);
var t1: TVLI;
begin
_modSquare_fast(t1, Z, false); // z^2
_modMult_fast(X1, X1, t1); // x1 * z^2
_modMult_fast(t1, t1, Z); // z^3
_modMult_fast(Y1, Y1, t1); // y1 * z^3
end;
// P = (x1, y1) => 2P, (x2, y2) => P'
procedure _XYcZ_initial_double(var X1, Y1, X2, Y2: TVLI; InitialZ: PVLI);
var z: TVLI;
begin
X2 := X1;
Y2 := Y1;
if InitialZ <> nil then
z := InitialZ^ else
z := _1;
_apply_z(X1, Y1, z);
EccPointDoubleJacobian(X1, Y1, z);
_apply_z(X2, Y2, z);
end;
// Input P = (x1, y1, Z), Q = (x2, y2, Z)
// Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3)
// or P => P', Q => P + Q
procedure _XYcZ_add(var X1, Y1, X2, Y2: TVLI);
var t5: TVLI;
begin
// t1 = X1, t2 = Y1, t3 = X2, t4 = Y2
_modSub(t5, X2, X1, Curve_P_32); // t5 = x2 - x1
_modSquare_fast(t5, t5, false); // t5 = (x2 - x1)^2 = A
_modMult_fast(X1, X1, t5); // t1 = x1*A = B
_modMult_fast(X2, X2, t5); // t3 = x2*A = C
_modSub(Y2, Y2, Y1, Curve_P_32); // t4 = y2 - y1
_modSquare_fast(t5, Y2, false); // t5 = (y2 - y1)^2 = D
_modSub(t5, t5, X1, Curve_P_32); // t5 = D - B
_modSub(t5, t5, X2, Curve_P_32); // t5 = D - B - C = x3
_modSub(X2, X2, X1, Curve_P_32); // t3 = C - B
_modMult_fast(Y1, Y1, X2); // t2 = y1*(C - B)
_modSub(X2, X1, t5, Curve_P_32); // t3 = B - x3
_modMult_fast(Y2, Y2, X2); // t4 = (y2 - y1)*(B - x3)
_modSub(Y2, Y2, Y1, Curve_P_32); // t4 = y3
X2 := t5;
end;
// Input P = (x1, y1, Z), Q = (x2, y2, Z)
// Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3)
// or P => P - Q, Q => P + Q
procedure _XYcZ_addC(var X1, Y1, X2, Y2: TVLI);
var t5, t6, t7: TVLI;
begin
// t1 = X1, t2 = Y1, t3 = X2, t4 = Y2
_modSub(t5, X2, X1, Curve_P_32); // t5 = x2 - x1
_modSquare_fast(t5, t5, false); // t5 = (x2 - x1)^2 = A
_modMult_fast(X1, X1, t5); // t1 = x1*A = B
_modMult_fast(X2, X2, t5); // t3 = x2*A = C
_modAdd(t5, Y2, Y1, Curve_P_32); // t4 = y2 + y1
_modSub(Y2, Y2, Y1, Curve_P_32); // t4 = y2 - y1
_modSub(t6, X2, X1, Curve_P_32); // t6 = C - B
_modMult_fast(Y1, Y1, t6); // t2 = y1 * (C - B)
_modAdd(t6, X1, X2, Curve_P_32); // t6 = B + C
_modSquare_fast(X2, Y2, false); // t3 = (y2 - y1)^2
_modSub(X2, X2, t6, Curve_P_32); // t3 = x3
_modSub(t7, X1, X2, Curve_P_32); // t7 = B - x3
_modMult_fast(Y2, Y2, t7); // t4 = (y2 - y1)*(B - x3)
_modSub(Y2, Y2, Y1, Curve_P_32); // t4 = y3
_modSquare_fast(t7, t5, false); // t7 = (y2 + y1)^2 = F
_modSub(t7, t7, t6, Curve_P_32); // t7 = x3'
_modSub(t6, t7, X1, Curve_P_32); // t6 = x3' - B
_modMult_fast(t6, t6, t5); // t6 = (y2 + y1)*(x3' - B)
_modSub(Y1, t6, Y1, Curve_P_32); // t2 = y3'
X1 := t7;
end;
procedure EccPointMult(out Output: TEccPoint; const Point: TEccPoint; Scalar: TVLI; InitialZ: PVLI);
var Rx, Ry: array[0..1] of TVLI;
z: TVLI;
i, nb: integer;
begin
// R0 and R1
Rx[1] := Point.x;
Ry[1] := Point.y;
_XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], InitialZ);
for i := _numBits(Scalar) - 2 downto 1 do begin
if GetBit(Scalar, i) then
nb := 0 else
nb := 1;
_XYcZ_addC(Rx[1-nb], Ry[1-nb], Rx[nb], Ry[nb]);
_XYcZ_add(Rx[nb], Ry[nb], Rx[1-nb], Ry[1-nb]);
end;
if GetBit(Scalar, 0) then
nb := 0 else
nb := 1;
_XYcZ_addC(Rx[1-nb], Ry[1-nb], Rx[nb], Ry[nb]);
// Find final 1/Z value
_modSub(z, Rx[1], Rx[0], Curve_P_32); // X1 - X0
_modMult_fast(z, z, Ry[1-nb]); // Yb * (X1 - X0)
_modMult_fast(z, z, Point.x); // xP * Yb * (X1 - X0)
_modInv(z, z, Curve_P_32); // 1 / (xP * Yb * (X1 - X0))
_modMult_fast(z, z, Point.y); // yP / (xP * Yb * (X1 - X0))
_modMult_fast(z, z, Rx[1-nb]); // Xb * yP / (xP * Yb * (X1 - X0))
// End 1/Z calculation
_XYcZ_add(Rx[nb], Ry[nb], Rx[1-nb], Ry[1-nb]);
_apply_z(Rx[0], Ry[0], z);
Output.x := Rx[0];
Output.y := Ry[0];
end;
procedure _bswap256(dest, source: PQWordArray);
begin
dest[0] := bswap64(source[3]);
dest[1] := bswap64(source[2]);
dest[2] := bswap64(source[1]);
dest[3] := bswap64(source[0]);
end;
// Compute a = sqrt(a) (mod curve_p)
procedure ModSqrt(var a: TVLI);
var i: integer;
p1, result: TVLI;
begin
result := _1;
// Since curve_p == 3 (mod 4) for all supported curves, we can compute
// sqrt(a) = a^((curve_p + 1) / 4) (mod curve_p)
_add(p1, Curve_P_32, _1); // p1 = curve_p + 1
for i := _numBits(p1) - 1 downto 2 do begin
_modSquare_fast(result, result, false);
if GetBit(p1, i) then
_modMult_fast(result, result, a);
end;
a := result;
end;
procedure EccPointDecompress(out Point: TEccPoint; Compressed: PByteArray);
begin
_bswap256(@Point.x, @Compressed[1]);
_modSquare_fast(Point.y, Point.x, false); // y = x^2
_modSub(Point.y, Point.y, _3, Curve_P_32); // y = x^2 - 3
_modMult_fast(Point.y, Point.y, Point.x); // y = x^3 - 3x
_modAdd(Point.y, Point.y, Curve_B_32, Curve_P_32); // y = x^3 - 3x + b
ModSqrt(Point.y);
if (Point.y[0] and $01) <> (Compressed[0] and $01) then
_sub(Point.y, Curve_P_32, Point.y);
end;
function ecc_make_key_pas(out PublicKey: TECCPublicKey; out PrivateKey: TECCPrivateKey): boolean;
var PrivateK: TVLI;
PublicPoint: TEccPoint;
tries: integer;
begin
result := false;
tries := 0;
repeat
inc(tries);
TAESPRNG.Fill(THash256(PrivateK));
if tries >= MAX_TRIES then
exit;
if _isZero(PrivateK) or (_cmp(PrivateK, _1) = 0) or (_cmp(PrivateK, _11) = 0) then
continue;
// Make sure the private key is in the range [1, n-1]
// For the supported curves, n is always large enough that we only need
// to subtract once at most
if _cmp(Curve_N_32, PrivateK) <> 1 then
_sub(PrivateK, PrivateK, Curve_N_32);
EccPointMult(PublicPoint, Curve_G_32, PrivateK, nil);
until not (_isZero(PublicPoint.x) and _isZero(PublicPoint.y));
_bswap256(@PrivateKey, @PrivateK);
_bswap256(@PublicKey[1], @PublicPoint.x);
PublicKey[0] := 2 + (PublicPoint.y[0] and $01);
result := true;
_clear(PrivateK); // erase sensitive information from stack
_clear(PublicPoint.x);
_clear(PublicPoint.y);
end;
function ecdh_shared_secret_pas(const PublicPoint: TECCPublicKeyUncompressed;
const PrivateKey: TECCPrivateKey; out Secret: TEccSecretKey): boolean;
var
PrivateK: TVLI;
Product: TEccPoint;
RandomKey: TVLI;
begin
TAESPRNG.Fill(THash256(RandomKey));
_bswap256(@PrivateK, @PrivateKey);
EccPointMult(Product, TEccPoint(PublicPoint), PrivateK, @RandomKey);
_bswap256(@Secret, @Product.x);
result := not (_isZero(Product.x) and _isZero(Product.y));
_clear(Product.x); // erase sensitive information from stack
_clear(Product.y);
_clear(PrivateK);
_clear(RandomKey);
end;
procedure ecc_uncompress_key_pas(const Compressed: TECCPublicKey;
out Uncompressed: TECCPublicKeyUncompressed);
begin
EccPointDecompress(TEccPoint(Uncompressed), @Compressed);
end;
function ecdh_shared_secret_pas(const PublicKey: TECCPublicKey;
const PrivateKey: TECCPrivateKey; out Secret: TEccSecretKey): boolean;
var PublicPoint: TECCPublicKeyUncompressed;
begin
EccPointDecompress(TEccPoint(PublicPoint), @PublicKey);
result := ecdh_shared_secret_pas(PublicPoint, PrivateKey, Secret);
end;
// computes result = (Left * Right) mod Modulo
procedure _modMult(out Output: TVLI; const Left, Right, Modulo: TVLI);
var carry: UInt64;
cmp: integer;
ModMultiple, Product: TVLI2;
ModMultipleVLI_Lo, ModMultipleVLI_Hi, ProductVLI_Lo, ProductVLI_Hi: PVLI;
DigitShift, BitShift, ProductBits, ModBits: integer;
VLI: PVLI;
begin
ModBits := _numBits(Modulo);
_mult(Product, Left, Right);
ProductVLI_Hi := @Product[NUM_ECC_DIGITS];
ProductVLI_Lo := @Product;
ProductBits := _numBits(ProductVLI_Hi^);
if ProductBits > 0 then
ProductBits := ProductBits + NUM_ECC_DIGITS*64 else
ProductBits := _numBits(ProductVLI_Lo^);
if ProductBits < ModBits then begin // l_product < p_mod
Output := PVLI(@Product)^;
exit;
end;
// Shift p_mod by (LeftBits - ModBits). This multiplies p_mod by the largest
// power of two possible while still resulting in a number less than p_left
ModMultipleVLI_Lo := @ModMultiple;
ModMultipleVLI_Hi := @ModMultiple[NUM_ECC_DIGITS];
_clear(ModMultipleVLI_Lo^);
_clear(ModMultipleVLI_Hi^);
DigitShift := (ProductBits-ModBits) shr 6;
BitShift := (ProductBits-ModBits) and 63;
if BitShift > 0 then begin
VLI := @ModMultiple[DigitShift];
ModMultiple[DigitShift+NUM_ECC_DIGITS] := _lshift(VLI^, Modulo, BitShift)
end else begin
VLI := @ModMultiple[DigitShift];
VLI^ := Modulo;
end;
// Subtract all multiples of Modulo to get the remainder
while (ProductBits > NUM_ECC_DIGITS*64) or (_cmp(ModMultipleVLI_Lo^, Modulo) >= 0) do begin
cmp := _cmp(ModMultipleVLI_Hi^, ProductVLI_Hi^);
if (cmp < 0) or ((cmp = 0) and (_cmp(ModMultipleVLI_Lo^, ProductVLI_Lo^) <= 0)) then begin
if _sub(ProductVLI_Lo^, ProductVLI_Lo^, ModMultipleVLI_Lo^) > 0 then
_sub(ProductVLI_Hi^, ProductVLI_Hi^, _1); // borrow
_sub(ProductVLI_Hi^, ProductVLI_Hi^, ModMultipleVLI_Hi^);
end;
carry := (ModMultiple[NUM_ECC_DIGITS] and $01) shl 63;
_rshift1(ModMultipleVLI_Hi^);
_rshift1(ModMultipleVLI_Lo^);
if carry > 0 then
ModMultiple[NUM_ECC_DIGITS-1] := ModMultiple[NUM_ECC_DIGITS-1] or carry;
dec(ProductBits);
end;
Output := PVLI(@Product)^;
end;
function ecdsa_sign_pas(const PrivateKey: TECCPrivateKey; const Hash: TEccHash;
out Signature: TECCSignature): boolean;
var k, Temp, S: TVLI;
P: TEccPoint;
Tries: integer;
begin
result := false;
Tries := 0;
repeat
inc(Tries);
TAESPRNG.Fill(THash256(k));
if Tries >= MAX_TRIES then
exit;
if _isZero(k) or (_cmp(k, _1) = 0) or (_cmp(k, _11) = 0) then
continue;
if _cmp(Curve_N_32, k) <> 1 then
_sub(k, k, Curve_N_32);
// Temp = k * G
EccPointMult(P, Curve_G_32, k, nil);
// r = x1 (mod n)
if _cmp(Curve_N_32, P.x) <> 1 then
_sub(P.x, P.x, Curve_N_32);
until not _isZero(p.x);
_bswap256(@Signature, @P.x);
_bswap256(@Temp, @PrivateKey);
_modMult(S, P.x, Temp, Curve_N_32); // s = r*d
_bswap256(@Temp, @Hash);
_modAdd(S, Temp, S, Curve_N_32); // s = e + r*d
_modInv(k, k, Curve_N_32); // k = 1 / k
_modMult(S, S, k, Curve_N_32); // s = (e + r*d) / k
_bswap256(@Signature[ECC_BYTES], @S);
result := true;
end;
function ecdsa_verify_pas(const PublicKey: TECCPublicKeyUncompressed;
const Hash: TECCHash; const Signature: TECCSignature): boolean;
var i, Index, NumBits: integer;
PublicPoint: TEccPoint absolute PublicKey;
SumPoint: TEccPoint;
Point: PEccPoint;
Points: array[0..3] of PEccPoint;
rx, ry, tx, ty, tz, l_r, l_s, u1, u2, z: TVLI;
begin
result := false;
_bswap256(@l_r, @Signature);
_bswap256(@l_s, @Signature[ECC_BYTES]);
if _isZero(l_r) or _isZero(l_s) or
(_cmp(Curve_N_32, l_r) <> 1) or (_cmp(Curve_N_32, l_s) <> 1) then
exit; // r, s must be <> 0 and < n
// calculate u1 and u2
_modInv(z, l_s, Curve_N_32); // Z = s^-1
_bswap256(@u1, @Hash);
_modMult(u1, u1, z, Curve_N_32); // u1 = e/s
_modMult(u2, l_r, z, Curve_N_32); // u2 = r/s
// calculate l_sum = G + Q
SumPoint.x := PublicPoint.x;
SumPoint.y := PublicPoint.y;
tx := Curve_G_32.x;
ty := Curve_G_32.y;
_modSub(z, SumPoint.x, tx, Curve_P_32); // Z = x2 - x1
_XYcZ_add(tx, ty, SumPoint.x, SumPoint.y);
_modInv(z, z, Curve_P_32); // Z = 1/Z
_apply_z(SumPoint.x, SumPoint.y, z);
// use Shamir's trick to calculate u1*G + u2*Q
Points[0] := nil;
Points[1] := @Curve_G_32;
Points[2] := @PublicPoint;
Points[3] := @SumPoint;
NumBits := _numBits(u1);
Index := _numBits(u2);
if Index > NumBits then
NumBits := Index;
if GetBit(u1, NumBits-1) then
Index := 1 else
Index := 0;
if GetBit(u2, NumBits-1) then
inc(Index, 2);
Point := Points[Index];
rx := Point.x;
ry := Point.y;
z := _1;
for i := NumBits - 2 downto 0 do begin
EccPointDoubleJacobian(rx, ry, z);
if GetBit(u1, i) then
Index := 1 else
Index := 0;
if GetBit(u2, i) then
inc(Index, 2);
Point := Points[Index];
if Point <> nil then begin
tx := Point.x;
ty := Point.y;
_apply_z(tx, ty, z);
_modSub(tz, rx, tx, Curve_P_32); // Z = x2 - x1
_XYcZ_add(tx, ty, rx, ry);
_modMult_fast(z, z, tz);
end;
end;
_modInv(z, z, Curve_P_32); // Z = 1/Z
_apply_z(rx, ry, z);
// v = x1 (mod n)
if _cmp(Curve_N_32, rx) <> 1 then
_sub(rx, rx, Curve_N_32);
result := IsEqual(THash256(rx), THash256(l_r)); // Accept only if v == r
end;
function ecdsa_verify_pas(const PublicKey: TECCPublicKey; const Hash: TEccHash;
const Signature: TECCSignature): boolean;
var PublicPoint: TECCPublicKeyUncompressed;
begin
EccPointDecompress(TEccPoint(PublicPoint), @PublicKey);
result := ecdsa_verify_pas(PublicPoint, Hash, Signature);
end;
{$else}
function ecc_make_key_pas(out PublicKey: TECCPublicKey; out PrivateKey: TECCPrivateKey): boolean;
begin
result := false; // we need proper UInt64 support at compiler level
end;
function ecdh_shared_secret_pas(const PublicKey: TECCPublicKey;
const PrivateKey: TECCPrivateKey; out Secret: TECCSecretKey): boolean;
begin
result := false; // we need proper UInt64 support at compiler level
end;
function ecdh_shared_secret_pas(const PublicPoint: TECCPublicKeyUncompressed;
const PrivateKey: TECCPrivateKey; out Secret: TEccSecretKey): boolean;
begin
result := false; // we need proper UInt64 support at compiler level
end;
procedure ecc_uncompress_key_pas(const Compressed: TECCPublicKey;
out Uncompressed: TECCPublicKeyUncompressed);
begin
FillZero(THash512(Uncompressed));
end;
function ecdsa_sign_pas(const PrivateKey: TECCPrivateKey; const Hash: TECCHash;
out Signature: TECCSignature): boolean;
begin
result := false; // we need proper UInt64 support at compiler level
end;
function ecdsa_verify_pas(const PublicKey: TECCPublicKey; const Hash: TECCHash;
const Signature: TECCSignature): boolean;
begin
result := false; // we need proper UInt64 support at compiler level
end;
function ecdsa_verify_pas(const PublicKey: TECCPublicKeyUncompressed;
const Hash: TEccHash; const Signature: TECCSignature): boolean;
begin
result := false; // we need proper UInt64 support at compiler level
end;
{$endif HASUINT64}
{ *********** middle-level certificate-based public-key cryptography *********** }
procedure FillZero(out Priv: TECCPrivateKey); overload;
begin
PInt64Array(@Priv)^[0] := 0;
PInt64Array(@Priv)^[1] := 0;
PInt64Array(@Priv)^[2] := 0;
PInt64Array(@Priv)^[3] := 0;
end;
const
// Mon, 01 Aug 2016 encoded as COM/TDateTime value
ECC_DELTA = 42583;
function NowECCDate: TECCDate;
begin
result := Trunc(NowUTC) - ECC_DELTA;
end;
function ECCDate(const DateTime: TDateTime): TECCDate;
var now: integer;
begin
if DateTime=0 then
result := 0 else begin
now := Trunc(DateTime) - ECC_DELTA;
if cardinal(now)>high(TECCDate) then
result := 0 else
result := now;
end;
end;
function ECCToDateTime(ECCDate: TECCDate): TDateTime;
begin
if ECCDate=0 then
result := 0 else
result := ECCDate + ECC_DELTA;
end;
function ECCText(ECCDate: TECCDate; Expanded: boolean): RawUTF8;
begin
if ECCDate=0 then
result := '' else
result := DateToIso8601(ECCDate + ECC_DELTA, Expanded);
end;
function ECCText(const Issuer: TECCCertificateIssuer): RawUTF8;
var tmp: array[0..sizeof(Issuer)] of byte;
begin
if IsZero(Issuer) then
result := '' else begin
PAESBlock(@tmp)^ := TAESBlock(Issuer);
tmp[sizeof(Issuer)] := 0; // add a trailing #0 as expected for trailing bits
result := BaudotToAscii(@tmp,sizeof(Issuer));
if result='' then
result := SynCommons.BinToHex(@Issuer,sizeof(Issuer));
end;
end;
function ECCIssuer(const Text: RawUTF8; out Issuer: TECCCertificateIssuer): boolean;
var baudot: RawByteString;
len: integer;
begin
FillZero(THash128(Issuer));
baudot := AsciiToBaudot(Text);
len := length(baudot);
result := len>sizeof(Issuer);
if result then // truncated
len := sizeof(Issuer);
MoveFast(pointer(baudot)^,Issuer,len);
end;
function ECCText(const ID: TECCCertificateID): RawUTF8;
begin
if IsZero(ID) then
result := '' else
result := AESBlockToString(TAESBlock(ID));
end;
function ECCID(const Text: RawUTF8; out ID: TECCCertificateID): boolean;
begin
if length(Text)<>sizeof(ID)*2 then
result := false else
result := SynCommons.HexToBin(pointer(Text),@ID,sizeof(ID));
end;
function ECCCheck(const content: TECCCertificateContent): boolean;
begin
with content.Signed do
if (IssueDate=0) or (IssueDate=65535) or IsZero(Serial) or IsZero(Issuer) or
IsZero(AuthoritySerial) or IsZero(AuthorityIssuer) or
IsZero(@PublicKey,sizeof(PublicKey)) or
IsZero(@content.Signature,sizeof(content.Signature)) then
result := false else
result := (content.Version in [1]) and
(fnv32(0,@content,sizeof(content)-4)=content.CRC);
end;
function ECCCheckDate(const content: TECCCertificateContent): boolean;
var now: TECCDate;
begin
now := NowECCDate;
with content.Signed do
result := (IssueDate<=now) and
((ValidityStart=0) or (ValidityStart<=now)) and
((ValidityEnd=0) or (ValidityEnd>=now));
end;
function IsEqual(const issuer1,issuer2: TECCCertificateIssuer): boolean; overload;
var a: TPtrIntArray absolute issuer1;
b: TPtrIntArray absolute issuer2;
begin
result := (a[0]=b[0]) and (a[1]=b[1])
{$ifndef CPU64} and (a[2]=b[2]) and (a[3]=b[3]){$endif};
end;
function IsEqual(const id1,id2: TECCCertificateID): boolean; overload;
var a: TPtrIntArray absolute id1;
b: TPtrIntArray absolute id2;
begin
result := (a[0]=b[0]) and (a[1]=b[1])
{$ifndef CPU64} and (a[2]=b[2]) and (a[3]=b[3]){$endif};
end;
function IsZero(const issuer: TECCCertificateIssuer): boolean;
var a: TPtrIntArray absolute issuer;
begin
result := (a[0]=0) and (a[1]=0)
{$ifndef CPU64} and (a[2]=0) and (a[3]=0){$endif};
end;
function IsZero(const id: TECCCertificateID): boolean;
var a: TPtrIntArray absolute id;
begin
result := (a[0]=0) and (a[1]=0)
{$ifndef CPU64} and (a[2]=0) and (a[3]=0){$endif};
end;
function ECCSelfSigned(const content: TECCCertificateContent): boolean;
begin
with content.Signed do
result := IsEqual(AuthoritySerial,Serial) and not IsZero(Serial) and
IsEqual(AuthorityIssuer,Issuer);
end;
function ECCCheck(const content: TECCSignatureCertifiedContent): boolean;
begin
result := (content.Version in [1]) and (content.Date<>0) and
not IsZero(content.AuthoritySerial) and not IsZero(content.AuthorityIssuer) and
not IsZero(@content.Signature,sizeof(content.Signature));
end;
function ECCSign(const base64: RawUTF8; out content: TECCSignatureCertifiedContent): boolean;
begin
result := Base64ToBin(pointer(base64),@content,length(base64),sizeof(content),false);
end;
function ECCText(const sign: TECCSignatureCertifiedContent): RawUTF8; overload;
begin
if ECCCheck(sign) then
result := BinToBase64(@sign,sizeof(sign)) else
result := '';
end;
function ECCText(const sign: TECCSignature): RawUTF8; overload;
begin
if IsZero(@sign,sizeof(sign)) then
result := '' else
result := BinToBase64(@sign,sizeof(sign));
end;
function ECCVerify(const sign: TECCSignatureCertifiedContent;
const hash: THash256; const auth: TECCCertificateContent): TECCValidity;
begin
if IsZero(hash) then
result := ecvBadParameter else
if not ECCCheck(sign) then
result := ecvCorrupted else
if sign.Date>NowECCDate then
result := ecvInvalidDate else
if not ECCCheck(auth) then
result := ecvUnknownAuthority else
if not ECCCheckDate(auth) then
result := ecvDeprecatedAuthority else
if not ecdsa_verify(auth.Signed.PublicKey,hash,sign.Signature) then
result := ecvInvalidSignature else
if ECCSelfSigned(auth) then
result := ecvValidSelfSigned else
result := ecvValidSigned;
end;
const
ECIES_MAGIC: array[0..1] of array[0..15] of AnsiChar =
('SynEccEncrypted'#26, 'SynEccEncrypt01'#26);
ECIES_NOSYNLZ: array[ecaPBKDF2_HMAC_SHA256_AES256_CFB_SYNLZ..
ecaPBKDF2_HMAC_SHA256_AES128_CTR_SYNLZ] 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_AES128_CFB, ecaPBKDF2_HMAC_SHA256_AES128_CBC,
ecaPBKDF2_HMAC_SHA256_AES128_OFB, ecaPBKDF2_HMAC_SHA256_AES128_CTR);
ECIES_AES: array[ecaPBKDF2_HMAC_SHA256_AES256_CFB..
ecaPBKDF2_HMAC_SHA256_AES128_CTR] of TAESAbstractClass = (
TAESCFB, TAESCBC, TAESOFB, TAESCTR, TAESCFB, TAESCBC, TAESOFB, TAESCTR,
TAESCFB, TAESCBC, TAESOFB, TAESCTR, TAESCFB, TAESCBC, TAESOFB, TAESCTR);
ECIES_AESSIZE: array[ecaPBKDF2_HMAC_SHA256_AES256_CFB..
ecaPBKDF2_HMAC_SHA256_AES128_CTR] of integer = (
256, 256, 256, 256, 256, 256, 256, 256,
128, 128, 128, 128, 128, 128, 128, 128);
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 [Low(ECIES_AES)..High(ECIES_AES)]) 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: integer;
tmp: RawByteString;
begin
result := false;
if encryptedfile='' then
exit;
F := FileOpen(encryptedfile,fmOpenRead or fmShareDenyNone);
if PtrInt(F)<0 then
exit;
if FileRead(F,head,sizeof(head))=sizeof(head) then
result := ECIESHeader(head);
if result and (rawencryptedfile<>'') then begin
len := GetFileSize(F,nil)-sizeof(head);
SetLength(tmp,len);
if FileRead(F,pointer(tmp)^,len)<>len then
result := false else
result := FileFromString(tmp,rawencryptedfile);
end;
FileClose(F);
end;
function ECIESHeaderText(const head: TECIESHeader): RawUTF8;
{$ifdef NOVARIANTS}
var s: RawUTF8;
begin
s := ECCText(head.sign);
{$else}
var s: variant;
sign: TECCSignatureCertified;
begin
sign := TECCSignatureCertified.CreateFrom(head.sign,true);
try
if sign.Check then begin
s := sign.ToVariant;
TDocVariantData(s).AddValueFromText('ECDSA',ECCText(head.sign.Signature));
end;
finally
sign.Free;
end;
{$endif}
with head do
result := JSONEncode(['Date',ECCText(date), 'Size',size,
'Recipient',ECCText(rec), 'RecipientSerial',ECCText(recid),
'FileTime',DateTimeToIso8601Text(UnixTimeToDateTime(unixts)),
'Algorithm',ToText(algo)^,
'RandomPublicKey',SynCommons.BinToHex(@rndpub,sizeof(rndpub)),
'HMAC',SHA256DigestToString(hmac), 'Signature',s,
'Meta',(efMetaData in ECIESFeatures(head))]);
end;
function ECIESHeaderText(const encryptedfile,rawencryptedfile: TFileName): RawUTF8; overload;
var h: TECIESHeader;
begin
if ECIESHeaderFile(encryptedfile,h,rawencryptedfile) then
result := ECIESHeaderText(h) else
result := '';
end;
{ *********** high-level certificate-based public-key cryptography *********** }
function ToText(val: TECCValidity): PShortString;
begin
result := GetEnumName(TypeInfo(TECCValidity),ord(val));
end;
function ToText(res: TECCDecrypt): PShortString;
begin
result := GetEnumName(TypeInfo(TECCDecrypt),ord(res));
end;
function ToText(algo: TECIESAlgo): PShortString;
begin
result := GetEnumName(TypeInfo(TECIESAlgo),ord(algo));
end;
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;
var
_ECCKeyFileFolder: TFileName;
function ECCKeyFileFolder: TFileName;
begin
if _ECCKeyFileFolder='' then begin
_ECCKeyFileFolder := GetSystemPath(spUserData)+
{$ifdef MSWINDOWS}'Synopse\Keys\'{$else}'.synopse/keys/'{$endif};
if not DirectoryExists(_ECCKeyFileFolder) then
CreateDir(_ECCKeyFileFolder); // always create this folder
end;
result := _ECCKeyFileFolder;
end;
function ECCKeyFileFind(var TruncatedFileName: TFileName; privkey: boolean): boolean;
var match: TFindFilesDynArray;
ext,mask: TFileName;
begin
match := nil; // to please Kylix
if privkey then
ext := ECCCERTIFICATESECRET_FILEEXT else
ext := ECCCERTIFICATEPUBLIC_FILEEXT;
result := true;
if FileExists(TruncatedFileName) then
exit;
if FileExists(TruncatedFileName+ext) then begin
TruncatedFileName := TruncatedFileName+ext;
exit;
end;
mask := ExtractFileName(TruncatedFileName)+'*'+ext;
match := FindFiles(ExtractFilePath(TruncatedFileName),mask);
if length(match)<>1 then
match := FindFiles(ECCKeyFileFolder,mask);
if length(match)<>1 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
keyfile := UTF8ToString(ECCText(head.recid));
result := ECCKeyFileFind(keyfile,privkey);
end;
end;
{ TECCCertificate }
constructor TECCCertificate.Create;
begin
inherited Create;
fContent.Version := 1;
end;
constructor TECCCertificate.CreateFrom(const binary: TECCCertificateContent);
begin
Create;
fContent := binary;
if not ECCCheck(fContent) then
raise EECCException.CreateUTF8('Invalid %.CreateFrom',[self]);
end;
constructor TECCCertificate.CreateFromBase64(const base64: RawUTF8);
begin
Create;
if not FromBase64(base64) then
raise EECCException.CreateUTF8('Invalid %.CreateFromBase64',[self]);
end;
constructor TECCCertificate.CreateFromAuth(const AuthPubKey: TFileName;
const AuthBase64, AuthSerial: RawUTF8);
begin
Create;
if not FromAuth(AuthPubKey,AuthBase64,AuthSerial) then
raise EECCException.CreateUTF8('Invalid %.CreateFromAuth',[self]);
end;
function TECCCertificate.GetAuthorityIssuer: RawUTF8;
begin
result := ECCText(fContent.Signed.AuthorityIssuer);
end;
function TECCCertificate.GetAuthoritySerial: RawUTF8;
begin
result := ECCText(fContent.Signed.AuthoritySerial);
end;
function TECCCertificate.GetIssueDate: RawUTF8;
begin
result := ECCText(fContent.Signed.IssueDate);
end;
function TECCCertificate.GetIssuer: RawUTF8;
begin
result := ECCText(fContent.Signed.Issuer);
end;
function TECCCertificate.GetSerial: RawUTF8;
begin
result := ECCText(fContent.Signed.Serial);
end;
function TECCCertificate.GetValidityEnd: RawUTF8;
begin
result := ECCText(fContent.Signed.ValidityEnd);
end;
function TECCCertificate.GetValidityStart: RawUTF8;
begin
result := ECCText(fContent.Signed.ValidityStart);
end;
function TECCCertificate.GetIsSelfSigned: boolean;
begin
result := (self<>nil) and ECCSelfSigned(fContent);
end;
function TECCCertificate.CheckCRC: boolean;
begin
result := (self<>nil) and ECCCheck(fContent);
end;
function TECCCertificate.FromBase64(const base64: RawUTF8): boolean;
var st: TRawByteStringStream;
begin
if base64='' then
result := false else begin
st := TRawByteStringStream.Create(Base64ToBinSafe(base64));
try
result := LoadFromStream(st) and ECCCheck(fContent);
finally
st.Free;
end;
end;
end;
function TECCCertificate.FromFile(const filename: TFileName): boolean;
var json: RawUTF8;
fn: TFileName;
begin
if ExtractFileExt(filename)='' then
fn := filename+ECCCERTIFICATEPUBLIC_FILEEXT else
fn := filename;
json := StringFromFile(fn);
if json='' then
result := false else
result := FromBase64(JSONDecode(json,'Base64',nil,true));
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
authfilename := UTF8ToString(AuthSerial);
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.ToBase64: RawUTF8;
var st: TRawByteStringStream;
begin
st := TRawByteStringStream.Create;
try
if SaveToStream(st) then
result := BinToBase64(st.DataString);
finally
st.Free;
end;
end;
function TECCCertificate.PublicToBase64: RawUTF8;
var sav: boolean;
begin
sav := fStoreOnlyPublicKey;
fStoreOnlyPublicKey := true;
result := ToBase64;
fStoreOnlyPublicKey := sav;
end;
function TECCCertificate.LoadFromStream(Stream: TStream): boolean;
begin
result := (Stream.Read(fContent,sizeof(fContent))=sizeof(fContent)) and
InternalLoad(ReadStringFromStream(Stream,524288));
end;
function TECCCertificate.SaveToStream(Stream: TStream): boolean;
begin
result := CheckCRC and
(Stream.Write(fContent,sizeof(fContent))=sizeof(fContent)) and
WriteStringToStream(Stream,InternalSave);
end;
function TECCCertificate.InternalLoad(const data: RawByteString): boolean;
begin
result := true;
end;
function TECCCertificate.InternalSave: RawByteString;
begin
result := '';
end;
{$ifdef ISDELPHI20062007}
{$WARNINGS OFF} // circument 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: THash256;
begin
result := '';
if Plain='' then
exit;
if not CheckCRC then
raise EECCException.CreateUTF8('%.Encrypt: no public key',[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 [Low(ECIES_AES)..High(ECIES_AES)]) then
raise EECCException.CreateUTF8('%.Encrypt: unsupported %',[self,ToText(Algo)^]);
try
head.magic := THash128(ECIES_MAGIC[0]);
head.rec := fContent.Signed.Issuer;
head.recid := fContent.Signed.Serial;
head.size := length(Plain);
head.date := NowECCDate;
head.unixts := DateTimeToUnixTime(FileDateTime);
content := Plain;
if Signature.Check then begin
head.sign := Signature.fContent;
{$ifndef NOVARIANTS}
if Signature.InheritsFrom(TECCSignatureCertifiedFile) then
with _Safe(TECCSignatureCertifiedFile(Signature).MetaData)^ do
if (Kind=dvObject) and (Count>0) then begin
head.magic := THash128(ECIES_MAGIC[1]); // indicates efMetaData
content := ToJSON('','',jsonUnquotedPropNameCompact)+#0+content;
end; // new format storing {metadata}+#0+plain
{$endif}
end else
FillcharFast(head.sign,sizeof(head.sign),255); // Version=255=not signed
if not ecc_make_key(head.rndpub,rndpriv) then
raise EECCException.CreateUTF8('%.Encrypt: ecc_make_key?',[self]);
SetLength(secret,sizeof(TECCSecretKey));
if not ecdh_shared_secret(fContent.Signed.PublicKey,rndpriv,PECCSecretKey(secret)^) then
raise EECCException.CreateUTF8('%.Encrypt: ecdh_shared_secret?',[self]);
PBKDF2_HMAC_SHA256(secret,KDFSalt,KDFRounds,aeskey,'salt');
if Algo in [low(ECIES_NOSYNLZ)..high(ECIES_NOSYNLZ)] then begin
dec := SynLZCompress(content);
if length(dec)>length(content) then begin // SynLZ was inefficient
FillZero(dec);
dec := content;
Algo := ECIES_NOSYNLZ[Algo];
end;
end else
dec := content;
head.Algo := Algo;
enc := ECIES_AES[Algo].SimpleEncrypt( // encrypt with PKCS7 padding
dec,aeskey,ECIES_AESSIZE[Algo],true,true);
PBKDF2_HMAC_SHA256(secret,MACSalt,MACRounds,mackey,'hmac');
HMAC_SHA256(mackey,enc,head.hmac); // HMAC of the encrypted content
head.crc := crc32c(PCardinal(@head.hmac)^,@head,sizeof(head)-sizeof(head.crc));
SetLength(result,sizeof(head)+length(enc));
PECIESHeader(result)^ := head;
MoveFast(pointer(enc)^,PByteArray(result)[sizeof(head)],length(enc));
finally
FillZero(aeskey);
FillZero(mackey);
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} // circument Delphi 2007 false posit