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; } } }