/// 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) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** Version: MPL 1.1/GPL 2.0/LGPL 2.1 The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is Synopse framework. The Initial Developer of the Original Code is Arnaud Bouchez. Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): - Kenneth MacKay (micro-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/kmackay/micro-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 ***** 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 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, SynTable, 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 a signature, in the DER format // - static allocated buffer as returned by EccSignToDer() TEccSignatureDer = array[0..(ECC_BYTES * 2) + 7] 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 micro-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 micro-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 micro-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 micro-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 raw signature into a DER compatible content // - returns the number of bytes encoded into der[] buffer function EccSignToDer(const sign: TEccSignature; out der: TEccSignatureDer): integer; /// 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 // .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(TSynPersistentLock) 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 static\x86_64-win64\eccwin64O1.o} {$endif} {$ifdef ECC_O2} {$ifdef FPC} {$L static\x86_64-win64\eccwin64O2.o} {$else} {$L SynEcc64O2.o} // same file as static\x86_64-win64\eccwin64O2.o {$endif} {$endif} {$ifdef ECC_O3} {$L static\x86_64-win64\eccwin64O3.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} { Pure Pascal Version of low-level ECC process (adapted from micro-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; 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; {$ifdef HASUINT64} 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 _equals(const Left, Right: TVLI): boolean; {$ifdef HASINLINE}inline;{$endif} begin result := (Left[0]=Right[0]) and (Left[1]=Right[1]) and (Left[2]=Right[2]) and (Left[3]=Right[3]); 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 GetBitPtr(@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 GetBitPtr(@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 GetBitPtr(@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 GetBitPtr(@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 _equals(PrivateK, _1) or _equals(PrivateK, _11) 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 _equals(k, _1) or _equals(k, _11) 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 GetBitPtr(@u1, NumBits-1) then Index := 1 else Index := 0; if GetBitPtr(@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 GetBitPtr(@u1, i) then Index := 1 else Index := 0; if GetBitPtr(@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..1] of TECCCertificateIssuer; begin if IsZero(Issuer) then result := '' else begin tmp[0] := Issuer; tmp[1][0] := 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; const DER_SEQUENCE = $30; DER_INTEGER = #$02; function DerAppend(P: PAnsiChar; vli: PByteArray): PAnsiChar; var pos, prefix: PtrUInt; begin pos := 0; while vli[pos] = 0 do inc(pos); // ignore trailing zeros prefix := vli[pos] shr 7; // two's complement? P[0] := DER_INTEGER; P[1] := AnsiChar(ECC_BYTES - pos + prefix); P[2] := #$00; // prepend 0 for negative number (if prefix=1) inc(P, 2 + prefix); MoveSmall(@vli[pos], P, ECC_BYTES - pos); result := P + ECC_BYTES - pos; end; function EccSignToDer(const sign: TEccSignature; out der: TEccSignatureDer): integer; begin if _isZero(PVLI(@sign[0])^) or _isZero(PVLI(@sign[ECC_BYTES])^) then result := 0 else begin result := DerAppend(DerAppend(@der[2],@sign[0]),@sign[ECC_BYTES])-PAnsiChar(@der); der[0] := DER_SEQUENCE; der[1] := result - 2; end; 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 positive warning {$endif} function TECCCertificate.EncryptFile(const FileToCrypt, DestFile: TFileName; const Salt: RawUTF8; SaltRounds: integer; Algo: TECIESAlgo; IncludeSignFile: boolean): boolean; var plain,encrypted: RawByteString; cert: TECCSignatureCertified; dest: TFileName; filetime: TDateTime; begin plain := StringFromFile(FileToCrypt); if plain='' then raise EECCException.CreateUTF8('File not found: [%]',[FileToCrypt]); if DestFile='' then dest := FileToCrypt+ENCRYPTED_FILEEXT else dest := DestFile; filetime := FileAgeToDateTime(FileToCrypt); try if IncludeSignFile then {$ifdef NOVARIANTS} cert := TECCSignatureCertified.CreateFromFile(FileToCrypt,true) else {$else} cert := TECCSignatureCertifiedFile.CreateFromFile(FileToCrypt,true) else {$endif} cert := nil; try encrypted := Encrypt(plain,cert,filetime,Salt,SaltRounds,'hmac',100,Algo); if encrypted='' then result := false else result := FileFromString(encrypted,dest); finally cert.Free; end; finally FillZero(plain); end; end; {$ifndef NOVARIANTS} function TECCCertificate.ToVariant(withBase64: boolean): variant; begin result := _ObjFast(['Version',Version,'Serial',Serial,'Issuer',Issuer, 'IssueDate',IssueDate,'ValidityStart',ValidityStart,'ValidityEnd',ValidityEnd, 'AuthoritySerial',AuthoritySerial,'AuthorityIssuer',AuthorityIssuer, 'IsSelfSigned',IsSelfSigned]); if withBase64 then TDocVariantData(result).AddValue('Base64',RawUTF8ToVariant(ToBase64)); end; function TECCCertificate.ToFile(const filename: TFileName): boolean; begin if CheckCRC then result := JSONReformatToFile(VariantSaveJSON(ToVariant),filename) else result := false; end; {$endif} { TECCCertificateSecret } constructor TECCCertificateSecret.CreateNew(Authority: TECCCertificateSecret; const IssuerText: RawUTF8; ExpirationDays: integer; StartDate: TDateTime; ParanoidVerify: boolean); var priv: PECCPrivateKey; pub: PECCPublicKey; now: TECCDate; sha: TSHA256; hash: TSHA256Digest; temp: TECCSignature; retry: boolean; begin Create; now := NowECCDate; with fContent.Signed do begin IssueDate := now; if ExpirationDays>0 then begin if StartDate=0 then ValidityStart := now else ValidityStart := ECCDate(StartDate); ValidityEnd := ValidityStart+ExpirationDays; end; TAESPRNG.Fill(TAESBlock(Serial)); if IssuerText='' then TAESPRNG.Fill(TAESBlock(Issuer)) else ECCIssuer(IssuerText,Issuer); for retry := false to true do begin if not ecc_make_key(PublicKey,fPrivateKey) then raise EECCException.CreateUTF8('%.CreateNew: ecc_make_key?',[self]); if Authority=nil then begin AuthoritySerial := Serial; AuthorityIssuer := Issuer; priv := @fPrivateKey; // self-signing pub := @PublicKey; end else begin AuthoritySerial := Authority.fContent.Signed.Serial; AuthorityIssuer := Authority.fContent.Signed.Issuer; priv := @Authority.fPrivateKey; pub := @Authority.fContent.Signed.PublicKey; if ParanoidVerify then // check below will be on Authority keys if not ecdsa_sign(fPrivateKey,hash,temp) or not ecdsa_verify(PublicKey,hash,temp) then if retry then raise EECCException.CreateUTF8('%.CreateNew: ParanoidVerify1?',[self]) else continue; end; sha.Full(@fContent.Signed,sizeof(TECCCertificateSigned),hash); if not ecdsa_sign(priv^,hash,fContent.Signature) then raise EECCException.CreateUTF8('%.CreateNew: ecdsa_sign?',[self]); if not ParanoidVerify or ecdsa_verify(pub^,hash,fContent.Signature) then break else if retry then raise EECCException.CreateUTF8('%.CreateNew: ParanoidVerify2?',[self]); end; end; fContent.CRC := fnv32(0,@fContent,sizeof(fContent)-4); end; constructor TECCCertificateSecret.CreateFromSecureBinary( const Binary: RawByteString; const PassWord: RawUTF8; PBKDF2Rounds: integer; AES: TAESAbstractClass); begin CreateFromSecureBinary(pointer(Binary),length(Binary),PassWord,PBKDF2Rounds,AES); end; constructor TECCCertificateSecret.CreateFromSecureBinary(Data: pointer; Len: integer; const PassWord: RawUTF8; PBKDF2Rounds: integer; AES: TAESAbstractClass); begin Create; if not LoadFromSecureBinary(Data,Len,PassWord,PBKDF2Rounds,AES) then raise EECCException.CreateUTF8('Invalid %.CreateFromSecureBinary',[self]); end; constructor TECCCertificateSecret.CreateFromSecureFile( const FileName: TFileName; const PassWord: RawUTF8; PBKDF2Rounds: integer; AES: TAESAbstractClass); begin Create; if not LoadFromSecureFile(FileName,PassWord,PBKDF2Rounds,AES) then raise EECCException.CreateUTF8('Invalid %.CreateFromSecureFile("%")', [self,FileName]); end; constructor TECCCertificateSecret.CreateFromSecureFile( const FolderName: TFileName; const Serial, PassWord: RawUTF8; PBKDF2Rounds: integer; AES: TAESAbstractClass); begin CreateFromSecureFile(IncludeTrailingPathDelimiter(FolderName)+UTF8ToString(Serial), PassWord,PBKDF2Rounds,AES); end; destructor TECCCertificateSecret.Destroy; begin FillZero(fPrivateKey); inherited Destroy; end; function TECCCertificateSecret.InternalLoad(const data: RawByteString): boolean; begin result := fStoreOnlyPublicKey or TAESPRNG.AFUnsplit(data,fPrivateKey,sizeof(fPrivateKey)); end; function TECCCertificateSecret.InternalSave: RawByteString; begin if fStoreOnlyPublicKey then result := '' else result := TAESPRNG.Main.AFSplit(fPrivateKey,sizeof(fPrivateKey),fAFSplitStripes); end; function TECCCertificateSecret.HasSecret: boolean; begin result := (self<>nil) and not IsZero(THash256(fPrivateKey)); end; const PRIVKEY_MAGIC: array[0..15] of AnsiChar = 'SynEccPrivatKey'#26; PRIVKEY_SALTSIZE = 16; // 128-bit is enough, since it is transmitted as clear function TECCCertificateSecret.SaveToSecureBinary(const PassWord: RawUTF8; AFStripes, PBKDF2Rounds: integer; AES: TAESAbstractClass; NoHeader: boolean): RawByteString; var pksav: boolean; stsav, head: integer; st: TRawByteStringStream; salt, enc: RawByteString; aeskey: TAESKey; a: TAESAbstract; e: PAnsiChar absolute result; begin result := ''; if AES=nil then AES := TAESCFB; pksav := fStoreOnlyPublicKey; stsav := fAFSplitStripes; try fStoreOnlyPublicKey := false; fAFSplitStripes := AFStripes; salt := TAESPRNG.Fill(PRIVKEY_SALTSIZE); st := TRawByteStringStream.Create; try if SaveToStream(st) then begin PBKDF2_HMAC_SHA256(PassWord,salt,PBKDF2Rounds,aeskey); a := AES.Create(aeskey); try enc := a.EncryptPKCS7(st.DataString,true); // result := PRIVKEY_MAGIC+salt+enc; fails under FPC :( if NoHeader then head := 0 else head := sizeof(PRIVKEY_MAGIC); SetLength(result,head+PRIVKEY_SALTSIZE+length(enc)); MoveFast(PRIVKEY_MAGIC,e[0],head); XorBlock16(pointer(salt),@e[head],@PRIVKEY_MAGIC); MoveFast(pointer(enc)^,e[head+PRIVKEY_SALTSIZE],length(enc)); finally a.Free; end; end; finally FillcharFast(pointer(st.DataString)^,length(st.DataString),0); st.Free; end; finally fStoreOnlyPublicKey := pksav; fAFSplitStripes := stsav; FillZero(aeskey); end; end; function TECCCertificateSecret.SaveToSecureFileName(FileNumber: integer): TFileName; var tmp: RawUTF8; begin if self=nil then result := '' else begin if FileNumber>0 then FormatUTF8('%-%',[Serial,UInt3DigitsToShort(FileNumber)],tmp) else tmp := Serial; result := UTF8ToString(tmp)+ECCCERTIFICATESECRET_FILEEXT; end; end; function TECCCertificateSecret.SaveToSecureFile(const PassWord: RawUTF8; const DestFolder: TFileName; AFStripes, PBKDF2Rounds: integer; AES: TAESAbstractClass; NoHeader: boolean): boolean; begin if (self=nil) or not DirectoryExists(DestFolder) then result := false else result := FileFromString(SaveToSecureBinary(PassWord,AFStripes,PBKDF2Rounds,AES,NoHeader), IncludeTrailingPathDelimiter(DestFolder)+SaveToSecureFileName); end; function TECCCertificateSecret.SaveToSecureFiles(const PassWord: RawUTF8; const DestFolder: TFileName; DestFileCount, AFStripes, PBKDF2Rounds: integer; AES: TAESAbstractClass; NoHeader: boolean): boolean; var diff,one: RawByteString; head,index,pos,difflen,onechunk,onelen: integer; o: PAnsiChar absolute one; dest: TFileName; begin if DestFileCount=1 then begin result := SaveToSecureFile(PassWord,DestFolder,AFStripes,PBKDF2Rounds,AES,NoHeader); exit; end; result := false; dest := IncludeTrailingPathDelimiter(DestFolder); if (self=nil) or (DestFileCount<=0) or not DirectoryExists(dest) then exit; if DestFileCount>255 then DestFileCount := 255; diff := SaveToSecureBinary(PassWord,AFStripes*DestFileCount,PBKDF2Rounds,AES,true); difflen := length(diff); onechunk := difflen div DestFileCount; if NoHeader then head := 0 else head := sizeof(PRIVKEY_MAGIC); pos := 0; for index := 1 to DestFileCount do begin if index0 then exit; SetString(salt,PAnsiChar(Data)+head,PRIVKEY_SALTSIZE); try XorBlock16(pointer(salt),@PRIVKEY_MAGIC); PBKDF2_HMAC_SHA256(PassWord,salt,PBKDF2Rounds,aeskey); if AES=nil then AES := TAESCFB; a := AES.Create(aeskey); try decrypted := a.DecryptPKCS7Buffer(PAnsiChar(Data)+head+PRIVKEY_SALTSIZE,Len,true,false); if decrypted='' then exit; // invalid content finally a.Free; end; st := TRawByteStringStream.Create(decrypted); try if LoadFromStream(st) then result := ECCCheck(fContent) and not IsZero(THash256(fPrivateKey)); finally st.Free; end; finally FillZero(decrypted); FillZero(aeskey); end; end; function TECCCertificateSecret.LoadFromSecureFile( const FileName: TFileName; const PassWord: RawUTF8; PBKDF2Rounds: integer; AES: TAESAbstractClass): boolean; var FN: TFileName; begin if ExtractFileExt(FileName)='' then FN := FileName+ECCCERTIFICATESECRET_FILEEXT else FN := FileName; result := LoadFromSecureBinary(StringFromFile(FN),PassWord,PBKDF2Rounds,AES); end; function TECCCertificateSecret.SaveToSource(const ConstName, Comment, PassWord: RawUTF8; IncludePassword: boolean; AFStripes, PBKDF2Rounds: integer; AES: TAESAbstractClass; IncludeRaw: boolean): RawUTF8; var data: RawByteString; name,suffix: RawUTF8; begin result := ''; if (self=nil) or (Password='') then exit; data := SaveToSecureBinary(Password,AFStripes,PBKDF2Rounds,AES,true); // NoHeader=true if data='' then exit; if ConstName='' then name := '_'+copy(Serial,1,24) else name := UpperCase(ConstName); if IncludePassword then suffix := FormatUTF8(' %_PASS = %;'#13#10' %_CYPH = ''%'';'#13#10, [name,QuotedStr(PassWord),name,TSynPersistentWithPassword.ComputePassword(PassWord)]); if ConstName<>'' then suffix := FormatUTF8(' %_SERIAL = ''%'';'#13#10'%',[name,Serial,suffix]); suffix := FormatUTF8(' %_ROUNDS = %;'#13#10'%',[name,PBKDF2Rounds,suffix]); if IncludeRaw then suffix := FormatUTF8(' %_RAW = ''%'';'#13#10'%',[name, SynCommons.BinToHex(@fPrivateKey,sizeof(fPrivateKey)),suffix]); result := BinToSource(name,Comment,pointer(data),length(data),16,suffix) end; function TECCCertificateSecret.SignToBase64(Data: pointer; Len: integer): RawUTF8; begin if (Data=nil) or (Len<0) then result := '' else result := SignToBase64(SHA256Digest(Data,Len)); end; function TECCCertificateSecret.SignToBase64(const Hash: THash256): RawUTF8; var sign: TECCSignatureCertified; begin result := ''; if (self=nil) or IsZero(Hash) then exit; sign := TECCSignatureCertified.CreateNew(self,Hash); try result := sign.ToBase64; finally sign.Free; end; end; {$ifndef NOVARIANTS} function TECCCertificateSecret.SignFile(const FileToSign: TFileName; const MetaNameValuePairs: array of const): TFileName; var content: RawByteString; sign: RawUTF8; doc, meta: TDocVariantData; sha: TSHA256Digest; begin content := StringFromFile(FileToSign); if content='' then raise EECCException.CreateUTF8('%.SignFile: file [%] not found',[self,FileToSign]); sha := SHA256Digest(pointer(content),length(content)); sign := SignToBase64(sha); meta.InitObject(['name',ExtractFileName(FileToSign), 'date',DateTimeToIso8601Text(FileAgeToDateTime(FileToSign))],JSON_OPTIONS_FAST); meta.AddNameValuesToObject(MetaNameValuePairs); doc.InitObject([ 'meta',variant(meta), 'size',length(content), 'md5',MD5(content), 'sha256',SHA256DigestToString(sha), 'sign',sign],JSON_OPTIONS_FAST); result := FileToSign+ECCCERTIFICATESIGN_FILEEXT; FileFromString(doc.ToJSON('','',jsonHumanReadable),result); end; {$endif} function TECCCertificateSecret.Decrypt( const Encrypted: RawByteString; out Decrypted: RawByteString; Signature: PECCSignatureCertifiedContent; MetaData: PRawJSON; FileDateTime: PDateTime; const KDFSalt: RawUTF8; KDFRounds: integer; const MACSalt: RawUTF8; MACRounds: integer): TECCDecrypt; var head: TECIESHeader; features: TECIESFeatures; data: PAnsiChar; datalen,metaend: integer; secret,enc,dec,temp: RawByteString; aeskey, mackey: THash256; hmac: THash256; begin result := ecdCorrupted; datalen := length(Encrypted)-sizeof(TECIESHeader); if (datalen<=0) or not ECIESHeader(Encrypted,head) then exit; data := @PByteArray(Encrypted)[sizeof(TECIESHeader)]; if CheckCRC and HasSecret then try if not IsEqual(head.recid,fContent.Signed.Serial) then begin result := ecdInvalidSerial; exit; end; SetLength(secret,sizeof(TECCSecretKey)); if not ecdh_shared_secret(head.rndpub,fPrivateKey,PECCSecretKey(secret)^) then exit; PBKDF2_HMAC_SHA256(secret,MACSalt,MACRounds,mackey,'hmac'); HMAC_SHA256(@mackey,data,sizeof(mackey),datalen,hmac); result := ecdInvalidMAC; if not IsEqual(hmac,head.hmac) then exit; PBKDF2_HMAC_SHA256(secret,KDFSalt,KDFRounds,aeskey,'salt'); SetString(enc,data,datalen); dec := ECIES_AES[head.Algo].SimpleEncrypt( enc,aeskey,ECIES_AESSIZE[head.Algo],false,true); if head.Algo in [low(ECIES_NOSYNLZ)..high(ECIES_NOSYNLZ)] then SynLZDecompress(pointer(dec),length(dec),Decrypted) else Decrypted := dec; result := ecdDecryptError; features := ECIESFeatures(head); if efMetaData in features then begin if (Decrypted='') or (Decrypted[1]<>'{') then exit; metaend := PosExChar(#0,Decrypted); // {metadata}+#0+plain if metaend=0 then exit; if MetaData<>nil then MetaData^ := copy(Decrypted,1,metaend-1); temp := copy(Decrypted,metaend+1,maxInt); FillZero(Decrypted); // avoid uncyphered content on heap Decrypted := temp; end; if cardinal(length(Decrypted))<>head.size then exit; if FileDateTime<>nil then if head.unixts = 0 then FileDateTime^ := 0 else FileDateTime^ := UnixTimeToDateTime(head.unixts); if (Signature<>nil) and ECCCheck(head.sign) then begin result := ecdDecryptedWithSignature; Signature^ := head.sign; end else result := ecdDecrypted; finally FillZero(aeskey); FillZero(mackey); FillZero(secret); if dec<>Decrypted then FillZero(dec); end else result := ecdNoPrivateKey; end; function TECCCertificateSecret.DecryptFile(const FileToDecrypt, DestFile: TFileName; const Salt: RawUTF8; SaltRounds: integer; Signature: PECCSignatureCertifiedContent; MetaData: PRawJSON): TECCDecrypt; var content,plain: RawByteString; dest: TFileName; filetime: TDateTime; begin content := StringFromFile(FileToDecrypt); result := ecdNoContent; if content<>'' then try result := ecdNoPrivateKey; if not CheckCRC then exit; if DestFile='' then dest := GetFileNameWithoutExt(FileToDecrypt) else dest := DestFile; result := Decrypt(content,plain,Signature,MetaData,@filetime,Salt,SaltRounds); if result in ECC_VALIDDECRYPT then if not FileFromString(plain,dest,false,filetime) then result := ecdWriteFileError; finally FillZero(plain); end; end; { TECCCertificateSecretSetting } function TECCCertificateSecretSetting.CertificateSecret( const FolderName: TFileName): TECCCertificateSecret; begin if self=nil then result := nil else if FileName<>'' then result := TECCCertificateSecret.CreateFromSecureFile( FileName,GetPassWordPlain,PasswordRounds) else if Serial='' then result := nil else result := TECCCertificateSecret.CreateFromSecureFile( FolderName,Serial,GetPassWordPlain,PasswordRounds); end; constructor TECCCertificateSecretSetting.Create; begin inherited Create; fPasswordRounds := DEFAULT_ECCROUNDS; end; { TECCCertificateDecryptSetting } constructor TECCCertificateDecryptSetting.Create; begin inherited Create; fSalt := 'salt'; fSaltRounds := DEFAULT_ECCROUNDS; end; { TECCSignatureCertified } constructor TECCSignatureCertified.Create; begin inherited Create; fContent.Version := 1; end; constructor TECCSignatureCertified.CreateFrom( const binary: TECCSignatureCertifiedContent; NoException: boolean); begin Create; if ECCCheck(binary) then fContent := binary else if not NoException then raise EECCException.CreateUTF8('Invalid %.CreateFrom',[self]); end; constructor TECCSignatureCertified.CreateFromBase64(const base64: RawUTF8; NoException: boolean); begin Create; if not FromBase64(base64) then if not NoException then raise EECCException.CreateUTF8('Invalid %.CreateFromBase64',[self]); end; constructor TECCSignatureCertified.CreateFromFile(const signfilename: TFileName; NoException: boolean); begin Create; if not FromFile(signfilename) then if not NoException then raise EECCException.CreateUTF8('Invalid %.CreateFromFile("%")', [self,signfilename]); end; constructor TECCSignatureCertified.CreateNew( Authority: TECCCertificateSecret; Data: pointer; Len: integer); begin CreateNew(Authority,SHA256Digest(Data,Len)); end; constructor TECCSignatureCertified.CreateNew( Authority: TECCCertificateSecret; const Hash: THash256); begin Create; if not Authority.HasSecret then raise EECCException.CreateUTF8('%.CreateNew: secret=0 %',[self,Authority]); if IsZero(Hash) then raise EECCException.CreateUTF8('%.CreateNew(Hash=0)',[self]); fContent.Date := NowECCDate; fContent.AuthoritySerial := Authority.Content.Signed.Serial; fContent.AuthorityIssuer := Authority.Content.Signed.Issuer; if not ecdsa_sign(Authority.fPrivateKey,Hash,fContent.Signature) then raise EECCException.CreateUTF8('%.CreateNew: ecdsa_sign?',[self]); end; function TECCSignatureCertified.GetAuthorityIssuer: RawUTF8; begin result := ECCText(fContent.AuthorityIssuer); end; function TECCSignatureCertified.GetAuthoritySerial: RawUTF8; begin result := ECCText(fContent.AuthoritySerial); end; function TECCSignatureCertified.GetDate: RawUTF8; begin result := ECCText(fContent.Date); end; function TECCSignatureCertified.FromBase64(const base64: RawUTF8): boolean; begin result := (self<>nil) and Base64ToBin(pointer(base64),@fContent,length(base64),sizeof(fContent),false) and ECCCheck(fContent); end; function TECCSignatureCertified.FromFile(const signfilename: TFileName): boolean; var json: RawUTF8; begin if FileExists(signfilename+ECCCERTIFICATESIGN_FILEEXT) then json := StringFromFile(signfilename+ECCCERTIFICATESIGN_FILEEXT) else json := StringFromFile(signfilename); if json='' then result := false else result := FromBase64(JSONDecode(json,'sign',nil,true)); end; function TECCSignatureCertified.ToBase64: RawUTF8; begin result := BinToBase64(@fContent,sizeof(fContent)); end; {$ifndef NOVARIANTS} function TECCSignatureCertified.ToVariant: variant; begin result := _ObjFast(['Version',Version,'Date',Date, 'AuthoritySerial',AuthoritySerial,'AuthorityIssuer',AuthorityIssuer]); end; {$endif} function TECCSignatureCertified.Check: boolean; begin result := (self<>nil) and ECCCheck(fContent); end; function TECCSignatureCertified.Verify(Authority: TECCCertificate; const hash: THash256): TECCValidity; begin if self=nil then result := ecvBadParameter else if not Authority.CheckCRC then result := ecvUnknownAuthority else result := ECCVerify(fContent,hash,Authority.fContent); end; function TECCSignatureCertified.Verify(Authority: TECCCertificate; Data: pointer; Len: integer): TECCValidity; begin result := Verify(Authority,SHA256Digest(Data,Len)); end; function TECCSignatureCertified.SaveToDERBinary: RawByteString; var der: TEccSignatureDer; begin if not Check then result := '' else SetString(result,PAnsiChar(@der),EccSignToDer(fContent.Signature,der)); end; function TECCSignatureCertified.SaveToDERFile( const FileName: TFileName): boolean; begin if not Check then result := false else result := FileFromString(SaveToDERBinary, FileName); end; {$ifndef NOVARIANTS} { TECCSignatureCertifiedFile } function TECCSignatureCertifiedFile.FromFile(const aFileName: TFileName): boolean; var json: RawUTF8; begin if SameText(ExtractFileExt(aFileName),ECCCERTIFICATESIGN_FILEEXT) then json := StringFromFile(aFileName) else json := StringFromFile(aFileName+ECCCERTIFICATESIGN_FILEEXT); result := FromFileJson(json); end; function TECCSignatureCertifiedFile.FromFileJson(const aFileContent: RawUTF8): boolean; begin fLowLevelInfo.Clear; if not fLowLevelInfo.InitJSON(aFileContent,JSON_OPTIONS_FAST) then begin result := false; exit; end; fSize := fLowLevelInfo.I['size']; fMetaData := fLowLevelInfo.GetValueOrEmpty('meta'); // Value[] makes GPF fMD5 := fLowLevelInfo.U['md5']; fSHA256 := fLowLevelInfo.U['sha256']; result := (fSize>0) and (_Safe(fMetaData)^.Kind<>dvArray) and MD5StringToDigest(fMD5,fMD5Digest) and SHA256StringToDigest(fSHA256,fSHA256Digest) and FromBase64(fLowLevelInfo.U['sign']); end; function TECCSignatureCertifiedFile.FromDecryptedFile(const aDecryptedContent: RawByteString; const Signature: TECCSignatureCertifiedContent; const MetaData: RawJSON): boolean; var MD5: TMD5; SHA256: TSHA256; begin result := ECCCheck(Signature); if not result then exit; fSize := length(aDecryptedContent); MD5.Full(pointer(aDecryptedContent),fSize,fMD5Digest); fMD5 := MD5DigestToString(fMD5Digest); SHA256.Full(pointer(aDecryptedContent),fSize,fSHA256Digest); fSHA256 := SHA256DigestToString(fSHA256Digest); fMetaData := _JsonFast(MetaData); fContent := Signature; fLowLevelInfo.Clear; fLowLevelInfo.InitObject(['size',fSize,'md5',fMD5,'sha256',fSHA256, 'sign',ToBase64,'meta',fMetaData]); end; constructor TECCSignatureCertifiedFile.CreateFromDecryptedFile( const aDecryptedContent: RawByteString; const Signature: TECCSignatureCertifiedContent; const MetaData: RawJSON); begin inherited Create; if not FromDecryptedFile(aDecryptedContent,Signature,MetaData) then raise EECCException.CreateUTF8('Invalid Signature for %.CreateFromDecryptedFile',[self]); end; {$endif NOVARIANTS} { TECCCertificateChain } constructor TECCCertificateChain.CreateFromJson(const json: RawUTF8); begin Create; if not LoadFromJson(json) then raise EECCException.CreateUTF8('Invalid %.CreateFromJson',[self]); end; constructor TECCCertificateChain.CreateFromArray(const values: TRawUTF8DynArray); begin Create; if not LoadFromArray(values) then raise EECCException.CreateUTF8('Invalid %.CreateFromArray',[self]); end; destructor TECCCertificateChain.Destroy; begin ObjArrayClear(fItems); inherited; end; function TECCCertificateChain.IsValid(cert: TECCCertificate): TECCValidity; begin if (self=nil) or (cert=nil) then result := ecvBadParameter else result := IsValid(cert.Content); end; function TECCCertificateChain.IsValid(const content: TECCCertificateContent; ignoreDate: boolean): TECCValidity; var auth: TECCCertificateContent; sha: TSHA256; hash: TSHA256Digest; crc: Int64; begin result := ecvCorrupted; if not ECCCheck(content) then exit; if not ignoreDate then begin result := ecvInvalidDate; if not ECCCheckDate(content) then exit; end; if ECCSelfSigned(content) then result := ecvValidSelfSigned else result := ecvValidSigned; if fIsValidCached then begin crc := crc64c(@content,sizeof(content)); fSafe.Lock; try if Int64ScanExists(pointer(fIsValidCache),fIsValidCacheCount,crc) then exit; finally fSafe.Unlock; end; end else crc := 0; if result=ecvValidSelfSigned then auth.Signed.PublicKey := content.Signed.PublicKey else if not GetBySerial(content.Signed.AuthoritySerial,auth) then begin result := ecvUnknownAuthority; exit; end else if not ECCCheckDate(auth) then begin result := ecvDeprecatedAuthority; exit; end; sha.Full(@content.Signed,sizeof(content.Signed),hash); if ecdsa_verify(auth.Signed.PublicKey,hash,content.Signature) then begin fSafe.Lock; try if fIsValidCached and (crc<>0) then AddInt64(fIsValidCache,fIsValidCacheCount,crc); finally fSafe.Unlock; end; end else result := ecvInvalidSignature; end; function TECCCertificateChain.IndexBySerial(const Serial: TECCCertificateID): integer; var ser: THash128Rec absolute Serial; begin if (self<>nil) and ((ser.Lo<>0) or (ser.Hi<>0)) then begin for result := 0 to length(fItems)-1 do with PHash128Rec(@fItems[result].Signed.Serial)^ do {$ifdef CPU64} if (ser.Lo=Lo) and (ser.Hi=Hi) then {$else} if (ser.i0=i0) and (ser.i1=i1) and (ser.i2=i2) and (ser.i3=i3) then {$endif} exit; end; result := -1; end; function TECCCertificateChain.GetBySerial(const Serial: TECCCertificateID): TECCCertificate; var i: integer; begin i := IndexBySerial(Serial); if i<0 then result := nil else result := fItems[i]; end; function TECCCertificateChain.GetBySerial(const Serial: RawUTF8): TECCCertificate; var id: TECCCertificateID; begin if ECCID(Serial,id) then result := GetBySerial(id) else result := nil; end; function TECCCertificateChain.GetBySerial(const Serial: TECCCertificateID; out Content: TECCCertificateContent): boolean; var cert: TECCCertificate; begin fSafe.Lock; try cert := GetBySerial(Serial); if cert<>nil then begin Content := cert.Content; result := true; end else result := false; finally fSafe.UnLock; end; end; function TECCCertificateChain.GetBySerial(const Serial: TECCCertificateID; out PublicKey: TECCPublicKey): boolean; var cert: TECCCertificate; begin fSafe.Lock; try cert := GetBySerial(Serial); if cert<>nil then begin PublicKey := cert.Content.Signed.PublicKey; result := true; end else result := false; finally fSafe.UnLock; end; end; procedure TECCCertificateChain.SetIsValidCached(const Value: boolean); begin if fIsValidCached=Value then exit; fSafe.Lock; try fIsValidCached := Value; if not Value then begin fIsValidCache := nil; fIsValidCacheCount := 0; end; finally fSafe.UnLock; end; end; function TECCCertificateChain.InternalAdd(cert: TECCCertificate; expected: TECCValidity): integer; begin result := -1; if (self=nil) or (cert=nil) or (IsValid(cert.fContent,true)<>expected) then exit; fSafe.Lock; try if IndexBySerial(cert.Signed.Serial)<0 then result := ObjArrayAdd(fItems,cert); finally fSafe.UnLock; end; end; function TECCCertificateChain.Add(cert: TECCCertificate): integer; begin result := InternalAdd(cert,ecvValidSigned); end; function TECCCertificateChain.AddSelfSigned(cert: TECCCertificate): integer; begin result := InternalAdd(cert,ecvValidSelfSigned); end; procedure TECCCertificateChain.Clear; begin fSafe.Lock; try ObjArrayClear(fItems); fIsValidCacheCount := 0; fIsValidCache := nil; finally fSafe.UnLock; end; end; function TECCCertificateChain.GetCount: integer; begin if self=nil then result := 0 else result := length(fItems); end; function TECCCertificateChain.IsAuthorized(sign: TECCSignatureCertified): boolean; begin if (self<>nil) and (sign<>nil) then result := IsAuthorized(sign.Content) else result := false; end; function TECCCertificateChain.IsAuthorized( const sign: TECCSignatureCertifiedContent): boolean; var content: TECCCertificateContent; begin result := GetBySerial(sign.AuthoritySerial, content) and IsEqual(content.Signed.AuthorityIssuer, sign.AuthorityIssuer); end; function TECCCertificateChain.IsAuthorized(const base64sign: RawUTF8): boolean; var sign: TECCSignatureCertifiedContent; begin if ECCSign(base64sign,sign) then result := IsAuthorized(sign) else result := false; end; function TECCCertificateChain.IsSigned(sign: TECCSignatureCertified; Data: pointer; Len: integer): TECCValidity; var hash: TSHA256Digest; begin if (self<>nil) and (sign<>nil) and (Data<>nil) and (Len>0) then begin hash := SHA256Digest(Data,Len); {$ifndef NOVARIANTS} if sign.InheritsFrom(TECCSignatureCertifiedFile) then with TECCSignatureCertifiedFile(sign) do if (Size<>Len) or not IsEqual(hash,SHA256Digest) or not IsEqual(MD5Buf(Data^,Len),MD5Digest) then begin result := ecvCorrupted; exit; end; {$endif} result := IsSigned(sign.Content,hash); end else result := ecvBadParameter; end; {$ifndef NOVARIANTS} function TECCCertificateChain.IsSigned(sign: TECCSignatureCertifiedFile): TECCValidity; begin if (self<>nil) and (sign<>nil) then result := IsSigned(sign.Content,sign.SHA256Digest) else result := ecvBadParameter; end; {$endif NOVARIANTS} function TECCCertificateChain.IsSigned(sign: TECCSignatureCertified; const hash: THash256): TECCValidity; begin if (self<>nil) and (sign<>nil) then result := IsSigned(sign.Content,hash) else result := ecvBadParameter; end; function TECCCertificateChain.IsSigned(const sign: TECCSignatureCertifiedContent; Data: pointer; Len: integer): TECCValidity; begin if (Data=nil) or (Len<=0) then result := ecvBadParameter else result := IsSigned(sign,SHA256Digest(Data,Len)); end; function TECCCertificateChain.IsSigned(const base64sign: RawUTF8; const hash: THash256): TECCValidity; var sign: TECCSignatureCertifiedContent; begin if ECCSign(base64sign,sign) then result := IsSigned(sign,hash) else result := ecvBadParameter; end; function TECCCertificateChain.IsSigned(const base64sign: RawUTF8; Data: pointer; Len: integer): TECCValidity; var sign: TECCSignatureCertifiedContent; begin if ECCSign(base64sign,sign) then result := IsSigned(sign,Data,Len) else result := ecvBadParameter; end; function TECCCertificateChain.IsSigned(const sign: TECCSignatureCertifiedContent; const hash: THash256): TECCValidity; var auth: TECCCertificateContent; begin if self=nil then result := ecvBadParameter else if not GetBySerial(sign.AuthoritySerial,auth) then result := ecvUnknownAuthority else result := ECCVerify(sign,hash,auth); end; function TECCCertificateChain.SaveToJson: RawUTF8; begin result := JSONEncodeArrayUTF8(SaveToArray); end; function TECCCertificateChain.SaveToArray: TRawUTF8DynArray; var i: integer; begin fSafe.Lock; try SetLength(result,length(fItems)); for i := 0 to high(result) do result[i] := fItems[i].PublicToBase64; finally fSafe.UnLock; end; end; function TECCCertificateChain.LoadFromJson(const json: RawUTF8): boolean; var values: TRawUTF8DynArray; tmp: TSynTempBuffer; // private copy begin tmp.Init(json); try result := (DynArrayLoadJSON(values,tmp.buf,TypeInfo(TRawUTF8DynArray))<>nil) and LoadFromArray(values); finally tmp.Done; end; end; function TECCCertificateChain.LoadFromArray(const values: TRawUTF8DynArray): boolean; var i: integer; begin result := false; if self=nil then exit; fSafe.Lock; try Clear; SetLength(fItems,length(values)); for i := 0 to high(values) do begin fItems[i] := TECCCertificate.Create; if not fItems[i].FromBase64(values[i]) then begin ObjArrayClear(fItems); exit; end; end; finally fSafe.UnLock; end; result := true; end; function TECCCertificateChain.ValidateItems: TECCCertificateObjArray; var i: integer; begin result := nil; if self=nil then exit; fSafe.Lock; try for i := 0 to high(fItems) do if not (IsValid(fItems[i]) in ECC_VALIDSIGN) then ObjArrayAdd(result,fItems[i]); finally fSafe.UnLock; end; end; {$ifndef NOVARIANTS} // uses TDocVariantData for JSON serialization constructor TECCCertificateChain.CreateFromFile(const jsonfile: TFileName); begin Create; if not LoadFromFile(jsonfile) then raise EECCException.CreateUTF8('Invalid %.CreateFromFile("%")',[self,jsonfile]); end; constructor TECCCertificateChain.CreateFromFiles(const files: array of TFileName); var i: integer; auth: TECCCertificate; begin Create; for i := 0 to high(files) do begin auth := TECCCertificate.Create; try if auth.FromFile(files[i]) then begin ObjArrayAdd(fItems,auth); auth := nil; end else raise EECCException.CreateUTF8( '%.CreateFromFiles: invalid file [%]',[self,files[i]]); finally auth.Free; end; end; end; function TECCCertificateChain.SaveToFileVariant: variant; var pub64,items: TDocVariantData; i,n: integer; begin fSafe.Lock; try n := length(fItems); pub64.InitFast(n,dvArray); items.InitFast(n,dvArray); for i := 0 to n-1 do begin pub64.AddItemText(fItems[i].PublicToBase64); items.AddItem(fItems[i].ToVariant(false)); end; result := _ObjFast(['PublicBase64',variant(pub64),'Items',variant(items)]); finally fSafe.UnLock; end; end; function TECCCertificateChain.SaveToFileContent: RawUTF8; begin VariantSaveJSON(SaveToFileVariant,twJSONEscape,result); end; function TECCCertificateChain.LoadFromFileContent(const cajsoncontent: RawUTF8): boolean; var doc: TDocVariantData; values: TRawUTF8DynArray; begin result := false; if doc.InitJSON(cajsoncontent,JSON_OPTIONS_FAST) then begin doc.GetAsDocVariantSafe('PublicBase64')^.ToRawUTF8DynArray(values); result := LoadFromArray(values); end; end; function GetChainFileName(const jsonfile: TFileName): TFileName; begin if ExtractFileExt(jsonfile)='' then result := jsonfile+ECCCERTIFICATES_FILEEXT else result := jsonfile; end; function TECCCertificateChain.SaveToFile(const jsonfile: TFileName): boolean; var json: RawUTF8; begin if (Count=0) or (jsonfile='') then result := false else begin json := SaveToFileContent; result := JSONBufferReformatToFile(pointer(json),GetChainFileName(jsonfile)); end; end; function TECCCertificateChain.LoadFromFile(const jsonfile: TFileName): boolean; var json: RawUTF8; fn: TFileName; begin fn := GetChainFileName(jsonfile); json := StringFromFile(fn); if json='' then json := StringFromFile(ECCKeyFileFolder+fn); if json='' then result := false else result := LoadFromFileContent(json); end; {$endif NOVARIANTS} { TECDHEProtocol } constructor TECDHEProtocol.Create(aAuth: TECDHEAuth; aPKI: TECCCertificateChain; aPrivate: TECCCertificateSecret); var res: TECCValidity; begin if (aPKI<>nil) and (aPrivate<>nil) then begin res := aPKI.IsValid(aPrivate); if not (res in ECC_VALIDSIGN) then raise EECCException.CreateUTF8('%.Create failed: aPKI.IsValid(%)=%', [self,aPrivate.Serial,ToText(res)^]); end; inherited Create; fAlgo.auth := aAuth; fPKI := aPKI; fPrivate := aPrivate; fEFSalt := 'ecdhesalt'; fMACSalt := 'ecdhemac'; end; constructor TECDHEProtocol.CreateFrom(aAnother: TECDHEProtocol); begin Create(aAnother.fAlgo.auth,aAnother.fPKI,aAnother.fPrivate); fEFSalt := aAnother.fEFSalt; fMACSalt := aAnother.fMACSalt; end; var _FromKeySetCA: TECCCertificateChain; _FromKeySetCARefCount: integer; destructor TECDHEProtocol.Destroy; begin if fAES[true]<>fAES[false] then fAES[true].Free; // occurs only for TAESCBC fAES[false].Free; FillZero(fkM[false].b); FillZero(fkM[true].b); if fPKI<>nil then if ownPKI in fOwned then fPKI.Free else if (fPKI=_FromKeySetCA) and (_FromKeySetCARefCount>0) then dec(_FromKeySetCARefCount); if ownPrivate in fOwned then fPrivate.Free; inherited Destroy; end; class procedure TECDHEProtocol.FromKeySetCA(aPKI: TECCCertificateChain); begin if _FromKeySetCA<>nil then if _FromKeySetCARefCount>0 then raise EECCException.CreateUTF8('%.FromKeySetCA: % is still used by % instance(s)', [self,_FromKeySetCA,_FromKeySetCARefCount]) else _FromKeySetCA.Free; _FromKeySetCA := aPKI; end; class function TECDHEProtocol.FromKey(const aKey: RawUTF8; aServer: boolean): TECDHEProtocol; const CL: array[boolean] of TECDHEProtocolClass = ( TECDHEProtocolServer, TECDHEProtocolClient); var sw: TSynNameValue; pw,c: RawUTF8; fn: TFileName; algo: TECDHEAlgo; ca: TECCCertificateChain; chain: TRawUTF8DynArray; priv: TECCCertificateSecret; i,pr: integer; begin result := nil; if not IdemPChar(pointer(aKey),'A=') then exit; // a=mutual;k=hmacsha256;e=aescrc128;m=duringef;p=34a2;pw=password;pr=60000;ca=.. sw.InitFromCSV(pointer(aKey),'=',';'); if not sw.ValueEnum('a',TypeInfo(TECDHEAuth),algo.auth) then exit; // mandatory parameter sw.ValueEnum('k',TypeInfo(TECDHEKDF),algo.kdf); sw.ValueEnum('e',TypeInfo(TECDHEEF),algo.ef); sw.ValueEnum('m',TypeInfo(TECDHEMAC),algo.mac); // compute ca: TECCCertificateChain ca := nil; c := sw.Str['ca']; if c<>'' then begin ca := TECCCertificateChain.Create; {$ifndef NOVARIANTS} fn := UTF8ToString(c); if not ca.LoadFromFile(fn) then {$endif NOVARIANTS} begin CSVToRawUTF8DynArray(c,',','',chain); for i := 0 to high(chain) do chain[i] := UnQuoteSQLString(chain[i]); if ca.LoadFromArray(chain) then ca.IsValidCached := true else // for faster Clone process FreeAndnil(ca); end; end; if (ca=nil) and (_FromKeySetCA<>nil) then begin ca := _FromKeySetCA; inc(_FromKeySetCARefCount); end; // compute priv: TECCCertificateSecret priv := nil; fn := UTF8ToString(sw.Str['p']); pw := sw.Str['pw']; pr := sw.ValueInt('pr',60000); // DEFAULT_ECCROUNDS may change if (fn<>'') and (pw<>'') and ECCKeyFileFind(fn,true) then priv := TECCCertificateSecret.CreateFromSecureFile(fn,pw,pr); result := CL[aServer].Create(algo.auth,ca,priv); result.KDF := algo.kdf; result.EF := algo.ef; result.MAC := algo.mac; if (ca<>nil) and (ca<>_FromKeySetCA) then include(result.fOwned,ownPKI); if priv<>nil then include(result.fOwned,ownPrivate); end; class function TECDHEProtocol.FromKeyCompute(const privkey,privpassword: RawUTF8; privrounds: integer; const pki: RawUTF8; auth: TECDHEAuth; kdf: TECDHEKDF; ef: TECDHEEF; mac: TECDHEMAC; customkey: cardinal): RawUTF8; begin FormatUTF8('a=%',[ord(auth)],result); if kdf<>low(kdf) then result := result+';k='+TrimLeftLowerCaseShort(ToText(kdf)); if ef<>low(ef) then result := result+';e='+TrimLeftLowerCaseShort(ToText(ef)); if mac<>low(mac) then result := result+';m='+TrimLeftLowerCaseShort(ToText(mac)); result := lowercase(result); if pki<>'' then result := result+';ca='+pki; if privkey<>'' then begin result := FormatUTF8('%;p=%;pw=%',[result,privkey,privpassword]); if privrounds<>60000 then // DEFAULT_ECCROUNDS may change result := FormatUTF8('%;pr=%',[result,privrounds]); end; result := TSynPersistentWithPassword.ComputePassword(result,customkey); end; const ED: array[boolean] of string[7] = ('Decrypt','Encrypt'); procedure TECDHEProtocol.SetKey(aEncrypt: boolean); begin if fAES[aEncrypt]=nil then raise EECCException.CreateUTF8('%.% with no handshake',[self,ED[aEncrypt]]); fAES[aEncrypt].IV := fkM[aEncrypt].Lo; // kM is a CTR -> IV unicity if fAlgo.mac=macDuringEF then if not fAES[aEncrypt].MACSetNonce(fkM[aEncrypt].b) then raise EECCException.CreateUTF8('%.%: macDuringEF not available in %/%', [self,ED[aEncrypt],ToText(fAlgo.ef)^,fAES[aEncrypt]]); end; procedure TECDHEProtocol.ComputeMAC(aEncrypt: boolean; aEncrypted: pointer; aLen: integer; out aMAC: THash256Rec); var i,c: cardinal; begin case fAlgo.mac of macDuringEF: if not fAES[aEncrypt].MACGetLast(aMac.b) then // computed during EF process raise EECCException.CreateUTF8('%.%: macDuringEF not available in %/%', [self,ED[aEncrypt],ToText(fAlgo.ef)^,fAES[aEncrypt]]); macHmacCrc256c: HMAC_CRC256C(@fkM[aEncrypt],aEncrypted,sizeof(THash256),aLen,aMAC.b); macHmacSha256: HMAC_SHA256(@fkM[aEncrypt],aEncrypted,sizeof(THash256),aLen,aMAC.b); macHmacCrc32c: begin c := HMAC_CRC32C(@fkM[aEncrypt],aEncrypted,sizeof(THash256),aLen); for i := 0 to 7 do aMac.c[i] := c; // naive 256-bit diffusion end; macXxHash32: begin c := xxHash32(fkM[aEncrypt].i0,aEncrypted,aLen); for i := 0 to 7 do aMac.c[i] := c; // naive 256-bit diffusion end; macNone: crc256c(@fkM[aEncrypt],sizeof(THash256),aMAC.b); // replay attack only else raise EECCException.CreateUTF8('%.%: ComputeMAC %?', [self,ED[aEncrypt],ToText(fAlgo.mac)^]); end; with fkM[aEncrypt] do for i := 0 to 3 do begin inc(q[i]); // sequence number against replay attacks if q[i]<>0 then break; end; end; procedure TECDHEProtocol.Encrypt(const aPlain: RawByteString; out aEncrypted: RawByteString); var len: integer; begin fSafe.Lock; try SetKey(true); len := fAES[true].EncryptPKCS7Length(length(aPlain),false); SetString(aEncrypted,nil,len+sizeof(THash256)); fAES[true].EncryptPKCS7Buffer(Pointer(aPlain),pointer(aEncrypted), length(aPlain),len,false); ComputeMac(true,pointer(aEncrypted),len,PHash256Rec(@PByteArray(aEncrypted)[len])^); finally fSafe.UnLock; end; end; function TECDHEProtocol.Decrypt(const aEncrypted: RawByteString; out aPlain: RawByteString): TProtocolResult; var P: PAnsiChar absolute aEncrypted; len, i: integer; mac: THash256Rec; begin result := sprInvalidMAC; len := length(aEncrypted)-sizeof(THash256); if len<=0 then exit; fSafe.Lock; try SetKey(false); aPlain := fAES[false].DecryptPKCS7Buffer(P,len,false,false); if aPlain='' then begin with fkM[false] do for i := 0 to 3 do begin inc(q[i]); // don't compute MAC, but increase sequence if q[i]<>0 then break; end; exit; end; ComputeMac(false,P,len,mac); if IsEqual(mac.b,PHash256(P+len)^) then result := sprSuccess; finally fSafe.Unlock; end; end; function TECDHEProtocol.CheckError(const aEncrypted: RawByteString): TProtocolResult; begin if fAlgo.mac<>macDuringEF then begin result := sprUnsupported; exit; end; fSafe.Lock; try SetKey(false); if fAES[false].MACCheckError(pointer(aEncrypted),length(aEncrypted)) then result := sprSuccess else result := sprInvalidMAC; finally fSafe.Unlock; end; end; procedure TECDHEProtocol.SharedSecret(sA,sB: PHash256); const AES_CLASS: array[TECDHEEF] of TAESAbstractClass = ( // efAesCrc, efAesCfb, efAesOfb, efAesCtr, efAesCbc TAESCFBCRC, TAESCFB, TAESOFB, TAESCTR, TAESCBC, TAESCFBCRC, TAESCFB, TAESOFB, TAESCTR, TAESCBC); AES_BITS: array[TECDHEEF] of integer = ( 128, 128, 128, 128, 128, 256, 256, 256, 256, 256); var secret: THash256; procedure ComputeSecret(const salt: RawByteString); var hmac: THMAC_SHA256; begin hmac.Init(pointer(salt),length(salt)); if fAlgo.auth<>authServer then hmac.Update(sA^); if fAlgo.auth<>authClient then hmac.Update(sB^); hmac.Update(fRndA); hmac.Update(fRndB); hmac.Done(secret); end; begin if fAES[false]<>nil then raise EECCException.CreateUTF8('%.SharedSecret already called',[self]); if fAlgo.kdf<>kdfHmacSha256 then raise EECCException.CreateUTF8('%.SharedSecret %?',[self,ToText(fAlgo.kdf)^]); try ComputeSecret(fEFSalt); fAES[false] := AES_CLASS[fAlgo.ef].Create(secret,AES_BITS[fAlgo.ef]); fAES[true] := fAES[false].CloneEncryptDecrypt; ComputeSecret(fMACSalt); fkM[false].b := secret; // first 128-bit also used as AES IV fkM[true].b := secret; finally FillZero(secret); end; end; function TECDHEProtocol.Verify(frame: PByteArray; len: integer; const QC: TECCCertificateContent; out res: TProtocolResult): boolean; var hash: TSHA256Digest; sha: TSHA256; begin result := false; res := sprInvalidCertificate; if fPKI<>nil then begin fCertificateValidity := fPKI.IsValid(QC); if not (fCertificateValidity in ECC_VALIDSIGN) then exit; end else if not ECCCheck(QC) then exit; dec(len,sizeof(TECCSignature)); // Sign at the latest position sha.Full(frame,len,hash); res := sprInvalidSignature; if not ecdsa_verify(QC.Signed.PublicKey,hash,PECCSignature(@frame[len])^) then exit; res := sprSuccess; result := true; end; procedure TECDHEProtocol.Sign(frame: PByteArray; len: integer; out QC: TECCCertificateContent); var hash: TSHA256Digest; sha: TSHA256; begin QC := fPrivate.fContent; dec(len,sizeof(TECCSignature)); // Sign at the latest position sha.Full(frame,len,hash); if not ecdsa_sign(fPrivate.fPrivateKey,hash,PECCSignature(@frame[len])^) then raise EECCException.CreateUTF8('%.Sign: ecdsa_sign?',[self]); end; function TECDHEProtocol.Clone: IProtocol; begin result := TECDHEProtocolClass(ClassType).CreateFrom(self); end; { TECDHEProtocolClient } constructor TECDHEProtocolClient.Create(aAuth: TECDHEAuth; aPKI: TECCCertificateChain; aPrivate: TECCCertificateSecret); begin if (aAuth<>authServer) and not aPrivate.CheckCRC then raise EECCException.CreateUTF8('%.Create: need valid Private Key for %', [self,ToText(aAuth)^]) else inherited; end; procedure TECDHEProtocolClient.ComputeHandshake(out aClient: TECDHEFrameClient); begin if fAES[false]<>nil then raise EECCException.CreateUTF8('%.ComputeHandshake already called',[self]); FillCharFast(aClient,sizeof(aClient),0); aClient.algo := fAlgo; TAESPRNG.Main.FillRandom(fRndA); aClient.RndA := fRndA; if fAlgo.auth<>authClient then if not ecc_make_key(aClient.QE,fdE) then raise EECCException.CreateUTF8('%.ComputeHandshake: ecc_make_key?',[self]); if fAlgo.auth<>authServer then Sign(@aClient,sizeof(aClient),aClient.QCA); end; function TECDHEProtocolClient.ValidateHandshake(const aServer: TECDHEFrameServer): TProtocolResult; var sA,sB: THash256; begin result := sprUnexpectedAlgorithm; if cardinal(aServer.algo)<>cardinal(fAlgo) then exit; result := sprBadRequest; if IsZero(fRndA) or not IsEqual(aServer.RndA,fRndA) or IsZero(aServer.RndB) or IsEqual(aServer.RndA,aServer.RndB) then exit; fRndB := aServer.RndB; if fAlgo.auth<>authClient then if not Verify(@aServer,sizeof(aServer),aServer.QCB,result) then exit; try result := sprInvalidEphemeralKey; if fAlgo.auth<>authServer then if not ecdh_shared_secret(aServer.QF,fPrivate.fPrivateKey,sA) then exit; result := sprInvalidPublicKey; if fAlgo.auth<>authClient then if not ecdh_shared_secret(aServer.QCB.Signed.PublicKey,fdE,sB) then exit; SharedSecret(@sA,@sB); finally FillZero(sA); FillZero(sB); FillZero(fdE); end; result := sprSuccess; end; function TECDHEProtocolClient.ProcessHandshake(const MsgIn: RawUTF8; out MsgOut: RawUTF8): TProtocolResult; var out1: TECDHEFrameClient; in2: TECDHEFrameServer; begin if MsgIn='' then begin ComputeHandshake(out1); MsgOut := BinToBase64(@out1,SizeOf(out1)); result := sprSuccess; end else if Base64ToBin(Pointer(MsgIn),@in2,length(MsgIn),sizeof(in2),false) then result := ValidateHandshake(in2) else result := sprBadRequest; end; { TECDHEProtocolServer } constructor TECDHEProtocolServer.Create(aAuth: TECDHEAuth; aPKI: TECCCertificateChain; aPrivate: TECCCertificateSecret); begin if (aAuth<>authClient) and not aPrivate.CheckCRC then raise EECCException.CreateUTF8('%.Create: need valid Private Key for %', [self,ToText(aAuth)^]); inherited; include(fAuthorized,aAuth); // conservative default end; constructor TECDHEProtocolServer.CreateFrom(aAnother: TECDHEProtocol); begin inherited CreateFrom(aAnother); fAuthorized := (aAnother as TECDHEProtocolServer).fAuthorized; end; function TECDHEProtocolServer.ComputeHandshake(const aClient: TECDHEFrameClient; out aServer: TECDHEFrameServer): TProtocolResult; var dF: TECCPrivateKey; sA,sB: THash256; begin result := sprUnexpectedAlgorithm; if cardinal(aClient.algo)<>cardinal(fAlgo) then begin if not (aClient.algo.auth in fAuthorized) or (aClient.algo.kdf<>fAlgo.kdf) or (aClient.algo.ef<>fAlgo.ef) or (aClient.algo.mac<>fAlgo.mac) then exit; if (aClient.algo.auth<>authClient) and not fPrivate.CheckCRC then exit; fAlgo.auth := aClient.algo.auth; // client forced another mode end; result := sprBadRequest; if IsZero(aClient.RndA) then exit; fRndA := aClient.RndA; if fAlgo.auth<>authServer then if not Verify(@aClient,sizeof(aClient),aClient.QCA,result) then exit; FillCharFast(aServer,sizeof(aServer),0); aServer.algo := fAlgo; aServer.RndA := fRndA; TAESPRNG.Main.FillRandom(fRndB); aServer.RndB := fRndB; if fAlgo.auth<>authServer then if not ecc_make_key(aServer.QF,dF) then raise EECCException.CreateUTF8('%.ComputeHandshake: ecc_make_key?',[self]); try result := sprInvalidPublicKey; if fAlgo.auth<>authServer then if not ecdh_shared_secret(aClient.QCA.Signed.PublicKey,dF,sA) then exit; result := sprInvalidEphemeralKey; if fAlgo.auth<>authClient then if not ecdh_shared_secret(aClient.QE,fPrivate.fPrivateKey,sB) then exit; SharedSecret(@sA,@sB); finally FillZero(sA); FillZero(sB); FillZero(dF); end; if fAlgo.auth<>authClient then Sign(@aServer,sizeof(aServer),aServer.QCB); result := sprSuccess; end; function TECDHEProtocolServer.ProcessHandshake(const MsgIn: RawUTF8; out MsgOut: RawUTF8): TProtocolResult; var in1: TECDHEFrameClient; out1: TECDHEFrameServer; begin if Base64ToBin(Pointer(MsgIn),@in1,length(MsgIn),sizeof(in1),false) then begin result := ComputeHandshake(in1,out1); MsgOut := BinToBase64(@out1,SizeOf(out1)); end else result := sprBadRequest; end; {$ifndef NOVARIANTS} { TJWTES256 } constructor TJWTES256.Create(aCertificate: TECCCertificate; aClaims: TJWTClaims; const aAudience: array of RawUTF8; aExpirationMinutes: integer; aIDIdentifier: TSynUniqueIdentifierProcess; aIDObfuscationKey: RawUTF8); begin if not aCertificate.CheckCRC then raise EJWTException.CreateUTF8('%.Create(aCertificate?)',[self]); inherited Create('ES256',aClaims,aAudience,aExpirationMinutes, aIDIdentifier,aIDObfuscationKey); fCertificate := aCertificate; end; destructor TJWTES256.Destroy; begin if fOwnCertificate then fCertificate.Free; inherited; end; procedure TJWTES256.CheckSignature(const headpayload: RawUTF8; const signature: RawByteString; var JWT: TJWTContent); var sha: TSHA256; hash: TSHA256Digest; begin JWT.result := jwtInvalidSignature; if length(signature)<>sizeof(TECCSignature) then exit; sha.Full(pointer(headpayload),length(headpayload),hash); if ecdsa_verify(fCertificate.fContent.Signed.PublicKey,hash,PECCSignature(signature)^) then JWT.result := jwtValid; end; function TJWTES256.ComputeSignature(const headpayload: RawUTF8): RawUTF8; var sha: TSHA256; hash: TSHA256Digest; sign: TECCSignature; begin if not fCertificate.InheritsFrom(TECCCertificateSecret) or not TECCCertificateSecret(fCertificate).HasSecret then raise EECCException.CreateUTF8('%.ComputeSignature expects % (%) to hold '+ 'a private key',[self,fCertificate,fCertificate.Serial]); sha.Full(pointer(headpayload),length(headpayload),hash); if not ecdsa_sign(TECCCertificateSecret(fCertificate).fPrivateKey,hash,sign) then raise EECCException.CreateUTF8('%.ComputeSignature: ecdsa_sign?',[self]); result := BinToBase64URI(@sign,sizeof(sign)); end; {$endif NOVARIANTS} initialization {$ifdef HASUINT64} assert(NUM_ECC_DIGITS=4); {$endif} assert(sizeof(TECCCertificateContent)=173); // on all platforms and compilers assert(sizeof(TECDHEFrameClient)=290); assert(sizeof(TECDHEFrameServer)=306); {$ifdef ECC_32ASM} pointer(@ecc_make_key) := pointer(@_ecc_make_key); pointer(@ecdh_shared_secret) := pointer(@_ecdh_shared_secret); pointer(@ecdsa_sign) := pointer(@_ecdsa_sign); pointer(@ecdsa_verify) := pointer(@_ecdsa_verify); {$endif ECC_32ASM} end.