Skip to content

Commit

Permalink
Need implementation of an openSSL based ECDSA signing using NIST P256…
Browse files Browse the repository at this point in the history
…v1 (project-chip#421)

* Need implementation of an openSSL based ECDSA signing using NIST P256v1 project-chip#255

* Restyled by clang-format

* Fix crypto test in Travis CI

* PR Feedback. Add a new test to test signing using SHA256

* PR Feedback - use SHA256. Limit to P56v1 curve. change API to let caller pass in the signature buffer

* Update min signature buffer size

* Update msg signature buffer length.

* Update msg signature buffer length.

* Delete unused method

* Update comments

* Rename kMinimum_ECDSA_Signature_Buffer_Length to kMax_ECDSA_Signature_Length

* Update API comments

Co-authored-by: Bhaskar Sarma <bhaskars@apple.com>
Co-authored-by: Restyled.io <commits@restyled.io>
  • Loading branch information
3 people authored Apr 22, 2020
1 parent d89d0a3 commit 6235e2e
Show file tree
Hide file tree
Showing 6 changed files with 484 additions and 18 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ config.log
config.status
configure
src/include/BuildConfig.h.in
src/include/BuildConfig.h.in~

# Repos stuff
.repos-warning-stamp
Expand Down
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"preLaunchTask": "Build & Run Crypto Tests",
"preLaunchTask": "Build openSSL crypto Tests",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
Expand Down
17 changes: 14 additions & 3 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,18 @@
"command": "make -C examples/lock-app/nrf5",
"group": "none",
"dependsOn": "Clean Tree",
"problemMatcher": ["$gcc"]
}
]
"problemMatcher": [
"$gcc"
]
},
{
"label": "Build openSSL crypto Tests",
"type": "shell",
"command": " make -C build/default/src/crypto/ && make -C build/default/src/crypto/tests/ TestOpenSSLCrypto",
"group": "none",
"problemMatcher": [
"$gcc"
]
},
],
}
33 changes: 33 additions & 0 deletions src/crypto/CHIPCryptoPAL.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
namespace chip {
namespace Crypto {

const size_t kMax_ECDSA_Signature_Length = 72;

/**
* @brief A function that implements 256-bit AES-CCM encryption
* @param plaintext Plaintext to encrypt
Expand Down Expand Up @@ -91,6 +93,37 @@ CHIP_ERROR HKDF_SHA256(const unsigned char * secret, const size_t secret_length,
**/
CHIP_ERROR DRBG_get_bytes(unsigned char * out_buffer, const size_t out_length);

/**
* @brief A function to sign a msg using ECDSA
* @param msg Message that needs to be signed
* @param msg_length Length of message
* @param private_key Key to use to sign the message. Private keys are ASN.1 DER encoded as padded big-endian field elements as
*described in SEC 1: Elliptic Curve Cryptography [https://www.secg.org/sec1-v2.pdf]
* @param private_key_length Length of private key
* @param out_signature Buffer that will hold the output signature. The signature consists of: 2 EC elements (r and s), represented
*as ASN.1 DER integers, plus the ASN.1 sequence Header
* @param out_signature_length Length of out buffer
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
CHIP_ERROR ECDSA_sign_msg(const unsigned char * msg, const size_t msg_length, const unsigned char * private_key,
const size_t private_key_length, unsigned char * out_signature, size_t & out_signature_length);

/**
* @brief A function to sign a msg using ECDSA
* @param msg Message that needs to be signed
* @param msg_length Length of message
* @param public_key Key to use to verify the message signature. Public keys are ASN.1 DER encoded as uncompressed points as
*described in SEC 1: Elliptic Curve Cryptography [https://www.secg.org/sec1-v2.pdf]
* @param private_key_length Length of public key
* @param signature Signature to use for verification. The signature consists of: 2 EC elements (r and s), represented as ASN.1 DER
*integers, plus the ASN.1 sequence Header
* @param signature_length Length of signature
* @return Returns a CHIP_NO_ERROR on successful verification, a CHIP_ERROR otherwise
**/
CHIP_ERROR ECDSA_validate_msg_signature(const unsigned char * msg, const size_t msg_length, const unsigned char * public_key,
const size_t public_key_length, const unsigned char * signature,
const size_t signature_length);
} // namespace Crypto
} // namespace chip

#endif
251 changes: 251 additions & 0 deletions src/crypto/CHIPCryptoPALOpenSSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,42 @@

#include <openssl/conf.h>
#include <openssl/rand.h>
#include <openssl/ecdsa.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/kdf.h>
#include <openssl/ossl_typ.h>
#include <support/CodeUtils.h>
#include <string.h>

#define kKeyLengthInBits 256

enum class DigestType
{
SHA256
};

enum class ECName
{
P256v1
};

using namespace chip::Crypto;

static int _nidForCurve(ECName name)
{
switch (name)
{
case ECName::P256v1:
return EC_curve_nist2nid("P-256");
break;

default:
return NID_undef;
break;
}
}

static bool _isValidTagLength(size_t tag_length)
{
if (tag_length == 8 || tag_length == 12 || tag_length == 16)
Expand All @@ -41,6 +69,36 @@ static bool _isValidTagLength(size_t tag_length)
return false;
}

static void _logSSLError()
{
int ssl_err_code = ERR_get_error();
while (ssl_err_code != 0)
{
const char * err_str_lib = ERR_lib_error_string(ssl_err_code);
const char * err_str_routine = ERR_func_error_string(ssl_err_code);
const char * err_str_reason = ERR_reason_error_string(ssl_err_code);
if (err_str_lib)
{
printf("\nssl err %s %s %s\n", err_str_lib, err_str_routine, err_str_reason);
}
ssl_err_code = ERR_get_error();
}
}

static const EVP_MD * _digestForType(DigestType digestType)
{
switch (digestType)
{
case DigestType::SHA256:
return EVP_sha256();
break;

default:
return NULL;
break;
}
}

CHIP_ERROR chip::Crypto::AES_CCM_256_encrypt(const unsigned char * plaintext, size_t plaintext_length, const unsigned char * aad,
size_t aad_length, const unsigned char * key, const unsigned char * iv,
size_t iv_length, unsigned char * ciphertext, unsigned char * tag, size_t tag_length)
Expand Down Expand Up @@ -251,3 +309,196 @@ CHIP_ERROR chip::Crypto::DRBG_get_bytes(unsigned char * out_buffer, const size_t
exit:
return error;
}

CHIP_ERROR chip::Crypto::ECDSA_sign_msg(const unsigned char * msg, const size_t msg_length, const unsigned char * private_key,
const size_t private_key_length, unsigned char * out_signature,
size_t & out_signature_length)
{
ERR_clear_error();
static_assert(kMax_ECDSA_Signature_Length >= 72, "ECDSA signature buffer length is too short");

CHIP_ERROR error = CHIP_NO_ERROR;
int result = 0;
EVP_MD_CTX * context = NULL;
int nid = NID_undef;
EC_KEY * ec_key = NULL;
EVP_PKEY * signing_key = NULL;
char * _hexKey = NULL;
BIGNUM * pvt_key = NULL;
const EVP_MD * md = NULL;
ECName curve_name = ECName::P256v1;
DigestType digest = DigestType::SHA256;
size_t out_length = 0;

VerifyOrExit(msg != NULL, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(msg_length > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
nid = _nidForCurve(curve_name);
VerifyOrExit(nid != NID_undef, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(private_key != NULL, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(private_key_length > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(out_signature != NULL, error = CHIP_ERROR_INVALID_ARGUMENT);
md = _digestForType(digest);
VerifyOrExit(md != NULL, error = CHIP_ERROR_INVALID_ARGUMENT);

ec_key = EC_KEY_new_by_curve_name(nid);
VerifyOrExit(ec_key != NULL, error = CHIP_ERROR_INTERNAL);

pvt_key = BN_bin2bn(private_key, private_key_length, pvt_key);
VerifyOrExit(pvt_key != NULL, error = CHIP_ERROR_INTERNAL);

result = EC_KEY_set_private_key(ec_key, pvt_key);
VerifyOrExit(result == 1, error = CHIP_ERROR_INTERNAL);

signing_key = EVP_PKEY_new();
VerifyOrExit(signing_key != NULL, error = CHIP_ERROR_INTERNAL);

result = EVP_PKEY_set1_EC_KEY(signing_key, ec_key);
VerifyOrExit(result == 1, error = CHIP_ERROR_INTERNAL);

context = EVP_MD_CTX_create();
VerifyOrExit(context != NULL, error = CHIP_ERROR_INTERNAL);

result = EVP_DigestSignInit(context, NULL, md, NULL, signing_key);
VerifyOrExit(result == 1, error = CHIP_ERROR_INTERNAL);

result = EVP_DigestSignUpdate(context, msg, msg_length);
VerifyOrExit(result == 1, error = CHIP_ERROR_INTERNAL);

// Call the EVP_DigestSignFinal with a NULL param to get length of the signature.

result = EVP_DigestSignFinal(context, NULL, &out_length);
VerifyOrExit(result == 1, error = CHIP_ERROR_INTERNAL);
VerifyOrExit(out_signature_length >= out_length, error = CHIP_ERROR_INVALID_ARGUMENT);

result = EVP_DigestSignFinal(context, out_signature, &out_length);
VerifyOrExit(result == 1, error = CHIP_ERROR_INTERNAL);
// This should not happen due to the check above. But check this nonetheless
VerifyOrExit(out_signature_length >= out_length, error = CHIP_ERROR_INTERNAL);
out_signature_length = out_length;

exit:
if (ec_key != NULL)
{
EC_KEY_free(ec_key);
ec_key = NULL;
}

if (context != NULL)
{
EVP_MD_CTX_destroy(context);
context = NULL;
}
if (signing_key != NULL)
{
EVP_PKEY_free(signing_key);
signing_key = NULL;
}

if (error != CHIP_NO_ERROR)
{
_logSSLError();
}

if (_hexKey != NULL)
{
free(_hexKey);
}

return error;
}

CHIP_ERROR chip::Crypto::ECDSA_validate_msg_signature(const unsigned char * msg, const size_t msg_length,
const unsigned char * public_key, const size_t public_key_length,
const unsigned char * signature, const size_t signature_length)
{
ERR_clear_error();
CHIP_ERROR error = CHIP_ERROR_INTERNAL;
int nid = NID_undef;
const EVP_MD * md = NULL;
EC_KEY * ec_key = NULL;
EVP_PKEY * verification_key = NULL;
EC_POINT * key_point = NULL;
EC_GROUP * ec_group = NULL;
int result = 0;
EVP_MD_CTX * md_context = NULL;
ECName curve_name = ECName::P256v1;
DigestType digest = DigestType::SHA256;

VerifyOrExit(msg != NULL, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(msg_length > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
nid = _nidForCurve(curve_name);
VerifyOrExit(nid != NID_undef, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(public_key != NULL, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(public_key_length > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(signature != NULL, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(signature_length > 0, error = CHIP_ERROR_INVALID_ARGUMENT);

md = _digestForType(digest);
VerifyOrExit(md != NULL, error = CHIP_ERROR_INVALID_ARGUMENT);

ec_group = EC_GROUP_new_by_curve_name(nid);
VerifyOrExit(ec_group != NULL, error = CHIP_ERROR_INTERNAL);

key_point = EC_POINT_new(ec_group);
VerifyOrExit(key_point != NULL, error = CHIP_ERROR_INTERNAL);

result = EC_POINT_oct2point(ec_group, key_point, public_key, public_key_length, NULL);
VerifyOrExit(result == 1, error = CHIP_ERROR_INTERNAL);

ec_key = EC_KEY_new_by_curve_name(nid);
VerifyOrExit(ec_key != NULL, error = CHIP_ERROR_INTERNAL);

result = EC_KEY_set_public_key(ec_key, key_point);
VerifyOrExit(result == 1, error = CHIP_ERROR_INTERNAL);

result = EC_KEY_check_key(ec_key);
VerifyOrExit(result == 1, error = CHIP_ERROR_INTERNAL);

verification_key = EVP_PKEY_new();
VerifyOrExit(verification_key != NULL, error = CHIP_ERROR_INTERNAL);

result = EVP_PKEY_set1_EC_KEY(verification_key, ec_key);
VerifyOrExit(result == 1, error = CHIP_ERROR_INTERNAL);

md_context = EVP_MD_CTX_create();
VerifyOrExit(md_context != NULL, error = CHIP_ERROR_INTERNAL);

result = EVP_DigestVerifyInit(md_context, NULL, md, NULL, verification_key);
VerifyOrExit(result == 1, error = CHIP_ERROR_INTERNAL);

result = EVP_DigestVerifyUpdate(md_context, msg, msg_length);
VerifyOrExit(result == 1, error = CHIP_ERROR_INTERNAL);

result = EVP_DigestVerifyFinal(md_context, signature, signature_length);
VerifyOrExit(result == 1, error = CHIP_ERROR_INVALID_SIGNATURE);
error = CHIP_NO_ERROR;

exit:
_logSSLError();
if (ec_group != NULL)
{
EC_GROUP_free(ec_group);
ec_group = NULL;
}
if (key_point != NULL)
{
EC_POINT_clear_free(key_point);
key_point = NULL;
}
if (md_context)
{
EVP_MD_CTX_destroy(md_context);
md_context = NULL;
}
if (ec_key != NULL)
{
EC_KEY_free(ec_key);
ec_key = NULL;
}
if (verification_key != NULL)
{
EVP_PKEY_free(verification_key);
verification_key = NULL;
}
return error;
}
Loading

0 comments on commit 6235e2e

Please sign in to comment.