Saturday, October 29. 2016
Integration of the DNIe 3.0 is ongoing
After knowing that I could successfully integrate the changes for DNIe 3.0 into the current OpenSC code. You can find and compile a working branch following this procedure:
git clone https://github.com/rickyepoderi/OpenSC.git cd OpenSC git checkout dnie30 ./bootstrap configure --enable-dnie-ui --prefix=/where/you/want make make install
In order to compile the code you need the auto-tools, pcsclite and openssl development packages. The --enable-dnie-ui option makes the DNIe driver to ask (pinentry is needed) for a confirmation before signing with the non-repudiation key.
For a week more or less I have been updating the bug about DNIe 3.0 integration. Sadly there has been a lot of noise with a non-related issue and the integration does not move forward much. But, for the moment, two people have tested the code with the DNIe 2.0 and five more with DNIe 3.0 (I personally can only test with DNIe 3.0 because my previous DNIe was kept in the police station when I renewed it). An here it is the main part in this entry, if you are Spanish, use linux, have a working card reader and know a bit about these things (you have to be able to compile it)...
You can test it quite easily with a few commands after the compilation.
Check the DNIe is inserted and everything is working. If the information is not displayed something is wrong with your setup.
./dnie-tool -a Using reader with a card: Broadcom Corp 5880 [Contacted SmartCard] (0123456789ABCD) 00 00 DNIe Number: XXXXXXXXX SurName: MARTIN Name: RICARDO IDESP: XXXXXXXXX DNIe Version: DNIe 04.10 B5 H 0155 EXP 2-(5.2-0) Serial number: XXXXXXXXXXXXXX
Check the login is working (from this test on you are really checking the integration).
./pkcs11-tool -l -I Cryptoki version 2.20 Manufacturer OpenSC Project Library OpenSC smartcard framework (ver 0.16) Using slot 0 with a present token (0x0) Logging in to "PIN1 (DNI electrónico)". Please enter User PIN: XXXXXXXX
Check the objects are read from the card.
./pkcs11-tool -l -O Using slot 0 with a present token (0x0) Logging in to "PIN1 (DNI electrónico)". Please enter User PIN: XXXXXXXX Private Key Object; RSA label: KprivAutenticacion ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Usage: sign Certificate Object, type = X.509 cert label: CertAutenticacion ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Public Key Object; RSA 2048 bits label: CertAutenticacion ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Usage: encrypt, verify Certificate Object, type = X.509 cert label: CertCAIntermediaDGP ID: ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ Public Key Object; RSA 2048 bits label: CertCAIntermediaDGP ID: KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK Usage: encrypt, verify Private Key Object; RSA label: KprivFirmaDigital ID: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY Usage: sign, non-repudiation Certificate Object, type = X.509 cert label: CertFirmaDigital ID: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY Public Key Object; RSA 2048 bits label: CertFirmaDigital ID: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY Usage: encrypt, verify Data object 25997200 label: 'DG1' application: '' app_id:
flags: modifiable ... Check that you can sign something. You need to pass the ID of the KprivFirmaDigital displayed in the previous point. The garbled data is the signature itself.
./pkcs11-tool -d YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY -s Using slot 0 with a present token (0x0) Logging in to "PIN1 (DNI electrónico)". Please enter User PIN: XXXXXXXX Using signature algorithm RSA-PKCS Sample data to sign T���N�A�ђ��T�G��*���>��K!�~C@zF��- ɾ���mSe���ո��`�#凒!��PR.1�Yt~����\j�7F�3S� z���k���@ѷd��Q2��U3w8���\h]"J7�F��ϫNR]E&�����)�"]4"7{W�XoC�`jf**iq��Ók++('j� ?nR�k�(�3�$ǥ���s2�����%[�� ~�=I0�e}D$;�!���
If you prefer I developed a little C program that makes all these tests one by one. It uses the PKCS#11 library generated by the OpenSC and checks all the cases described above plus a weird one in which two processes use the card at the same time (the idea is one process steals the secure channel to the other). I have just uploaded the code to a repo. There you can find instructions to compile and use it (I think it is not very complicated).
You can comment here or in the bug if it is working or not for you. But please, if you do not know what we are talking about, or you are not sure if you have done it in the proper way, just contain your impulse of sharing your experience and wait for a final integration.
Thanks in advance!
Saturday, October 15. 2016
Starting to play with DNIe 3.0 and OpenSC
The past monday I renewed my Spanish ID card (also known as DNIe). As you know there have been several entries about the Spanish electronic ID card throughout the blog. But mainly this three years old entry summarizes the the long story about the Spanish eID implementation. That entry says that the pain finally seemed to finish with the inclusion of the opendnie into the OpenSC project, but, as always, things can change quickly. The Spanish government has moved to a new version of the DNIe (now called 3.0) and the old implementation cannot handle with the new card. All the new identity cards expended in Spain are now 3.0 (so is mine) and also incompatible with current OpenSC.
During this week I have been struggling with it in order to make it work with OpenSC. German Blanco had already started the task and I just followed where he left it. Today I finally could sign some data and, I think, it is usable (I do not know if it is fully working). What it is sure is that everything is a complete mess. Now it is the time to sit down and try to make it in the correct way (because the situation is again quite complicated).
As always I am going to tell the story as it happened to me (I usually prefer to remember the story than the final solution).
The main difference between DNIe 2.0 and 3.0 it is that the new version establishes a new secure channel. That channel is called the PIN channel. A secure channel is a (at least for me) extremely complicated standard (CWA-14890) to encrypt the communication between the device and the card. Instead of sending plain APDUs to the card the APDU itself is encrypted using keys and certificates (similar idea like between HTTP and HTTPS protocols).
Until now the DNIe uses only one secure channel which was needed before any critical operation (login, sign, and so on). In DNIe 3.0 a second channel is used, it is called the PIN channel, and it is needed to any operation related to the PIN (pin verification, change,...). But it is the same procedure, the same logic is used to establish the PIN channel but using different certificates and keys.
German already knew about that and he had added the new data for the PIN channel in his branch. Spanish government publishes the sources of the MultiPKCS11 (their implementation is not related to OpenSC, remember what Einstein said "Only two things are infinite, the universe and human stupidity, and I'm not sure about the former") and I supposed he copied the values for the new channel from it. He also handled with some little differences between both versions (some lengths in TLV data and other minor differences).
I changed the idea that German had in mind, I simply use the same structure to create the channel. But I created two new methods to use one data (secure channel) or the other (PIN channel):
void dnie_change_cwa_provider_to_secure(sc_card_t card) { cwa_provider_t res = GET_DNIE_PRIV_DATA(card)->cwa_provider; /* redefine different IFD data for secure channel */ res->cwa_get_cvc_ifd_cert = dnie_get_cvc_ifd_cert; res->cwa_get_ifd_privkey = dnie_get_ifd_privkey; res->cwa_get_ifd_pubkey_ref = dnie_get_ifd_pubkey_ref; res->cwa_get_sn_ifd = dnie_get_sn_ifd; } void dnie_change_cwa_provider_to_pin(sc_card_t * card) { cwa_provider_t * res = GET_DNIE_PRIV_DATA(card)->cwa_provider; /* redefine different IFD data for PIN channel */ res->cwa_get_cvc_ifd_cert = dnie_get_cvc_ifd_cert_pin; res->cwa_get_ifd_privkey = dnie_get_ifd_privkey_pin; res->cwa_get_ifd_pubkey_ref = dnie_get_ifd_pubkey_ref_pin; res->cwa_get_sn_ifd = dnie_get_sn_ifd_pin; }
So you can create one channel or the other just calling one of the previous methods before. More or less German had reached this point in his branch but using two different structures instead of one (but changing the methods that retrieves the data).
Looking to the official MultiPKCS11 implementation, in order to login into the card the PIN channel should be established (instead the secure channel of the 2.0 version). So with this little diff in the dnie_pin_verify method we change the data as expected.
diff --git a/src/libopensc/card-dnie.c b/src/libopensc/card-dnie.c index e442cb4..b3bd170 100644 --- a/src/libopensc/card-dnie.c +++ b/src/libopensc/card-dnie.c @@ -2095,6 +2094,11 @@ static int dnie_pin_verify(struct sc_card *card, LOG_FUNC_CALLED(card->ctx); /* ensure that secure channel is established from reset */ + if (card->atr.value[15] >= DNIE_30_VERSION) { + /* the provider should be prepared for using PIN information */ + sc_log(card->ctx, "DNIe 3.0 detected doing PIN initialization"); + dnie_change_cwa_provider_to_pin(card); + } res = cwa_create_secure_channel(card, GET_DNIE_PRIV_DATA(card)->cwa_provider, CWA_SM_COLD); LOG_TEST_RET(card->ctx, res, "Establish SM failed");
As I said German was at this point in his branch. The PIN channel was correctly established but then the following APDU (the one that checks the PIN) failed (the card returned a 0x6988 error which is described as SM Data Object incorrect). I was stuck too for several hours with this problem but carefully checking differences between OpenSC and the official implementation I saw one important difference.
In the MultiPKCS11 implementation when the APDU is wrapped (the original -plain- APDU is encrypted inside the channel) the method secChannelEncodeAPDU does the following:
void CComm_DNIe::secChannelEncodeAPDU( byteBuffer & apdu ) { byteBuffer data = apdu.substr(5,apdu.size()-5); byteBuffer cc; if( data.size() ) { // some stuff here // ... } else if( apdu[4] ) { // other stuff here // ... }
So, the official implementation only sends data or receive data (the apdu[4] is the Le part of the APDU, which is the size of the buffer to receive data). It is one or the other. Nevertheless in the OpenSC side there are two separated ifs, so you can send both types of information in the wrapped APDU.
Following my intuition I installed the binary package of the MultiPKCS11 (it is completely impossible to compile those fucking sources in linux, FNMT guys, I am sure you can do a better job). Then I checked the differences between the APDU sent by both implementations and, as I expected, OpenSC sent three TLVs while the official one just two. OpenSC was sending the Le whereas MultiPKCS11 was not.
So again, another little diff to fix this point:
diff --git a/src/libopensc/cwa14890.c b/src/libopensc/cwa14890.c index 9d3e1a8..f89670f 100644 --- a/src/libopensc/cwa14890.c +++ b/src/libopensc/cwa14890.c @@ -1532,13 +1531,11 @@ int cwa_encode_apdu(sc_card_t * card, msg = "Error in compose tag 8x87 TLV"; goto encode_end; } - } - + } else if ((0xff & from->le) > 0) { /* if le byte is declared, compose and add Le TLV */ /* FIXME: For DNIe we must not send the le bytes when le == 256 but this goes against the standard and might break other cards reusing this code */ - if ((0xff & from->le) > 0) { u8 le = 0xff & from->le; res = cwa_compose_tlv(card, 0x97, 1, &le, &ccbuf, &cclen); if (res != SC_SUCCESS) {
And now the weirdest thing of all happened to me. I successfully logged into the card, the channel was established and now the pin APDU was sent and no error was returned. But DNIe, when using a secure channel, always returns a response of type 0x61XX. This response means that it was correctly executed and you have XX bytes prepared to you to retrieve them. Now a getResponse APDU is needed to get the data. That data is the encrypted APDU response ready to be deciphered.
OpenSC was not sending the getResponse APDU. I was shocked because it is exactly how 2.0 works. There is no difference here. It should be working smoothly.
After more hours struggling again, I think this is a disgusting bug introduced to the DNIe driver in the last months. More concretely I think this commit is the one that introduced the problem. So, to see if I was right I just decided to make my changes in the default branch of the German's clone (which was previous to that commit). And, effectively, the getResponse APDU was sent as expected.
The final part of the puzzle was understanding how the secure channel is established once the PIN channel is previously there. Finally I realized that the secure channel is established through the PIN channel (it is a kind of double channel or a chained channel). So I quickly hacked the OpenSC implementation to be able to establish a channel when a previous one is used (you just need to not reset the card and let the channel be re-established if a previous one is already set, I know there is room for improvement here). These two little lines make the difference:
diff --git a/src/libopensc/cwa14890.c b/src/libopensc/cwa14890.c index 9d3e1a8..f89670f 100644 --- a/src/libopensc/cwa14890.c +++ b/src/libopensc/cwa14890.c @@ -1085,11 +1084,11 @@ int cwa_create_secure_channel(sc_card_t * card, /* OK: lets start process */ /* reset card (warm reset, do not unpower card) */ - sc_log(ctx, "Resseting card"); - sc_reset(card, 0); + sc_log(ctx, "Resseting card is commented out because for 3.0 PIN is needed before"); + //sc_reset(card, 0); - /* mark SM status as in progress */ - provider->status.session.state = CWA_SM_INPROGRESS; + /* mark SM status as in progress => In DNIe 3.0 the secure channel is established through the PIN channel*/ + //provider->status.session.state = CWA_SM_INPROGRESS; /* call provider pre-operation method */ sc_log(ctx, "CreateSecureChannel pre-operations");
Finally in the dnie_pin_verify method was added some lines to re-establish the secure channel after the PIN channel. This way both versions, 2.0 and 3.0, works in the same way (after the login the secure channel is ready for any operation).
diff --git a/src/libopensc/card-dnie.c b/src/libopensc/card-dnie.c index e442cb4..b3bd170 100644 --- a/src/libopensc/card-dnie.c +++ b/src/libopensc/card-dnie.c @@ -2132,6 +2136,14 @@ static int dnie_pin_verify(struct sc_card *card, /* the end: a bit of Mister Proper and return */ dnie_free_apdu_buffers(&apdu, NULL, 0); data->apdu = NULL; + + /* ensure that secure channel is established after a PIN channel in 3.0 */ + if (card->atr.value[15] >= DNIE_30_VERSION) { + sc_log(card->ctx, "DNIe 3.0 detected => re-establish secure channel"); + dnie_change_cwa_provider_to_secure(card); + res = cwa_create_secure_channel(card, GET_DNIE_PRIV_DATA(card)->cwa_provider, CWA_SM_COLD); + } + LOG_FUNC_RETURN(card->ctx, res); #else LOG_TEST_RET(card->ctx, SC_ERROR_NOT_SUPPORTED, "built without support of SM and External Authentication");
And here it is, we have a working DNIe 3.0 implementation in OpenSC. It is absolutely messy and possibly incomplete. It can be broken for 2.0 version because I have only tested with 3.0. Thinking about the changes more deeply is absolutely necessary. But I would say that the basic ideas are the ones presented before. My hacked version can sign a text using the pkcs11-tool command that comes with the OpenSC bundle. I also logged into some Spanish webs that need the DNIe certificate. So basic functionality is working.
Obviously the main problem here is the issue of the getResponse not being called. I cannot test with a 2.0 DNIe (because I do not have one and all the people I asked for it have no idea about their PINs). So I am not sure if the current OpenSC branch is completely broken with the DNIe (2.0 I mean). I have asked German to test but he seems to be quite busy these days. So I do not know how to continue with this. I would prefer that commit to be reverted completely. I did not agree with that change at that time and I continue having the same opinion. I will try to upload my changes to some place, but I need a branch previous to that commit (I suppose git lets me do that but I have not even done it before).
Why is DNIe so fucking annoying in Linux?
Comments