Yubico/yubico-piv-tool

File signing fails when using OpenSSL EC private key

nzedler opened this issue · 14 comments

What I'm trying to achieve:

Use an OpenSSL Elliptic Curve (EC) private key with the proper certificate on the YubiKey for signing. That means using the following command with the ECCP256 / ECCP384 algorithm parameter:
yubico-piv-tool -a verify-pin --sign -s 9a -H SHA512 -A ECCP384 -i data.txt -o data.sig

Vs. What I get:

failed signing file
Failed signing!

This happens when using a OpenSSL prime curve (e.g. prime256v1) and a OpenSSL secp curve (e.g. secp384r1).
Using OpenSSL RSA like RSA 2048 works flawlessly.

Steps to reproduce:

Create CA:

openssl ecparam -name secp384r1 -genkey -noout -out ca_private_key.pem
openssl req -x509 -new -nodes -extensions v3_ca -key ca_private_key.pem -days 1024 -out ca-root.cer -sha512

Secp384r1 - Works not

YubiKey only supports EC keys with 256 or 384 bit, not 521 bit as stated in Key Generation.
Create certificate with e.g. secp384r1 key:
openssl ecparam -name secp384r1 -genkey -noout -out secp384r1_client_private_key.pem
openssl req -new -key secp384r1_client_private_key.pem -out secp384r1_client_req.csr -sha512
openssl x509 -req -in secp384r1_client_req.csr -CA ca-root.cer -CAkey ca_private_key.pem -CAcreateserial -out secp384r1_client.cer -days 365 -sha512

Import priv. key and cert on e.g. slot 9a:
yubico-piv-tool -a import-key -s 9a -k -i secp384r1_client_private_key.pem -K PEM
yubico-piv-tool -a import-certificate -s 9a -k -i secp384r1_client.cer -K PEM

Sign a string, verbous (v3) output:
echo "Hello World" > data.txt
yubico-piv-tool -a verify-pin --sign -s 9b -H SHA512 -A ECCP384 -i data.txt -o data.sig -v3
Output:

DBG ykpiv.c:589 (ykpiv_connect): Connect reader 'Yubico YubiKey OTP+FIDO+CCID 00 00' matching 'Yubikey'.
DBG ykpiv.c:595 (ykpiv_connect): SCardConnect succeeded for 'Yubico YubiKey OTP+FIDO+CCID 00 00', protocol=2
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 11 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 00a4040005a00000030800 (11)
DBG ykpiv.c:766 (_ykpiv_transmit): < 61114f0600001000010079074f05a0000003089000 (21)
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 5 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 0020008000 (5)
DBG ykpiv.c:766 (_ykpiv_transmit): < 63c3 (2)
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 5 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 00fd000000 (5)
DBG ykpiv.c:766 (_ykpiv_transmit): < 0504039000 (5)
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 5 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 00f8000000 (5)
DBG ykpiv.c:766 (_ykpiv_transmit): < 013865ba9000 (6)
Now processing for action 'verify-pin'.
Action 'verify-pin' does not need authentication.
Enter PIN: 
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 14 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 0020008008313233343536ffff00 (14)
DBG ykpiv.c:766 (_ykpiv_transmit): < 9000 (2)
Successfully verified PIN.
file hashed as: e1 c1 12 ff 90 8f eb c3 b9 8b 16 93 a6 cd 35 64 ea f8 e5 e6 ca 62 9d 08 4d 9f 0e ba 99 24 7c ac dd 72 e3 69 ff 89 41 39 7c 28 07 40 9f f6 6b e6 4b e9 08 da 17 ad 7b 8a 49 a2 a2 6c 0e 80 86 aa 
failed signing file
Failed signing!
DBG ykpiv.c:344 (ykpiv_disconnect): Disconnect card #20473274.

Prime256v1 - Works not

Create certificate with prime256v1 key (example on yubico website)
openssl ecparam -name prime256v1 -genkey -noout -out prime256v1_client_private_key.pem
openssl req -new -key prime256v1_client_private_key.pem -out prime256v1_client_req.csr -sha512
openssl x509 -req -in prime256v1_client_req.csr -CA ca-root.cer -CAkey ca_private_key.pem -CAcreateserial -out prime256v1_client.cer -days 365 -sha512

Import priv. key and cert on e.g. slot 9a:
yubico-piv-tool -a import-key -s 9a -k -i prime256v1_client_private_key.pem -K PEM
yubico-piv-tool -a import-certificate -s 9a -k -i prime256v1_client.cer -K PEM

Sign a string, verbous (v3) output:
echo "Hello World" > data.txt
yubico-piv-tool -a verify-pin --sign -s 9a -H SHA512 -A ECCP256 -i data.txt -o data.sig -v3
Output:

DBG ykpiv.c:589 (ykpiv_connect): Connect reader 'Yubico YubiKey OTP+FIDO+CCID 00 00' matching 'Yubikey'.
DBG ykpiv.c:595 (ykpiv_connect): SCardConnect succeeded for 'Yubico YubiKey OTP+FIDO+CCID 00 00', protocol=2
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 11 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 00a4040005a00000030800 (11)
DBG ykpiv.c:766 (_ykpiv_transmit): < 61114f0600001000010079074f05a0000003089000 (21)
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 5 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 0020008000 (5)
DBG ykpiv.c:766 (_ykpiv_transmit): < 63c3 (2)
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 5 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 00fd000000 (5)
DBG ykpiv.c:766 (_ykpiv_transmit): < 0504039000 (5)
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 5 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 00f8000000 (5)
DBG ykpiv.c:766 (_ykpiv_transmit): < 013865ba9000 (6)
Now processing for action 'verify-pin'.
Action 'verify-pin' does not need authentication.
Enter PIN: 
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 14 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 0020008008313233343536ffff00 (14)
DBG ykpiv.c:766 (_ykpiv_transmit): < 9000 (2)
Successfully verified PIN.
file hashed as: e1 c1 12 ff 90 8f eb c3 b9 8b 16 93 a6 cd 35 64 ea f8 e5 e6 ca 62 9d 08 4d 9f 0e ba 99 24 7c ac dd 72 e3 69 ff 89 41 39 7c 28 07 40 9f f6 6b e6 4b e9 08 da 17 ad 7b 8a 49 a2 a2 6c 0e 80 86 aa 
failed signing file
Failed signing!
DBG ykpiv.c:344 (ykpiv_disconnect): Disconnect card #20473274.

RSA 2048 - Works

Create certificate with RSA 2048 key (example on yubico website)
openssl genrsa -out rsa_client_private_key.pem 2048
openssl req -new -key rsa_client_private_key.pem -out rsa_client_req.csr -sha512
openssl x509 -req -in rsa_client_req.csr -CA ca-root.cer -CAkey ca_private_key.pem -CAcreateserial -out rsa_client.cer -days 365 -sha512

Import priv. key and cert on e.g. slot 9a:
yubico-piv-tool -a import-key -s 9a -k -i rsa_client_private_key.pem -K PEM
yubico-piv-tool -a import-certificate -s 9a -k -i rsa_client.cer -K PEM

Sign a string, verbous (v3) output:
echo "Hello World" > data.txt
yubico-piv-tool -a verify-pin --sign -s 9a -H SHA512 -A RSA2048 -i data.txt -o data.sig -v3
Output:

DBG ykpiv.c:589 (ykpiv_connect): Connect reader 'Yubico YubiKey OTP+FIDO+CCID 00 00' matching 'Yubikey'.
DBG ykpiv.c:595 (ykpiv_connect): SCardConnect succeeded for 'Yubico YubiKey OTP+FIDO+CCID 00 00', protocol=2
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 11 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 00a4040005a00000030800 (11)
DBG ykpiv.c:766 (_ykpiv_transmit): < 61114f0600001000010079074f05a0000003089000 (21)
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 5 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 0020008000 (5)
DBG ykpiv.c:766 (_ykpiv_transmit): < 63c3 (2)
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 5 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 00fd000000 (5)
DBG ykpiv.c:766 (_ykpiv_transmit): < 0504039000 (5)
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 5 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 00f8000000 (5)
DBG ykpiv.c:766 (_ykpiv_transmit): < 013865ba9000 (6)
Now processing for action 'verify-pin'.
Action 'verify-pin' does not need authentication.
Enter PIN: 
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 14 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 0020008008313233343536ffff00 (14)
DBG ykpiv.c:766 (_ykpiv_transmit): < 9000 (2)
Successfully verified PIN.
file hashed as: e1 c1 12 ff 90 8f eb c3 b9 8b 16 93 a6 cd 35 64 ea f8 e5 e6 ca 62 9d 08 4d 9f 0e ba 99 24 7c ac dd 72 e3 69 ff 89 41 39 7c 28 07 40 9f f6 6b e6 4b e9 08 da 17 ad 7b 8a 49 a2 a2 6c 0e 80 86 aa 
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 261 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 1087079aff7c8201068200818201000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003051300d060960864801650304020305000440e1c112ff908febc3b98b1693a6cd3564eaf8e5e6ca629d084d9f0eba99247cacdd72e369ff8941397c2807409ff66be64be908da1700 (261)
DBG ykpiv.c:766 (_ykpiv_transmit): < 9000 (2)
DBG ykpiv.c:810 (_ykpiv_transfer_data): Going to send 17 bytes in this go.
DBG ykpiv.c:760 (_ykpiv_transmit): > 0087079a0bad7b8a49a2a26c0e8086aa00 (17)
DBG ykpiv.c:766 (_ykpiv_transmit): < 7c6100 (3)
DBG ykpiv.c:839 (_ykpiv_transfer_data): The card indicates there is 256 bytes more data for us.
DBG ykpiv.c:760 (_ykpiv_transmit): > 00c0000000 (5)
DBG ykpiv.c:766 (_ykpiv_transmit): < 820104828201003954d46aa201f6d37f86e613283edd561c097a8594b934f051bf864b5df50ab5608673c2c1247ca81ef0105a6a013f71be42f2970cc3c955f20a5811f26812d1ce4d0d4fd1b67c8a65e0134cd28e4e504bc48633742f00145bc8f2aa17763987e849d7d9ba462a7a66ca693fea644e6f0b42d88112c370ee8082769ca97fb9dc7099781504a72ad882a98605c3b141726265ddce6774b7573e912385cbd9d4a5b3186b11d090201a7b21903002709e2eff416918ad7ed32ea01237faba35cc14fe051477bec324c31115923efbd188d6f7b5f802475ef516dfe692883116a9f235d62f61bd960b7b85dad71bbb4a9978cec4548bddd1f170466107 (258)
DBG ykpiv.c:839 (_ykpiv_transfer_data): The card indicates there is 7 bytes more data for us.
DBG ykpiv.c:760 (_ykpiv_transmit): > 00c0000007 (5)
DBG ykpiv.c:766 (_ykpiv_transmit): < 5282f21c4f67da9000 (9)
file signed as: 39 54 d4 6a a2 01 f6 d3 7f 86 e6 13 28 3e dd 56 1c 09 7a 85 94 b9 34 f0 51 bf 86 4b 5d f5 0a b5 60 86 73 c2 c1 24 7c a8 1e f0 10 5a 6a 01 3f 71 be 42 f2 97 0c c3 c9 55 f2 0a 58 11 f2 68 12 d1 ce 4d 0d 4f d1 b6 7c 8a 65 e0 13 4c d2 8e 4e 50 4b c4 86 33 74 2f 00 14 5b c8 f2 aa 17 76 39 87 e8 49 d7 d9 ba 46 2a 7a 66 ca 69 3f ea 64 4e 6f 0b 42 d8 81 12 c3 70 ee 80 82 76 9c a9 7f b9 dc 70 99 78 15 04 a7 2a d8 82 a9 86 05 c3 b1 41 72 62 65 dd ce 67 74 b7 57 3e 91 23 85 cb d9 d4 a5 b3 18 6b 11 d0 90 20 1a 7b 21 90 30 02 70 9e 2e ff 41 69 18 ad 7e d3 2e a0 12 37 fa ba 35 cc 14 fe 05 14 77 be c3 24 c3 11 15 92 3e fb d1 88 d6 f7 b5 f8 02 47 5e f5 16 df e6 92 88 31 16 a9 f2 35 d6 2f 61 bd 96 0b 7b 85 da d7 1b bb 4a 99 78 ce c4 54 8b dd d1 f1 70 46 52 82 f2 1c 4f 67 da 
Signature successful!
DBG ykpiv.c:344 (ykpiv_disconnect): Disconnect card #20473274.

The reason is the use of SHA512, which produces too large a hash for either ecp256 or ecp384. That's not the case for RSA2048, which is able to sign it. This is not obvious from the debug output, so I'm creating a pull request to improve that.
One could argue that it should truncate the hash (pkcs#11 specifies this for example), but that would be a breaking change.

Following the standards, I think a breaking change in this case is justified.

#414 now moves the truncation to libykpiv, whereas truncation was only done in libykcs11 before. Hence the above signatures should now succeed.

The reason is the use of SHA512, which produces too large a hash for either ecp256 or ecp384. That's not the case for RSA2048, which is able to sign it. This is not obvious from the debug output, so I'm creating a pull request to improve that.
One could argue that it should truncate the hash (pkcs#11 specifies this for example), but that would be a breaking change.

A hash function takes an input and always returns a hash of a defined length. A SHA512 hash is always 512 bit long, SHA256 always 256 bit and so on, no matter what the input is. So it shouldn't matter if I use RSA2048, ecp256 or ecp384. How is this behavior possible?

A hash function takes an input and always returns a hash of a defined length. A SHA512 hash is always 512 bit long, SHA256 always 256 bit and so on, no matter what the input is. So it shouldn't matter if I use RSA2048, ecp256 or ecp384. How is this behavior possible?

With RSA, you rely on PKCS 1v1.5 or PSS padding.

With ECDSA, the digest needs to be the same length as the key, because there is no padding.

@nzedler It's the output from SHA512 that is (was) too large for the rest of the ECDSA algorithm, the part that is performed in the PIV application. It expects Hr which is the output from the hash algorithm truncated to r, which is the size in bytes of the order of the curve. Instead of doing this truncation we were failing in the library code. Had we sent H to the PIV application it would have failed there instead, so now we simply truncate H from the right. This has been merged to master now, please test if you can.

@grandamp The ECDSA algorithm spec strongly suggests that H should not be smaller than the order of the group, and also specifies that H is is to be truncated if it is too large, yielding Hr. But it then takes Hr and converts to an integer, so in practice a smaller H will just be considered to be zero-padded (from the left) yielding the same integer so to speak. Hence you can use SHA1 with ecp256 for example, even though this may be somewhat out of spec.

@nzedler The truncation was already performed in the libykcs11 library btw, but it has now been moved down to the libykpiv library, so it now also applies to signing directly in yubico-piv-tool.

I feel like there should be a HUGE comment that when using SHA512 it’s in fact just a 256bit hash. Other than that great for usability. Thanks for your effort.

It is not a "just 256-bit hash". It is a 512-bit hash truncated to 256 bits.

Yes, of course it makes sense to document this change that brings ECDSA in compliance with the standard.

Well it’s not a a SHA256 hash but it’s certainly a hash with a size of 256bit. Which makes it just as secure as a SHA256 hash in regard to collisions.

@dmuensterer it is rather common practice to use only part of a larger hash as a 'tag'. There are claims that this is more secure than using the full output of a smaller hash since it won't reveal the full final state of the hash to an attacker, which could make it harder to find collisions. More importantly, this is literally what the ECDSA standard says you should do if H is too large, and pkcs#11 also specifically states this. So in that sense I'd say this was just correcting a bug. The change will be documented in the release notes of our next release (coming early in the new year).

@nzedler closing this as completed now, feel free to reopen if you feel it wasn't resolved.

@dmuensterer it is rather common practice to use only part of a larger hash as a 'tag'. There are claims that this is more secure than using the full output of a smaller hash since it won't reveal the full final state of the hash to an attacker, which could make it harder to find collisions. More importantly, this is literally what the ECDSA standard says you should do if H is too large, and pkcs#11 also specifically states this. So in that sense I'd say this was just correcting a bug. The change will be documented in the release notes of our next release (coming early in the new year).

Thanks for clarifying and the “bug” fix!