Search code examples
javascriptarraysencryptionwebcrypto-api

Unable to verify a raw UInt8Array ECDSA (secp256r1) message/signature/pubkey using WebCrypto API (subtlecrypto), generated from secure embedded chip


I am using a secure embedded chip (ATECC508) to generate an ECDSA message/signature combination using the secp256r1 curve.

The information from the chip outputs in Uint8Arrays: message[32], signature[64], and publickey[64]; The public key is in raw byte format, giving both X/Y coordinates in a 64 byte array, without the 0x04 padding on the front.

The public key will only successfully import into WebCrypto as long as I pad the 64 byte array with 0x04 on the front.

However, when I attempt to verify the message and signature, it always fails.

I created a simulator script to help debug. It uses WebCrypto to generate the message, signature, and public key instead of the crypto chip. The simulator generates a 32 byte Uint8array message array, and signs the message returning a 64byte Uint8array for its signature. The public key that WebCrypto generates is also verified to have the 0x04 padding in the front, before the 64bytes representing the X/Y coordinates of the curve. This means I believe that the information generated by the simulator should be in the exact same format as what the chip provides.

Any message, signature and public key which is generated from WebCrypto can be successfully verified...but I cant authenticate the information from the chip...it is always unsuccessful.

I have no reason to suspect the values from the chip are wrong, and I have generated many messages/signatures from the chip...none of them work. I have also tried a second chip with the same result.

Output example from chip (does not verify):

uint8_t message[32] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
};

uint8_t signature[64] = {
0xD6,0x82,0x25,0xCC,0x68,0x6F,0x4F,0x84,0x91,0x48,0x63,0x6E,0x67,0x3C,0xD4,0xC0,0xF8,0xE5,0x9D,0x7B,0xAD,0x6B,0xB3,0xF1,0x1C,0xDB,0x90,0xB7,0x1A,0x5E,0x43,0xCF,0xD8,0xC3,0x8C,0x77,0x74,0xE2,0xA0,0x29,0xFF,0x43,0x22,0x7D,0xF9,0x41,0x56,0x12,0x8A,0x1B,0xEA,0x4D,0x57,0x8A,0x37,0x9C,0x6A,0x85,0x0A,0x56,0xBE,0xEC,0x1A,0x69
};

uint8_t publicKey[64] = {
0x39,0xC3,0xDD,0x74,0x13,0x17,0x29,0x44,0x6D,0xC1,0xB3,0xDA,0x67,0xD4,0x9F,0xC0,
0x46,0xFC,0xBF,0x07,0x2F,0xCC,0x5B,0x9F,0xA5,0x1C,0x05,0xB9,0x74,0x30,0x7F,0x96,
0x9C,0x40,0x3B,0x16,0x35,0xF0,0x44,0x9F,0x02,0xBD,0x42,0x27,0x51,0xE3,0x31,0x21,
0xA4,0x43,0x4F,0x15,0x2F,0x2B,0x2B,0x2A,0x3F,0x67,0x52,0x19,0xC5,0xD9,0x25,0xF6,
};

Note: The public key above is padded with 0x04 before importing, otherwise WebCrypto throws an error.

Output example from WebCrypto simulator javascript console (this verifies OK):

Message to import in hex: 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
Message Uint8Array conversion: Uint8Array(32) [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, … ]

the signature in hex: 5d4575a19cad9a56b554bafbb1db212d8badd48b5d29f6556fa33f0e8650175f48157e70377c7aa5f97e55bc455b6520060dc86f32a00577a254a97e1b7b394c
the signature buffer: 
Uint8Array(64) [ 93, 69, 117, 161, 156, 173, 154, 86, 181, 84, … ]

public key in hex:
04d18669eb64b54118fbcdacbf79d2f185383abadb1982382a51675650f4596d9f917a7b9b215f2424418b4e7c824500dc29ea507f08b4ef51ca35f58de75ca274

note: above are hex strings, but I am converting to a UintArray8 before calling WebCrypto API using. PublicKey is converted into the correct CryptoKey() type before using to verify.

The javascript code I am using for verification:

   let result = await window.crypto.subtle.verify({
            name: "ECDSA",
            hash: { name: "SHA-256" }
        },
        publicKey, sigbuffer, msgbuffer);

And finally, the import:

return window.crypto.subtle.importKey(
        'raw',
        key, {
            name: 'ECDSA',
            namedCurve: 'P-256'
        },
        true, ["verify"]
    );

My intention is to be able to import and verify the information from the chip successfully into the WebCrypto API, but I have so far been unsuccessful. At the moment I am completely stuck and would be extremely grateful for any help...thanks in advance


Solution

  • Verification of the posted data is successful if the unhashed data is verified (I tested this with a C# solution, see online here). In conclusion, this proves that the unhashed data was signed.

    However, signing the unhashed data is uncommon, generally the hashed data is signed. On the one hand, this has purely practical reasons (asymmetric algorithms can generally only be used to encrypt short messages), but on the other hand it is also necessary for security reasons, s. e.g. here.

    For this reason, many libraries perform hashing implicitly, which is why a digest must be specified. This is also true for the WebCrypto API, causing the hashed data to be verified with a signature generated for the unhashed data, which of course fails.

    It is now also not possible to derive data whose hash just generates your message in order to then use this data as input for the WebCrypto API. Cryptographic hashes are supposed to prevent such an inference.

    For these reasons, the data cannot be verified using the WebCrypto API!

    So you need a JavaScript library that does not hash implicitly, e.g. the SJCL:

    var pubHex = '39c3dd74131729446dc1b3da67d49fc046fcbf072fcc5b9fa51c05b974307f969c403b1635f0449f02bd422751e33121a4434f152f2b2b2a3f675219c5d925f6';
    var pub = new sjcl.ecc.ecdsa.publicKey(
        sjcl.ecc.curves.c256, 
        sjcl.codec.hex.toBits(pubHex)
    )
    
    var msgHex = '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f';
    var msg = sjcl.codec.hex.toBits(msgHex)
    
    var sigHex = 'd68225cc686f4f849148636e673cd4c0f8e59d7bad6bb3f11cdb90b71a5e43cfd8c38c7774e2a029ff43227df94156128a1bea4d578a379c6a850a56beec1a69';
    var sig = sjcl.codec.hex.toBits(sigHex)
    
    var verified = false;
    try {
        var verified = pub.verify(msg, sig)
    } catch (e) {}
    console.log('Verification:', verified)
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/sjcl.min.js"></script>

    With this, the posted data is successfully verified.