I am trying to validate a signature returned by DS28C36 using OpenSSL in Linux. For testing purpose, I copied pasted public key and signature into global arrays. For signature conversion I followed this old post: Creating a DER formatted ECDSA signature from raw r and s However, when I converted it using i2d_ECDSA_SIG, it only return 2 bytes for some reasons and of course the following ECDSA_verify failed. In addition, when I tried to dump it using BN_bn2hex, I got a segmentation fault. I am not sure what I did wrong here.
Thanks for your help
This is my test code
#include <errno.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <resolv.h>
#include <openssl/obj_mac.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
unsigned char rom_id[] = {0x4c, 0x51, 0xb7, 0x12, 0x00, 0x00, 0x00, 0xfa};
unsigned char man_id[] = {0x00, 0x80};
unsigned char pub_key_raw[] = { 0x04,
0x34, 0xe3, 0x12, 0x1d, 0x73, 0xc4, 0x25, 0x68, 0xef, 0xf8, 0xcc, 0x89, 0xc7, 0x77, 0x9c, 0x4e,
0x16, 0xe7, 0x79, 0xd5, 0x76, 0x68, 0x0e, 0xff, 0xad, 0x1f, 0x53, 0xd6, 0x33, 0x70, 0xb8, 0xa3,
0xa5, 0xb9, 0x92, 0xc9, 0x3c, 0x75, 0xf7, 0x07, 0xbf, 0xed, 0xe1, 0x25, 0xbf, 0x1f, 0xcb, 0xeb,
0x30, 0x57, 0x4f, 0x05, 0x96, 0x28, 0x9f, 0x66, 0x33, 0x7a, 0x46, 0xb6, 0x76, 0x35, 0x8c, 0xdd};
unsigned char challenge[] = {0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc};
unsigned char usr_page3[] = {
0xB6, 0xB5, 0x88, 0x2C, 0xE1, 0xC3, 0x4F, 0x2B, 0x56, 0x36, 0xD7, 0xE3, 0x22, 0x5E, 0x3B, 0x34,
0x5D, 0x33, 0xD6, 0x7F, 0xEC, 0x1C, 0x2D, 0x45, 0x86, 0xFD, 0x14, 0xE6, 0x05, 0x39, 0x07, 0xF9};
unsigned char sig_s[] = {
0x18, 0x4e, 0xbf, 0xa0, 0x9b, 0xe6, 0xeb, 0x66, 0x42, 0x1f, 0x01, 0x9e, 0xb8, 0xf5, 0xf5, 0x18,
0x41, 0x34, 0x37, 0xd8, 0x20, 0x6e, 0x1d, 0x69, 0x94, 0x05, 0x15, 0x98, 0xa8, 0x64, 0x90, 0x1e};
unsigned char sig_r[] = {
0x8c, 0xf0, 0x3e, 0xb2, 0x51, 0x87, 0xb2, 0x5c, 0x78, 0x98, 0x71, 0xd9, 0x57, 0x5b, 0xdf, 0x13,
0x6e, 0x1c, 0xcd, 0x18, 0xba, 0xee, 0xec, 0x5e, 0xac, 0xbe, 0x95, 0xf1, 0x2f, 0x88, 0xaa, 0xa2};
void hexdump(void* buf, size_t length) {
unsigned char* p = (unsigned char*)buf;
unsigned char c;
int i = 0;
while (i < length) {
printf("%04x: ", i);
for (int j = 0; j < 16; j++) {
if (i + j < length) {
c = *(p + i + j);
printf("%02x ", c);
} else {
printf(" ");
}
}
printf(" ");
for (int j = 0; j < 16; j++) {
if (i + j < length) {
c = *(p + i + j);
if (c >= 32 && c <= 126) {
printf("%c", c);
} else {
printf(".");
}
} else {
printf(" ");
}
}
printf("\n");
i += 16;
}
}
int main(int argc, char **argv)
{
unsigned char *sig_ptr;
char *s_r, *s_s;
int param, msg_len, sig_len;
unsigned char message[256];
unsigned char hash[SHA256_DIGEST_LENGTH], signature_der[256];
EC_GROUP *g;
EC_POINT *p;
EC_KEY *k;
SHA256_CTX sha256;
ECDSA_SIG* ec_sig = ECDSA_SIG_new();
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
// seed the random number generator
srand((unsigned)time(NULL));
//Some initialization works
k = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_set_conv_form(k, POINT_CONVERSION_UNCOMPRESSED);
g = EC_KEY_get0_group(k);
p = EC_POINT_new(g);
//Load EC points
if (1 != EC_POINT_oct2point(g, p, pub_key_raw, 65, NULL)){
printf("EC_POINT_oct2point failed:\n");
printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
}
//Set EC public key
if (1 != EC_KEY_set_public_key(k, p)){
printf("EC_KEY_set_public_key failed:\n");
printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
}
//Check EC public key
if (1 != EC_KEY_check_key(k)) {
printf("EC_KEY_check_key failed:\n");
printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
}
else {
printf("Public key verified OK\n");
}
printf(" Page Data: ");
for (int i = 0; i < 32; i++){
printf("%02X", usr_page3[i]);
}
printf("\n");
hexdump(usr_page3, 32);
printf("\n Challenge: ");
for (int i = 0; i < 8; i++){
printf("%02X",challenge[i]);
}
printf("\n");
hexdump(challenge, 8);
// ROM NO
msg_len = 0;
memcpy(&message[msg_len],rom_id,8);
msg_len += 8;
// Page Data
memcpy(&message[msg_len], usr_page3, 32);
msg_len += 32;
// Challenge (Buffer)
memcpy(&message[msg_len], challenge, 32);
msg_len += 32;
// Page#
message[msg_len++] = 3;//
// MANID
memcpy(&message[msg_len], man_id, 2);
msg_len += 2;
// Calculate Hash
SHA256_Init(&sha256);
SHA256_Update(&sha256, message, strlen(message));
SHA256_Final(hash, &sha256);
if (NULL == BN_bin2bn((unsigned char*)sig_r, 32, ECDSA_SIG_get0_r(ec_sig))) {
printf("BN_bin2bn for r failed:\n");
printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
}
if (NULL == BN_bin2bn((unsigned char*)sig_s, 32, ECDSA_SIG_get0_s(ec_sig))) {
printf("BN_bin2bn for s failed:\n");
printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
}
s_r = BN_bn2hex(ECDSA_SIG_get0_r(ec_sig));
s_s = BN_bn2hex(ECDSA_SIG_get0_s(ec_sig));
printf("(sig->r, sig->s): (%s,%s)\n", s_r, s_s);
sig_ptr = signature_der;
sig_len = i2d_ECDSA_SIG(ec_sig, &sig_ptr);
printf("converted signature length = %d\n", sig_len);
// Validate ECDSA signature
if (1 != ECDSA_verify(0, hash, sizeof(hash), signature_der, sig_len, k)) {
printf("ECDSA_verify failed:\n");
printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
printf("Signature:\n");
hexdump(signature_der, sizeof(signature_der));
}
}
Preliminarily, you compute your hash incorrectly. First you (try to) copy 32 bytes from challenge
which contains only 8 bytes. Technically this is Undefined Behavior and the implementation is permitted to do anything, including destroy the Earth -- but most don't; in practice the usual result is either to crash or to use garbage data, and since you didn't observe a crash here, your implementation probably did the latter. Second you then use strlen(message)
as the length of your data, but it isn't, because the garbage from challenge
per above probably contains a null byte, and if not, man_id
certainly does.
However this would only cause the signature verification to fail, not the symptoms you are asking about. For them:
ECDSA_SIG* ec_sig = ECDSA_SIG_new();
...
if (NULL == BN_bin2bn((unsigned char*)sig_r, 32, ECDSA_SIG_get0_r(ec_sig))) {
printf("BN_bin2bn for r failed:\n");
printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
}
This creates a new ECDSA_SIG object, but no actual signature, so its R and S fields are empty (null). When you call _get0_r it simply returns the field without checking, and BN_bin2bn(buffer,len,null) creates a new BN object to contain the number which it returns (as a pointer) but you immediately discard. In short, this does NOT put the R value in ec_sig.
if (NULL == BN_bin2bn((unsigned char*)sig_s, 32, ECDSA_SIG_get0_s(ec_sig))) {
printf("BN_bin2bn for s failed:\n");
printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
}
Ditto for S.
s_r = BN_bn2hex(ECDSA_SIG_get0_r(ec_sig));
s_s = BN_bn2hex(ECDSA_SIG_get0_s(ec_sig));
printf("(sig->r, sig->s): (%s,%s)\n", s_r, s_s);
If you do this, it tries to treat the null pointers still in ec_sig as BN objects and print them, but as null pointers are not actual objects it crashes.
sig_ptr = signature_der;
sig_len = i2d_ECDSA_SIG(ec_sig, &sig_ptr);
If you do this (and not the previous), it tries to encode a "signature" that is not actually a signature, producing the bogus 2-byte encoding you observed.
You should instead do something like:
ECDSA_SIG *ec_sig = ECDSA_SIG_new();
BN *rbn = BN_bin2bn(rval,sizeof(rval),NULL);
BN *sbn = BN_bin2bn(sval,sizeof(sval),NULL);
// best style is to check and handle if r and/or s is NULL
// but these calls can't actually fail in any sane system
(void) ECDSA_SIG_set0(ec_sig,rbn,sbn);
// again you can check for return value != 1, but it won't happen
and of course correct the hash computation.
Aside: when passing an unsigned char []
variable (like your sig_r
or sig_s
) to a const unsigned char *
parameter (like BN_bin2bn
takes) you don't need a cast: any occurrence of an lvalue with type [qualified] array of T
other than as the operand of sizeof offsetof _Alignof
'decays' (is automatically converted) to pointer to [qualified] T
, and any pointer to unqualified T can automatically 'add' qualification const
and/or volatile
. Thus sig_r
with type unsigned char[32]
automatically becomes unsigned char *
and then const unsigned char *
as needed. This is extremely basic to programming in C (and the 'C subset' of C++, if you don't switch entirely to std::vector
and the like) and should be covered within the first two weeks of any course, or two chapters of any text.