361 lines
16 KiB
C#
361 lines
16 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|