Files
tbf/NfcC7_DLL/NfcHanler/Utils/FNv2_ECDsaP256_wSHA256.cs
T

361 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Security.Cryptography;
namespace NfcC7_DLL.NfcHanler.Utils
{
/*********************************************************************************************************
**********************************************************************************************************
ESDsaP256 Sign/Verify Parameter Classes:
********************************************************************************************************/
public class ECDsaKeyPair
{
public byte[] priKey; // 32-byte private key (used for signing)
public byte[] pubKey; // 64-byte public key (used for signature verification)
public Boolean valid; // Flag to indicate that this object contains a valid key pair
// Constructor
public ECDsaKeyPair()
{
this.priKey = new byte[32];
this.pubKey = new byte[64];
this.valid = false;
}
}
public class ECDsaSignParams
{
public ECDsaKeyPair keyPair; // ECDsaKeyPair object containing a valid key pair
public byte[] buffer; // buffer containing a packet to be signed (MAC header | MAC data)
public int data_idx; // start index of the data to be signed
public int data_len; // Length of the data to be signed
public byte[] signature; // 64-byte buffer where signature will be returned
}
public class ECDsaVerifyParams
{
public byte[] pubKey; // 64-byte public key (used for signature verification)
public byte[] buffer; // buffer containing a packet to be verified (MAC header | MAC data | Signature)
public int data_idx; // start index of the data to be verified
public int data_len; // Length of the data to be verified
public byte[] signature; // 64-byte buffer containing the signature to be verified
}
public class ECDHExchangeParams
{
public UInt32 endpoint_id; // 32-bit FlexNet ID
public byte[] pubKey_e; // 64-byte edpoint public key
public byte[] pubKey_r; // 64-byte RNI public key
public byte[] empKey; // 64-byte emphemeral key
public byte[] symKey; // 32-byte derived symmetric key
}
public static class FNv2_ECDsaP256_wSHA256
{
public static String VERSION = "160421-1.4";
public static byte _PRI_KEY_SIZE = 32;
public static byte _PUB_KEY_SIZE = 64;
public static byte _ECC_SIG_SIZE = 64;
public static byte _KEY_BLOB_HDR = 8;
// Version 1.0 - Released 03.06.2016 - Stephen Schamber - Sensus USA Inc
// Initial release to support ESCDaP256 w/ SHA-256 Ellipic Curve Cryptopgraphy Sign/Verify
// Supports ECDsa signature generation and verification
// Inludes method to parse/verify an ECDsa .ker.der file and extract the pub/pri key pair
// Version 1.1 - Released 04.01.2016 - Stephen Schamber - Sensus USA Inc
// Added ECDHExchange method to suport EC-DH key derivations
// Version 1.2 - Released 04.03.2016 - Stephen Schamber - Sensus USA Inc
// Use formatting function to generate symmetric key per FlexNet Asymmetric Encryption Spec
// Version 1.3 - Released 04.15.2016 - Stephen Schamber - Sensus USA Inc
// Extended the lengths in ECCVerify and ECCSign classes to int's
// Version 1.4 - Released 04.21.2016 - Stephen Schamber - Sensus USA Inc
// Changed .key file input format to PKCS8 to match Java generated keys/certs
// ERRORS......................................
public static byte ERR_NO_ERROR = 0x00;
public static byte ERR_INV_KEY_PAIR = 0x01;
public static byte ERR_INV_PUB_KEY = 0x02;
public static byte ERR_INV_SIG_LEN = 0x04;
public static byte ERR_FAIL_SIG_VERIFY = 0x10;
public static byte ERR_INV_DER_FILE = 0x20;
public static byte ERR_INV_DER_PARSE = 0x40;
/*********************************************************************************************************
**********************************************************************************************************
Name: ECDsaSign
Purpose: This method creates a 64-byte ESDsaP256 w/ SHA-256 digital signature on the data to be signed
PARAMETERS: ECDsaSignParams
(The signature will be returned in the ECDsaSignParams object)
RETURNS: 0 if sign is successful
> 0 if error checks on input parameters fail
********************************************************************************************************/
public static byte ECDsaSign(ECDsaSignParams ecdsa_s_p)
{
// Error Checks
byte error = ERR_NO_ERROR;
if (!ecdsa_s_p.keyPair.valid)
error |= ERR_INV_KEY_PAIR;
if (ecdsa_s_p.signature.Length != _ECC_SIG_SIZE)
error |= ERR_INV_SIG_LEN;
// Return if error
if (error > ERR_NO_ERROR) return error;
// Create the privKeyBlob from the key pair
byte[] privKeyBlob = new byte[_KEY_BLOB_HDR + _PRI_KEY_SIZE + _PUB_KEY_SIZE];
// Apend the Microsoft Key Storage Provider (MKSP) header for an EccPrivateBlob
privKeyBlob[0] = (byte)'E';
privKeyBlob[1] = (byte)'C';
privKeyBlob[2] = (byte)'S';
privKeyBlob[3] = (byte)'2';
privKeyBlob[4] = (byte)' ';
privKeyBlob[5] = 0x00;
privKeyBlob[6] = 0x00;
privKeyBlob[7] = 0x00;
// Copy public key after header
Array.Copy(ecdsa_s_p.keyPair.pubKey, 0, privKeyBlob, _KEY_BLOB_HDR, ecdsa_s_p.keyPair.pubKey.Length);
// Copy private key after public key
Array.Copy(ecdsa_s_p.keyPair.priKey, 0, privKeyBlob, _KEY_BLOB_HDR + _PUB_KEY_SIZE, ecdsa_s_p.keyPair.priKey.Length);
// Create a CngKey Object and import the priKeyBlob
CngKey ECDsakeyPair = CngKey.Import(privKeyBlob, CngKeyBlobFormat.EccPrivateBlob);
// Create the ECDsaCng object use the CngKey comtaining the key pair
using (ECDsaCng dsa = new ECDsaCng(ECDsakeyPair))
{
// ECDsaP256 with SHA256
dsa.HashAlgorithm = CngAlgorithm.Sha256;
// Generate Signature
ecdsa_s_p.signature = dsa.SignData(ecdsa_s_p.buffer, ecdsa_s_p.data_idx, ecdsa_s_p.data_len);
}
return ERR_NO_ERROR;
}
/*********************************************************************************************************
**********************************************************************************************************
Name: ECDsaVerify
Purpose: This method verifies a 64-byte ESDsaP256 w/ SHA-256 digital signature on signed data
PARAMETERS: ECDSaVerifyParams
RETURNS: 0 if verify is successful
> 0 if error checks on input parameters fail, or signature does not verify
********************************************************************************************************/
public static byte ECDsaVerify(ECDsaVerifyParams ecdsa_v_p)
{
byte error = ERR_NO_ERROR;
if (ecdsa_v_p.pubKey.Length != _PUB_KEY_SIZE)
error |= ERR_INV_PUB_KEY;
if (ecdsa_v_p.signature.Length != _ECC_SIG_SIZE)
error |= ERR_INV_SIG_LEN;
// Return if error
if (error > ERR_NO_ERROR) return error;
// Create the pubKeyBlob from the public key
byte[] pubKeyBlob = new byte[_KEY_BLOB_HDR + _PUB_KEY_SIZE];
// Apend the Microsoft Key Storage Provider (MKSP) header for an EccPublicBlob
pubKeyBlob[0] = (byte)'E';
pubKeyBlob[1] = (byte)'C';
pubKeyBlob[2] = (byte)'S';
pubKeyBlob[3] = (byte)'1';
pubKeyBlob[4] = (byte)' ';
pubKeyBlob[5] = 0x00;
pubKeyBlob[6] = 0x00;
pubKeyBlob[7] = 0x00;
// Copy public key after header
Array.Copy(ecdsa_v_p.pubKey, 0, pubKeyBlob, _KEY_BLOB_HDR, ecdsa_v_p.pubKey.Length);
// Create the ECDsaCng object and import a CngKey comtaining the public key
using (ECDsaCng dsa = new ECDsaCng(CngKey.Import(pubKeyBlob, CngKeyBlobFormat.EccPublicBlob)))
{
// ECDsaP256 with SHA256
dsa.HashAlgorithm = CngAlgorithm.Sha256;
// Verify Signature
return ((dsa.VerifyData(ecdsa_v_p.buffer, ecdsa_v_p.data_idx, ecdsa_v_p.data_len, ecdsa_v_p.signature)) ? ERR_NO_ERROR : ERR_FAIL_SIG_VERIFY);
}
}
/*********************************************************************************************************
**********************************************************************************************************
Name: ECDHExchange
Purpose: This method uses an endpoint public key and a generated key pair to derive a new symmetric key
PARAMETERS: ECDHExchangeParams
RETURNS: 0 if verify is successful
> 0 if error checks on input parameters fail, or signature does not verify
********************************************************************************************************/
public static byte ECDHExchange(ECDHExchangeParams ecdh_p)
{
byte error = ERR_NO_ERROR;
if (ecdh_p.pubKey_e.Length != _PUB_KEY_SIZE)
error |= ERR_INV_PUB_KEY;
if (ecdh_p.pubKey_r.Length != _PUB_KEY_SIZE)
error |= ERR_INV_PUB_KEY;
// Return if error
if (error > ERR_NO_ERROR) return error;
// Create the pubKeyBlob from the public key
byte[] pubKeyBlob = new byte[_KEY_BLOB_HDR + _PUB_KEY_SIZE];
// Apend the Microsoft Key Storage Provider (MKSP) header for an EccPublicBlob
pubKeyBlob[0] = (byte)'E';
pubKeyBlob[1] = (byte)'C';
pubKeyBlob[2] = (byte)'K';
pubKeyBlob[3] = (byte)'1';
pubKeyBlob[4] = (byte)' ';
pubKeyBlob[5] = 0x00;
pubKeyBlob[6] = 0x00;
pubKeyBlob[7] = 0x00;
// Copy the endpoint public key after header
Array.Copy(ecdh_p.pubKey_e, 0, pubKeyBlob, _KEY_BLOB_HDR, ecdh_p.pubKey_e.Length);
// Generate a random key pair to be used for the exchange
// ECC P256 Key Size
using (ECDiffieHellmanCng myEcdh = new ECDiffieHellmanCng(256))
{
// Ephemeral Key
// Remove ECK blob header and only return the ephemeral public key
Array.Copy(myEcdh.PublicKey.ToByteArray(), 8, ecdh_p.empKey, 0, myEcdh.PublicKey.ToByteArray().Length - 8);
// Derived Symmetric Key
myEcdh.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
myEcdh.HashAlgorithm = CngAlgorithm.Sha256;
// From the FlexNet Asymmetric Encryption Specification:
// KEP = SHA256( ‘b’32 0x00000001 (MSB First) || ‘b’256 Z || ‘b’64 RNI PubKey || ‘b’32 0x00000000 (MSB First) || ‘b’32 Meter FlexID (LSB First) || ‘b’24 0x000001 (MSB First))
myEcdh.SecretPrepend = new byte[] { 0x00, 0x00, 0x00, 0x01 };
byte[] append = new byte[19];
Array.Copy(ecdh_p.pubKey_r, 24, append, 0, 8);
Array.Copy(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0, append, 8, 4);
append[12] = (byte)(ecdh_p.endpoint_id);
append[13] = (byte)(ecdh_p.endpoint_id >> 8);
append[14] = (byte)(ecdh_p.endpoint_id >> 16);
append[15] = (byte)(ecdh_p.endpoint_id >> 24);
Array.Copy(new byte[] { 0x00, 0x00, 0x01 }, 0, append, 16, 3);
myEcdh.SecretAppend = append;
ecdh_p.symKey = myEcdh.DeriveKeyMaterial(CngKey.Import(pubKeyBlob, CngKeyBlobFormat.EccPublicBlob));
}
return ERR_NO_ERROR;
}
/*********************************************************************************************************
**********************************************************************************************************
Name: DERFileParseForKeys
Purpose: This method parses an ASN.1 binary key.der file, verifies format and extracts the key pair
This detects if the .key.der file is openssl format or PKCS8 format
PARAMETERS: DERFileName - a string containing the filename (full path) to key.der file
ECDsaKeyPair keyPair - the object where the extracted key pair will be returned
RETURNS: 0 if file format is correct and a valid key pair has been extracted
> 0 if file format is invalid or not the correct type and a key pair has not been extracted
********************************************************************************************************/
public static byte DERFileParseForKeys(String DERFileName, ECDsaKeyPair keyPair)
{
// Set the keyPair.valid to false
keyPair.valid = false;
// Check if the file exists
if (!File.Exists(DERFileName))
return ERR_INV_DER_FILE;
// Check if the file is ".key.der"
if (!DERFileName.Contains(".key.der"))
return ERR_INV_DER_FILE;
// Read all of the bytes from the file
byte[] rawDER = File.ReadAllBytes(DERFileName);
int derIdx = 0;
int oidIdx = 0;
int priKeyIdx = 0;
int pubKeyIdx = 0;
// Check for ASN.1 Start Sequence
if (rawDER[derIdx++] == 0x30)
{
// Openssl generated .key.der file?
if (rawDER[derIdx] == 0x77)
{
derIdx++;
priKeyIdx = 5;
oidIdx = priKeyIdx + 36;
pubKeyIdx = priKeyIdx + 48;
}
// PKCS8 file?
else if (rawDER[derIdx++] == 0x81 && rawDER[derIdx++] == 0x93)
{
priKeyIdx = 34;
oidIdx = priKeyIdx + 36;
pubKeyIdx = priKeyIdx + 48;
}
else
{
return ERR_INV_DER_PARSE;
}
}
derIdx = priKeyIdx;
// Check for ASN.1 octect string and correct private key length
if (rawDER[derIdx++] == 0x04 && rawDER[derIdx++] == _PRI_KEY_SIZE)
{
// Grab the private key and copy into keyPair.priKey
for (int i = 0; i < _PRI_KEY_SIZE; i++)
keyPair.priKey[i] = rawDER[derIdx++];
}
else
return ERR_INV_DER_PARSE;
byte[] prime256v1OID = { 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 };
// Check for ASN.1 OID Type (0x06) and valid encoded OID Len (0x08)
if (rawDER[derIdx++] == 0x06 && rawDER[derIdx++] == 0x08)
{
// Check for corrected ASN.1 encoded OID for "prime256v1" (1.2.840.10045.3.1.7)
for (int i = 0; i < prime256v1OID.Length; i++)
{
if (rawDER[derIdx++] != prime256v1OID[i])
return ERR_INV_DER_PARSE;
}
}
derIdx = pubKeyIdx;
// Check for ASN.1 bit string, correct public key length, zero pad-bits and non-compressed key field
if (rawDER[derIdx++] == 0x03 && rawDER[derIdx++] == 2 + _PUB_KEY_SIZE && rawDER[derIdx++] == 0 && rawDER[derIdx++] == 0x04)
{
// Grab the public key and copy into keyPair.pubKey
for (int i = 0; i < _PUB_KEY_SIZE; i++)
keyPair.pubKey[i] = rawDER[derIdx++];
}
else
return ERR_INV_DER_PARSE;
// Set the keyPair.valid to true
keyPair.valid = true;
return ERR_NO_ERROR;
}
}
}