After a Nitrokey Support forum user:
Hello. I own a Nitrokey HSM in order to store lots of data objects on it. I thought this would be no problem, but i found out, that this leads to enormous loading time, when i want to use the token. I need to access it via C++ and the OpenSC PKCS11 library but when i call the
C_Initialize-method, it takes ages. Setting the OPENSC_DEBUG environment variable to at least 3, i saw that all objects are read on initialization. Is this expected behavior or are there ways to omit this?
Source: https://support.nitrokey.com/t/slow-initialization-of-nitrokey-hsm/2906
Quick ideas:
C_Initialize().Logs and environment details provided here: https://github.com/OpenSC/OpenSC/issues/2208#issuecomment-763598974.
cc: @CardContact @Nitrokey
In the past, I have heavily optimized the initialization time of GoID (SC-HSM) a) by reducing the number of probing APDUs by determining some configuration from the ATR and b) by enabling on-disk file cache. If you want to do the same, you need to, indeed, go through the log step by step.
Note that you get the most benefit by optimizing the hardware access on the token's side. For example, a recent Yubikey doesn't need any of this optimization, because hardware access is just blazing fast...
Hi. I'm the originator of the question in the nitrokey forum. I attached the output of OpenSC which was generated, when setting the environment variable OPENSC_DEBUG=3.
I'm using OpenSC 0.20.0, 32bit on a Windows 10 PC (v.1903) and stored around 120 small data objects on the token, which seemed to be read on initialization, so calling C_Initialize took around 20s, but i need to store a lot more (~2000) objects on the token, so i fear, the loading times will be much greater in this scenario. The same loading time occures, when calling the pkcs11-tool.
First, try the following opensc.conf, it should fix most of your performance issues:
app default {
card_drivers = sc-hsm;
framework pkcs15 {
try_emulation_first = yes;
use_file_caching = true;
}
}
Second, your token has over one hundred data objects that are read out during initialization. I don't know what their use is, but their content is almost the same: They contain a KEY number and some gibberish along with the text Mahr license generator. Reading this out is the most time consuming part, which will be speeded up by using use_file_caching = true;
Outgoing APDU (10 bytes):
00 B1 00 00 04 54 02 00 00 00 .....T....
P:21392; T:24648 2021-01-20 14:01:36.774 [opensc-pkcs11] reader-pcsc.c:216:pcsc_internal_transmit: called
P:21392; T:24648 2021-01-20 14:01:36.813 [opensc-pkcs11] reader-pcsc.c:307:pcsc_transmit:
Incoming APDU (56 bytes):
30 34 30 10 0C 07 4B 45 59 5F 31 36 32 03 02 06 040...KEY_162...
C0 04 01 01 30 18 0C 16 4D 61 68 72 20 6C 69 63 ....0...Mahr lic
65 6E 73 65 20 67 65 6E 65 72 61 74 6F 72 A1 06 ense generator..
30 04 04 02 CD 52 90 00 0....R..
There are still other optimizations possible (e.g. by reading the object only when they are actually needed), but with the config above, it should work for most uses.
@frankmorgner Thank you for your suggestions. The use_file_caching=true entry speeded up the whole initialization process significantly but unfortunately storing the data locally is not what i want and i would prefer not to enable it. But you mentioned the possibility of reading the object only when needed, which seems like what i want. I want to skip the scan of all data objects, when calling C_Initialize and only read one object, when i need it. But i cannot find the configuration parameter to enable this, so could you please tell me, what the name of this parameter is.
I want to skip the scan of all data objects, when calling C_Initialize and only read one object, when i need it.
That would be a source code change and needs to be investigated. Maybe @CardContact wants to have a look...
@frankmorgner Ah ok, i thought there is a setting to do this. Adapting the source just for my problem would be a bit over the top i guess. Then i think the file caching will do it for now. Thanks for your help.
Having a second look, I noticed that most of the time is spent here:
https://github.com/OpenSC/OpenSC/blob/219c6cc49464c627c7741a2c17171d7f61fa536c/src/libopensc/pkcs15-sc-hsm.c#L1033-L1051
By design, the meta-description of the data objects is organized as separate files, which is why you're seeing a lot of READ BINARY commands. A different way of implementing this would be to have the description in a single file, as usually done in PKCS#15. However, I'm afraid this would be a breaking change for all existing configurations of SC-HSM. In particular, this would not only change the way the card/token is initialized, but it would also change how https://github.com/CardContact/sc-hsm-embedded interacts with the token, for example.
In conclusion: I don't see a good solution other than using the file cache.
@frankmorgner Thanks for this detailed answer and your further investigations. I see the problem. I use the "use_file_cache" parameter for now, but i'm still not happy with this. When i set use_file_cache=true, my object values are also cached to disk (and thus exposed without ), even if they are marked as private (CKA_PRIVATE is true). Ok, this is maybe not intended to be used in production code but i don't think, this should happen. Another possibility : I used the wrong Attribute to indicate, that the objects should really not be accessible without the user pin and thus the file caching "thinks" it's ok to store the data.
IMHO, using a hardware crypto token as a substitute for an encrypted file system is unwise.
A better solution might be keeping a key on the token, and store the files encrypted on your disk. Decrypt them with the token when you need to work with them.
Or shell out the cash and buy something like Apricorn USB drive that has secure hardware encryption of its content.
@mouse07410 Thats indeed a good point. Some user on stackoverflow suggestet something similar. I think i need to convince my boss to do it this way. It seems much more secure.
Most helpful comment
First, try the following
opensc.conf, it should fix most of your performance issues:Second, your token has over one hundred data objects that are read out during initialization. I don't know what their use is, but their content is almost the same: They contain a KEY number and some gibberish along with the text Mahr license generator. Reading this out is the most time consuming part, which will be speeded up by using
use_file_caching = true;There are still other optimizations possible (e.g. by reading the object only when they are actually needed), but with the config above, it should work for most uses.