OpenSC / libp11

PKCS#11 wrapper library
GNU Lesser General Public License v2.1
298 stars 183 forks source link

Issue with CKU_CONTEXT_SPECIFIC on Safenet HSM module #160

Closed mirozitnansky closed 7 years ago

mirozitnansky commented 7 years ago

First of all, you are doing great job guys, many thanks for time and energy you are putting into this project.

I am pretty noob with pkcs11 stuff, so please have patience with me. I spent quite some time trying to implement OpenSSL certification authority based on Sefenet HSM module which should store CA private keys. CA key is than used for singing client/server certificates. Using libp11 as OpenSSL engine, configured with pkcs11 lib supplied with HSM, I was able to make OpenSSL work correctly... on some conditions. Our HSM can work in few modes. Safenet's default (we are currently using this mode), FIPS-140 and others. We are planning to switch our device from current, default mode, into FIPS-140 mode. This means, among other things, device will be switched to No Public Crypto mode – user have to be always logged into token, if he is going to use key for crypto operation. In current mode, there no need to be logged in token to do the sign operation in OpenSSL. On the other hand, object in token can be public or private (I am not reffering to RSA pulic/private key). Private objects are only accessible after loggin in.

First problem I encountered lied on having private object while disabled No Public Crypto flag. Then even if I try to set pin for token, engine won’t log in and simply says there is no such object (because it was set private).

I have pkcs11 communication logged so pc_private_list.txt is list using safenet utility.

[root@mzcentos ~]# ctkmu l -s 0
ProtectToolkit C Key Management Utility 5.2.0
Copyright (c) Safenet, Inc. 2009-2016

logger: s_ctData->ft = 0x0x1dd5360
logger: s_ctData->ft->C_Initialize = 0x0x7f97d9acae20
Do you wish to view private (user) objects [y/N]: y

Please enter User's PIN for the token in slot 0: 

Manufacturer      = SafeNet Inc.
Label             = OPENSSL                         
Flags             = 0x649 (RNG USER-PIN-INIT CLOCK TOKEN-INIT DUAL-CRYPTO)

Public and Private Objects:
CA                               - PUBLIC_KEY      RSA         
CA                               - PRIVATE_KEY     RSA         

and pc_private_sign.txt is OpenSSL sign attempt which end up failed:

[root@mzcentos ca_ptk]# openssl req -config openssl.cnf -key "pkcs11:model=0000;manufacturer=SafeNet%20Inc.;serial=0000%3a84324;token=OPENSSL;id=%42%19%72%91%10%73%e4%b1%e0%7f%67%fe%2f%31%a8%6c%e6%5c%ca%66;object=CA;object-type=private?pin-value=1111" -keyform engine -engine pkcs11 -new -x509 -days 7300 -sha256 -extensions v3_ca -out certs/ca.cert.pem
logger: s_ctData->ft = 0x0x21241a0
logger: s_ctData->ft->C_Initialize = 0x0x7f9fb5b44e20
engine "pkcs11" set.
No private keys found.
No private keys found.
PKCS11_get_private_key returned NULL
cannot load Private Key from engine
140323973031840:error:26096080:engine routines:ENGINE_load_private_key:failed loading private key:eng_pkey.c:126:
unable to load Private Key

In this case, I assume, if OpenSSL logged into token, sing operation would worked fine. So I set No public crypto flag to true, OpenSSL logs in correctly, finds private key and do the sing operation. Npc_private_sign.txt

Second thing, it seems to me, is some lack of implementation on side of Safenet. It looks like, they have not implemented CKU_CONTEXT_SPECIFIC user type in pkcs11 at all. When I have set No public crypto true, but my object is Public, OpenSSL finds it, then request pin (ignoring pin in URL), but then sing fails on

[root@mzcentos ca_ptk]# openssl req -config openssl.cnf -key "pkcs11:model=0000;manufacturer=SafeNet%20Inc.;serial=0000%3a84324;token=OPENSSL;id=%42%19%72%91%10%73%e4%b1%e0%7f%67%fe%2f%31%a8%6c%e6%5c%ca%66;object=CA;object-type=private?pin-value=1111" -keyform engine -engine pkcs11 -new -x509 -days 7300 -sha256 -extensions v3_ca -out certs/ca.cert.pem
logger: s_ctData->ft = 0x0xd571a0
logger: s_ctData->ft->C_Initialize = 0x0x7f80d2c5be20
engine "pkcs11" set.
Missing CKA_ALWAYS_AUTHENTICATE attribute
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [SK]:
State or Province Name [Bratislavsky kraj]:
Locality Name [Bratislava]:
Organization Name [company]:
Organizational Unit Name [IKT]:
Common Name []:
Email Address [ikt@company.eu]:
Enter PKCS#11 key PIN for CA:
140191316727712:error:80009103:Vendor defined:PKCS11_rsa_encrypt:User type invalid:p11_rsa.c:120:
140191316727712:error:0D0DC006:asn1 encoding routines:ASN1_item_sign_ctx:EVP lib:a_sign.c:314:

This seems to be a problem in pkcs11 log Npc_publicobj_sign.txt:

pid(14918) tid(139744935835712) time(16/05/2017,23:02:12.252)   > C_Login hSession=0x00000002 userType=2{?} pPin=0x0x7ffd53d62830 pinLen=4
    pin: (4Bytes)
    31 31 31 31 

pid(14918) tid(139744935835712) time(16/05/2017,23:02:12.252)   << C_Login rv=0x00000103{user type invalid}

As I mentioned before, we plan to switch mode of our device to FIPS (with No public crypto flag), and we have public objects in token, so after switch we won’t be able to use them, because sing will fail as in previous case. If I am able to force login into token as standard user, crypto operations shoud work correctly even after mode switch. I noticed _Missing CKA_ALWAYSAUTHENTICATE attribute message in OpenSSL operations, but I am not sure if this has something to do with my situation.

All tests I am doing, are done on software HSM emulation, but hardware HSM works the same. Do you think, if is there any way to overcome this user type invalid issue?

Thanks in advance. If there is any test/log I can provide, I'll be happy to.

dengert commented 7 years ago

As you have noted in https://github.com/OpenSC/libp11/files/1005875/Npc_publicobj_sign.txt line 357-360 the PKCS#11 module does not understand 202 (CKA_ALWAYS_AUTHENTICATE) that that looks like the source of the "Missing CKA_ALWAYS_AUTHENTICATE attribute" The module should support the attribute and return FALSE.

In lines 364 to 370 a C_SignInit returns USER_NOT_LOGED_IN. Libp11 is assuming that in this case the the module is expecting a C_Login with CKU_CONTEXT_SPECIFIC which is what would be expected to work if the CKA_ALWAYS_AUTHENTICATE=True. So this again is a problem with the module, it does not even understand CKU_CONTEXT_SPECIFIC.

But to be fair, the PKCS#11 standards say C_SignInit can return CKR_USER_NOT_LOGGED_IN but does not limit it to private keys with CKA_ALWAYS_AUTHENTICATE=True. So the caller can not be sure if it should do a CKU_CONTEXT_SPECIFIC or normal C_Login with CKU_USER.

Not recognizing standard attributes and CKU__CONTEXT_SPECIFIC is a bug in the Sefenet HSM module.

Libp11 could make the assumption CKA_ALWAYS_AUTHENTICATE missing means False, and only do CKU_CONTEXT_SPECIFIC C_Login if CKA_ALWAYS_AUTHENTICATE=True.

The next question is what other attributes does Sefenet HSM module not support correctly?

mouse07410 commented 7 years ago

I support treating CKA_ALWAYS_AUTHENTICATE missing as False.

mirozitnansky commented 7 years ago

I went over documentation again and find this

Compliance This application expects PKCS#11 V 2.10 compliant implementation and will use SafeNet extensions (see the next section) if they are available.

Isn't it that CKA_ALWAYS_AUTHENTICATE was added in later versions of pkcs11? I checked 2.1 specification and there is no mention of attributes.

What looks very artsy to me, that Safenet didn't update their pkcs11 imeplentation even on devices released in 2015. Should I try compile and run new master?

dengert commented 7 years ago

CKA_ALWAYS_AUTHENTICATE is in PKCS#11-2.20 from 2004. Section "10.9 Private key objects" in Table 15. The default is CK_FALSE. CK__CONTEXT_SPECIFIC is also in 2.20.

In https://github.com/OpenSC/libp11/files/1005875/Npc_publicobj_sign.txt lines 29 to 31 shows the C_GetInfo:

pid(14918) tid(139744935835712) time(16/05/2017,23:02:07.225) < C_GetInfo rv=0x00000000{success} pInfo=0x0x7ffd53d62920 pInfo->cryptokiVersion: (2Bytes) 02 14

The SafeNet module appears to return version 2.20 Thus the Safenet module is not meeting the PKCS#11 standards it claims is supports.

mirozitnansky commented 7 years ago

Thanks for clarification, I will raise issue to support, but to be honest, I don't believe they will change anything...

mirozitnansky commented 7 years ago

I tried to run test on lastest version of ProtectServer toolkit (pkcs11 lib for hsm) which should be v2.20 compliant, it failed same as older version. I reported this to gemalto support.

I am not sure what to do next. I tried to compile lastest master from github, where I saw commits for this issue, but it won't compile. I ran bootstrap then configure, but it keeps running check and fails on missing autoconf build-master.txt. Should I wait for official release?

Thanks.

mtrojnar commented 7 years ago

make: Warning: File `Makefile.am' has modification time 7403 s in the future

Try configuring the correct timezone on your machine.

Alternatively, use "git clone" instead of downloading the auto-generated tarball.

mirozitnansky commented 7 years ago

thanks, I had date few days back

I ran test with NO PUBLIC CRYPTO and PUBLIC OBJECT again, now I end up user not logged

[root@mzcentos ca_ptk]# openssl req -config openssl.cnf -key "pkcs11:model=0000;manufacturer=SafeNet%20Inc.;serial=0000%3a84324;token=OPENSSL;id=%42%19%72%91%10%73%e4%b1%e0%7f%67%fe%2f%31%a8%6c%e6%5c%ca%66;object=CA;object-type=private?pin-value=1111" -keyform engine -engine pkcs11 -new -x509 -days 7300 -sha256 -extensions v3_ca -out certs/ca.cert.pem
logger: s_ctData->ft = 0x0x1f661a0
logger: s_ctData->ft->C_Initialize = 0x0x7f477b051fc0
engine "pkcs11" set.
Missing CKA_ALWAYS_AUTHENTICATE attribute
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [SK]:
State or Province Name [Bratislavsky kraj]:
Locality Name [Bratislava]:
Organization Name [company]:
Organizational Unit Name [IKT]:
Common Name []:
Email Address [ikt@company.eu]:
139945031366560:error:80009101:Vendor defined:PKCS11_rsa_encrypt:User not logged in:p11_rsa.c:120:
139945031366560:error:0D0DC006:asn1 encoding routines:ASN1_item_sign_ctx:EVP lib:a_sign.c:314:
[root@mzcentos ca_ptk]# 

NPC_PO.txt

in no public crypto mode, hsm requires to log in on every crypto operation

dengert commented 7 years ago

C_Login is never called.

I am also surprised that the private key was found without logging in. See lines 214-222. PKCS#11 has the CKA_PRIVATE which says a user may not access the object until the user has authenticated to the token. Access to a private key object includes doing a crypto operation.

(Note CKO_PRIVATE_KEY is a private key object. All objects have a CKA_PRIVATE attribute.)

PKCS#1 2.20 says: "The object search operation will only find objects that the session can view. For example, an object search in an “R/W Public Session” will not find any private objects (even if one of the attributes in the search template specifies that the search is for private objects)."

The SafeNet module appears to violate the above.

(It is possible to have a private key with CKA_PRIVATE=False. Which would then allow for it to be found and used (for example with C_SignInit) without a login. But it looks like that is not what was intended, as it was found but could not be used. )

So the libp11 assumed it was found without a login and could be used without a login.

Looks like https://github.com/OpenSC/libp11/commit/c71401f5ad719041f85172687b70079f067ad027 does not address the problems of the SafeNet implementation.

With previous code, pkcs11_authenticate would have been called, but pkcs11_authenticate has: if (!kpriv->always_authenticate) return 0; but it will only do CKU_CONTEXT_SPECIFIC type of C_Login

But there are times when the token could loose the login state because of interference from other processes or even a powered down USB reader. pkcs11_authenticate could be passed another parameter indication it should try a CKU_USER login and/or a CKU_CONTEXT_SPECIFIC login depending on why it was being called.

dengert commented 7 years ago

One more thing. What version of openssl are you using?

Since you are trying to create the CA certificate that is self signed, you may want to look at the CA.sh or CA.pl scripts to see how OpenSSL creates a CA self signed certificate. They actually do a two step process using openssl req and openssl ca -selfsign Thus there is only one sign operation in each step.

mirozitnansky commented 7 years ago

I am using 1.0.1e release 60.el7_3.1, same for openssl-devel.

Private key actually is not private object, list from safenet's key generation utility

[root@mzcentos ~]# ctkmu l -s 0 -v
ProtectToolkit C Key Management Utility 5.3.0
Copyright (c) Safenet, Inc. 2009-2016

logger: s_ctData->ft = 0x0x1fa7390
logger: s_ctData->ft->C_Initialize = 0x0x7f503a16efc0
Do you wish to view private (user) objects [y/N]: n

Manufacturer      = SafeNet Inc.
Label             = OPENSSL                         
Flags             = 0x649 (RNG USER-PIN-INIT CLOCK TOKEN-INIT DUAL-CRYPTO)

Public Objects:
CA                               - PUBLIC_KEY      RSA         

Attribute list for object CA, slot 0
Private      = 0
Sensitive    = 0
Modifiable   = 0
Import       = 0
Export       = 0
Wrap         = 0
Unwrap       = 0
Exportable   = 0
Extractable  = 0
Derive       = 0
Encrypt      = 0
Decrypt      = 0
Sign         = 0
Verify       = 0
Key Size     = 2048 bits

CA                               - PRIVATE_KEY     RSA         

Attribute list for object CA, slot 0
Private      = 0
Sensitive    = 1
Modifiable   = 0
Import       = 0
Export       = 0
Wrap         = 0
Unwrap       = 0
Exportable   = 0
Extractable  = 1
Derive       = 0
Encrypt      = 0
Decrypt      = 0
Sign         = 1
Verify       = 0
Sign Local   = 0
Key Size     = 2048 bits

list.txt I set it not to be private in generation process.

That is because while we are running our HSM in production environment (and I am simulating this setting) with NO_PUBLIC_CRYPTO disabled, openssl engine won't Login, and if key is private object, it cannot see it. I have to generate it as public, so it's visible for engine. In this combination engine works just fine. NPC_PO_OK.txt

Now I try to simulate future event, switching HSM to FIPS mode (NO_PUBLIC_CRYPTO enabled) then the problem with missing login in sign operation arises, because key is visible, but HSM requires Login to Sign with it.

I was also trying different approach, generating CA key as private object. But then, while NO_PUBLIC_CRYPTO disabled (as current production enviroment state), engine is not doing Login, therefore won't find private object. Probably because of missing CKA_ALWAYS_AUTHENTICATE attribute? In this scenarion, after switching NO_PUBLIC_CRYPTO enabled, all start to work fine, because engine start to login. This is log from such scenario, from the point of generating private object. line 322 - key generation line 361 - failed Sign attempt

[root@mzcentos ca_ptk]# openssl req -config openssl.cnf -key "pkcs11:model=0000;manufacturer=SafeNet%20Inc.;serial=0000%3a84324;token=OPENSSL;id=%42%19%72%91%10%73%e4%b1%e0%7f%67%fe%2f%31%a8%6c%e6%5c%ca%66;object=CA;object-type=private?pin-value=1111" -keyform engine -engine pkcs11 -new -x509 -days 7300 -sha256 -extensions v3_ca -out certs/ca.cert.pem
logger: s_ctData->ft = 0x0x19ee1a0
logger: s_ctData->ft->C_Initialize = 0x0x7fbf3da81fc0
engine "pkcs11" set.
No private keys found.
PKCS11_get_private_key returned NULL
cannot load Private Key from engine
140459397937056:error:26096080:engine routines:ENGINE_load_private_key:failed loading private key:eng_pkey.c:126:
unable to load Private Key

line 690 - enable NO_PUBLIC_CRYPTO line 1316 - successful Sing attempt

logger: s_ctData->ft = 0x0xfa51a0
logger: s_ctData->ft->C_Initialize = 0x0x7f4865242fc0
engine "pkcs11" set.
Missing CKA_ALWAYS_AUTHENTICATE attribute
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [SK]:
State or Province Name [Bratislavsky kraj]:
Locality Name [Bratislava]:
Organization Name [company]:
Organizational Unit Name [IKT]:
Common Name []:
Email Address [ikt@company.eu]:

PRIVATEOBJ_FULL.txt

At first step, I thought that this issue might be resolved, treating missing CKA_ALWAYS_AUTHENTICATE as CKA_ALWAYS_AUTHENTICATE = true. But this can have more behind it what I cannot understand, because of my little knowledge.

I understand, problem lies in different mode when generating key, and switch mode with already generated keys. But that's something I have to live with. I cannot change mode now, when generating key, because production systems (which are using HSM for various crypto operations with symmetric keys) are relaying on not having to Login for public object key using. They will have to be modified in future, before switch to FIPS mode. But I need to start using CA now, not in future.

Self signing is only first step. As I mentioned, I successfully used openssl engine for singing CA, intermediate CAs and client certificates, but I can't proceed forward knowing whole mechanism stops working in future when HSM will be switched. So I am trying to find a way. To be honest, I am trying to resolve this issue for quite a long time now. Writing here, bothering you is my last resort before giving up on using openssl CA with our HSM (what cannot easily accept after spending so much time with it).

mirozitnansky commented 7 years ago

Documentantion defines NO PUBLIC CRYPTO as follows:

No Public Crypto When this flag is TRUE, each token will have the CKF_LOGIN_REQUIRED flag set and all the cryptographic C_xxxInit functions and key operation functions: C_GenerateKey, C_GenerateKeyPair, C_WrapKey, C_UnwrapKey, C_DeriveKey, C_DigestKey will fail unless the session state is in a User mode (that is, either the USER or SO must be logged in).

If the session state is not in a User mode, any attempt to write to a token will fail (that is, using the functions C_CreateObject, C_DestroyObject and C_SetAttributeValue).

dengert commented 7 years ago

I have not used libp11 recently either. Trying to build it from master c71401f5ad719041f85172687b70079f067ad027

@mtrojnar Running ./bootstrap, I get libtoolize: Remember to add 'LT_INIT' to configure.ac. Then since I am building is a separate directory from the source,
I get these messages from configure:

../src/configure: line 13750: src/libp11.map: No such file or directory ../src/configure: line 13751: src/libp11.map: No such file or directory ../src/configure: line 13752: src/libp11.map: No such file or directory ../src/configure: line 13753: src/libp11.exports: No such file or directory ../src/configure: line 13754: src/libp11.map: No such file or directory ../src/configure: line 13755: src/libp11.map: No such file or directory ../src/configure: line 13756: src/libp11.map: No such file or directory ../src/configure: line 13757: src/libp11.map: No such file or directory chmod: cannot access 'src/libp11.map': No such file or directory

This looks like the libp11.map is being created in the $(srcdir) directory, rather then the $(builddir) But it builds anyway. ../src/configure --prefix=/opt/ossl-1.0.2 --with-pkcs11-module=opensc-pkcs11.so --disable-ld-version-script

@mirozitnansky I have never used SafeNet, but it sounds like the it is taking advantage of what I said earlier: "But to be fair, the PKCS#11 standards say C_SignInit can return CKR_USER_NOT_LOGGED_IN but does not limit it to private keys with CKA_ALWAYS_AUTHENTICATE=True. So the caller can not be sure if it should do a CKU_CONTEXT_SPECIFIC or normal C_Login with CKU_USER."

@mtrojnar I also said: "But there are times when the token could loose the login state because of interference from other processes or even a powered down USB reader. pkcs11_authenticate could be passed another parameter indication it should try a CKU_USER login and/or a CKU_CONTEXT_SPECIFIC login depending on why it was being called." This should solve the problem when SafeNet with the NO_PUBLIC_CRYPTO option. i.e. a normal C_Login could be done from pkcs11_authenticate.

mirozitnansky commented 7 years ago

If I may ask, how CKF_LOGIN_REQUIRED should be correctly treated? Specification says True if there are some cryptographic functions that a user MUST be logged in to perform At which point of communication should it be taken into consideration?

dengert commented 7 years ago

What most applications do is find all the certificates and private keys before doing any login. Then if a private key is needed because they are usually CKA_PRIVATE=True, the application does a C_Login.

libp11 looks at the flag and sets loginRequired and uses it in a number of places. src/eng_back.c:ctx_login for example. I don't know why it is not using the pin-value=1111 and doing a C_Login at this point.

It could be that ctx_load_privkey is called and the SafeNet has it marked as CKA_PRIVATE=False line 852 worksm and no login is ever done. 852 pk = ctx_load_key(ctx, s_key_id, ui_method, callback_data, 1, 0); 853 if (pk == NULL) / Try again with login / 854 pk = ctx_load_key(ctx, s_key_id, ui_method, callback_data, 1, 1);

But if @mtrojnar fixes pkcs11_autuenticate to do a C_Login(CKU_USER) that might work too.

You could try gdb or other debugger to see what is going on.

mtrojnar commented 7 years ago

../src/configure: line 13750: src/libp11.map: No such file or directory

Looks like 20c7b582b881533e7cd864e03f1c3fa58d767068. @nmav?

mtrojnar commented 7 years ago

But there are times when the token could loose the login state because of interference from other processes or even a powered down USB reader.

There is nothing we can do at this point. Those objects are gone. Logging in again won't restore them. It will create new objects.

pkcs11_authenticate could be passed another parameter indication it should try a CKU_USER login and/or a CKU_CONTEXT_SPECIFIC login depending on why it was being called.

No, re-login would destroy the previously acquired object handles. (this sentence has been edited for a more clear wording)

dengert commented 7 years ago

You said: "would destroy all the previously acquired handles" I don't understand. Which handles?

PKCS#11-2.20 says: "3. Call C_Login to log the user into the token. Since all sessions an application has with a token have a shared login state, C_Login only needs to be called for one of the sessions."

"When the user type is either CKU_SO or CKU_USER, if the call succeeds, each of the application's sessions will enter either the "R/W SO Functions" state, the "R/W User Functions" state, or the "R/O User Functions" state."

Are you saying this is something to do with pkcs11_authenticate() destroying the handles?

mtrojnar commented 7 years ago

@dengert What I'm saying is that pkcs_authenticate() was not designed to mess with CKU_SO or CKU_USER logins, as they modify the list of available object handles.

dengert commented 7 years ago

In @mirozitnansky 's case , the private key is already available and already has a handle so there is no messing with available object handles.

It looks like SafeNet was implemented to PKCS#11-2.10 which does not have CKA_ALWAYS_AUTHENTICATE or CKU_CONTEXT_SPECIFIC and the SafeNet's "No Public Crypto" option is their attempt to force a C_Login(CKU_USER) login.

mirozitnansky commented 7 years ago

@mtrojnar I tried latest commit and it works as expected. When ?pin-value=1111 is present in URL, Sing work well even with public object in FIPS mode. But this has one downside.

I successfully created CA pair. Now I want to have set all needed in openssl.cnf, including URL for key, so user who is signing client certificate request, doesn't have to understand details of pkcs11 URL format. So I have set, among other things

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = .
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

# The root key and root certificate.
private_key = "pkcs11:serial=0000%3a84324;token=OPENSSL;object=CA;object-type=private?pin-value=1111"

certificate       = $dir/certs/ca.cert.pem

then users call only

openssl ca -config openssl.cnf -extensions usr_cert -notext -md sha256 -in csr/${filename}.csr.pem -out certs/${filename}.cert.pem -engine pkcs11 -keyform engine

without URL part.

Problem with this setup is, everyone who can read openssl.cnf knows the pin for the token with key. I can possibly make openssl.cnf readable only for user who knows pin, but then I cannot enforce dual-control policy, when 2 people know 2 parts of pin and enter it one after another when Signing. But these are thing I can imagine to live without.

Maybe last thing, couldn't it be somehow implemented, at start of pksc11 session when GetInfo is called, to remember (or Login right away) that CKF_LOGIN_REQUIRED=1, and request pin when crypto operation is called same as when object is private and NO PUBLIC CRYPTO IS is enabled?

I can imagine it's easier said than done. :) Thanks.

dengert commented 7 years ago

Are you saying any user can get the CA to sign a certificate? You let users on your CA machine?

with regard to: "Maybe last thing, couldn't it be somehow implemented, at start of pksc11 session when GetInfo is called, to remember (or Login right away) that CKF_LOGIN_REQUIRED=1, and request pin when crypto operation is called same as when object is private and NO PUBLIC CRYPTO IS is enabled?"

As I said above if you made your private keys CKA_PRIVATE=True, it would do a C_Login early:

"It could be that ctx_load_privkey is called and the SafeNet has it marked as CKA_PRIVATE=False line 852 works and no login is ever done. 852 pk = ctx_load_key(ctx, s_key_id, ui_method, callback_data, 1, 0); 853 if (pk == NULL) / Try again with login / 854 pk = ctx_load_key(ctx, s_key_id, ui_method, callback_data, 1, 1);

mirozitnansky commented 7 years ago

Our HSM is external, network accessed (under IPtables rules), device locked in in own dedicated rack. There are servers (ha cluster), which are configured to access and use HSM over network, for different tasks than CA signing. These have restricted access, so I planned to use them also as CA machine. But they are operated by different people than those responsible for CA siging. If there is secret pin (which is not when pin is written in config file) based access on token which store CA key (one of many others in HSM), it would be secure to put CA on the same machine, because only selected people would be pin custodians.

I am not saying this is ideal scenario, I am just trying to use existing infrastructure and this would be secure "enough" for our use cases.

Those lines of code you put in here, that is something that could be done in future? Because now, when device has NO_PUBLIC_CRYPTO disabled and key is private, it's not found by engine.

mtrojnar commented 7 years ago

PKCS#11 v2.20 says:

CKF_LOGIN_REQUIRED 0x00000004 True if there are some cryptographic functions that a user must be logged in to perform

CKF_LOGIN_REQUIRED never meant that all cryptographic functions of the device require the user to be logged in.

mirozitnansky commented 7 years ago

I understand, didn't mean to play smartass, just trying to find way. It seems to as game with words, when who is the one who decided which operations are some... Can you imagine implement engine parameter FORCE_LOGIN=1 which would work similarly as when pin-value is present in URL, and would force user to Login at beginning of the session?

Anyway, you've both done a lot to help me, thanks for your time. If there won't be any other solution from me as having pin-value in URL to force login, I am still thankful.

mtrojnar commented 7 years ago

It seems to as game with words, when who is the one who decided which operations are some...

As @dengert wrote, this is exactly what CKA_PRIVATE is used for...

mtrojnar commented 7 years ago

Can you imagine implement engine parameter FORCE_LOGIN=1 which would work similarly as when pin-value is present in URL, and would force user to Login at beginning of the session?

Yes, a new engine parameter is definitely feasible. Give me a few days.

mirozitnansky commented 7 years ago

That's great, it should resolve all my issues. I will be checking new commits.

dengert commented 7 years ago

@mtrojnar You may not need a new parameter. You could use the fact that the module does not recognize the CKA_ALWAYS_AUTHENTICATE attribute and save as a flag for later. Then if C_SignInit returns CKR_USER_NOT_LOGGED_IN, do a C_Login(CKU_USER).

mtrojnar commented 7 years ago

@dengert I am not going to invoke C_Login(CKU_USER) from within a signing function. I already explained my rationale for this decision.

mirozitnansky commented 7 years ago

Sorry, that was missclick.

dengert commented 7 years ago

@mtrojnar Sorry we disagree. Its your project.

mirozitnansky commented 7 years ago

Now when I added new parameter to my openssl.cnf

[ openssl_def ]
engines = engine_section

[ engine_section ]
pkcs11 = pkcs11_section

[ pkcs11_section ]
engine_id = pkcs11
dynamic_path = /usr/lib64/engines/libpkcs11.so
MODULE_PATH = /opt/safenet/protecttoolkit5/ptk/lib/libcryptoki.so
FORCE_LOGIN=1
default_algorithms = ALL
init = 0

it ended up

Error configuring OpenSSL 140520159197088:error:260AB088:engine routines:ENGINE_ctrl_cmd_string:command takes no input:eng_ctrl.c:340: 140520159197088:error:260BC066:engine routines:INT_ENGINE_CONFIGURE:engine configuration error:eng_cnf.c:204:section=pkcs11_section, name=FORCE_LOGIN, value=1 140520159197088:error:0E07606D:configuration file routines:MODULE_RUN:module initialization error:conf_mod.c:237:module=engines, value=engine_section, retcode=-1

If i change FORCE_LOGIN=1 to FORCE_LOGIN removing '=1'

error on line 16 of openssl.cnf 139821941036960:error:0E079065:configuration file routines:DEF_LOAD_BIO:missing equal sign:conf_def.c:362:line 16

Wham am I doing wrong? If i remember correctly, similar happened to me at beginning of my integration attempts with VERBOSE parameter. Thanks

nmav commented 7 years ago

To put my two cents in, the safenet hsms are too popular today to handle them as special cases. I kind of like the suggestion of Doug, though in the end introduces quite some complexity.

mirozitnansky commented 7 years ago

BTW, received response from gemalto support, they explained that module supports algorithms of 2.20 but nothing else. v2.10 compliance is required to work with their module. In this specific case, NO_PUBLIC_CRYPTO enabled / public object, they suggest to detect CKF_LOGIN_REQUIRED from token and call C_LOGIN regardless of following acticities as CK_USER based on this flag. In the other words, nothing we have discussed here ... and no help with issue itself at all.

@nmav we chose Protectserver model because of it's ability to run custom made C application (they call FM module) within HSM module security domain, so it can access clear keys. That is fairly unique as far as I know. To be fair here, safenet's second product range called LUNA SA support OpenSSL integration out of the box. But we needed FM module at first place, CA keys and OpenSSL integration comes second. That's why was exited when found libp11 project.

mtrojnar commented 7 years ago

FORCE_LOGIN=1

According to the manual (https://www.openssl.org/docs/man1.0.2/apps/config.html) the proper invocation is: FORCE_LOGIN = EMPTY

dengert commented 7 years ago

A few questions about SafeNet:

mirozitnansky commented 7 years ago

I swear I didn't understood that explanation this way... to use EMPTY as actual value...

Anyway thanks. I tested it, now it correctly ask for pin when having public object and no public crypto enabled. But I thought, you'll implement it way, FORCE_LOGIN will call C_Login regardless activity/configuration. Now when having private object and no public crypt disabled, it just didn't find the key, which it would if user is logged.

[root@mzcentos ca_ptk]# openssl req -config openssl.cnf -key "pkcs11:model=0000;manufacturer=SafeNet%20Inc.;serial=0000%3a84324;token=OPENSSL;id=%42%19%72%91%10%73%e4%b1%e0%7f%67%fe%2f%31%a8%6c%e6%5c%ca%66;object=CA;object-type=private" -keyform engine -engine pkcs11 -new -x509 -days 7300 -sha256 -extensions v3_ca -out certs/ca.cert.pem
PKCS#11: Initializing the engine
logger: s_ctData->ft = 0x0xabd2b0
logger: s_ctData->ft->C_Initialize = 0x0x7fe02e3f2e20
Found 2 slots
engine "pkcs11" set.
Loading private key "pkcs11:model=0000;manufacturer=SafeNet%20Inc.;serial=0000%3a84324;token=OPENSSL;id=%42%19%72%91%10%73%e4%b1%e0%7f%67%fe%2f%31%a8%6c%e6%5c%ca%66;object=CA;object-type=private"
Looking in slot -1 for key: id=421972ffffff911073ffffffe4ffffffb1ffffffe07f67fffffffe2f31ffffffa86cffffffe65cffffffca66 label=CA
[0] SafeNet Software Only.:84                    (OPENSSL)
[1] SafeNet Software Only.     login             (AdminToken (0000))
Found slot:  SafeNet Software Only.:84324
Found token: OPENSSL
Found 0 certificate:
No private keys found.
PKCS11_get_private_key returned NULL
cannot load Private Key from engine
140600873306016:error:26096080:engine routines:ENGINE_load_private_key:failed loading private key:eng_pkey.c:126:

PC_PRVOBJ.txt Is this somehow solvable?

Just to be sure, I am really THANKFULL even for current solution. Only downside is, until hsm mode is switched to FIPS, it won't be required to enter pin when signing the certification request.

mirozitnansky commented 7 years ago

@dengert

Does SafeNet provide any vendor provided attributes? For example to find out if NO PUBLIC CRYPTO is enabled.

There are some, but nothing can be used for NPC mode identification. They are saying CKF_LOGIN_REQUIRED should be sufficient to detect login requirement. Everything documentation says I posted before https://github.com/OpenSC/libp11/issues/160#issuecomment-302888745

Are there multiple versions of the SafeNet module? Could code use the CK_TOKEN_INFO or CK_SLOT_INFO to determine if NO PUBLIC CRYPTO is enabled? Do they all have manufacturer="SafeNet Inc."?

I can't tell for sure. Currently I think manufacturer="SafeNet Inc." could be also used for LUNA SA hsms. But, as Safenet was 2 years ago acquired by Gemalto, it's quite possible they will change manufacturer name to Gemalto in future firmwares. So this is completely unreliable.

Does the NO PUBLIC CRYPTO require the C_Login before the C_SignInit? So if a C_SignInit fails with CKR_USER_NOT_LOGGED_IN, is it expecting a C_Login and second call to C_SignInit? Or does it do something like complete the C_SignInit and return CKR_USER_NOT_LOGGED_IN, expecting a C_Login and then C_Sign, C_SignFinal or C_SignUpdate. It must be keeping some state internal that a C_Login was done as expected.

https://github.com/OpenSC/libp11/issues/160#issuecomment-302888745 says _all the cryptographic CxxxInit

In second document I found this definition.

No Public Crypto The No Public Crypto flag, when set, indicates that no user can perform a cryptographic operation without having first authenticated themselves. When this flag is set, each token in the system will have the PKCS #11 CKF_LOGIN_REQUIRED flag set to indicate that applications must authenticate before operations are allowed. Note that this security flag does not affect the Admin token which always requires authentication for access. NOTE: This flag does not imply that public key cryptography is not allowed. Setting this flag will not prevent RSA processing.

Utilities that come with HSM for certification management always require to enter pin before C_Sign, regardless mode so I have quick way to test it.

dengert commented 7 years ago

So it sounds like they interpret CKF_LOGIN_REQUIRED to mean do a C_Login up front before looking for keys?

It looks like the ctx_load_privkey has be modified to test for if (!ctx->force_login) which should have fixed the issue from https://github.com/OpenSC/libp11/issues/160#issuecomment-303234149

What I was also asking about: Does SafeNet require C_Login to be called for every C_xxxInit call or can a single C_Login be done once for multiple crypto operations.

mirozitnansky commented 7 years ago

@dengert you call C_Login once, than repeat crypto operations how many times you wish in same session, untis it closes.

nmav commented 7 years ago

@mirozitnansky So it the only issue that this HSM doesn't support the CKU_CONTEXT_SPECIFIC? If C_Login falls back to using CKU_USER in the case CKR_USER_TYPE_INVALID is returned, would it address the issue?

btw. I'm trying to replicate that behavior in mock pkcs11 module https://gitlab.com/gnutls/gnutls/merge_requests/398/

mirozitnansky commented 7 years ago

Yes, I suppose. That would address compatibility with pkcs v2.10, which hasn't CKU_CONTEXT_SPECIFIC specified. But this is my option, I am no expert in this.

But setting FORCE_LOGIN, already overcome issue by forces engine to login before C_Sign CKU_USER. In my last comment to latest patch, I was just asking, if FORCE_LOGIN could work for every session, regardless of activity.

dengert commented 7 years ago

So would some pseudo code like this work: pkcs11_authenticate takes and extra argument, the user_type for C_Login. CKA_ALWAYS_AUTHENTICATE use of pkcs11_authenticate would pass CKU_CONTEXT_SPECIFIC

rv = C_XXXXInit(...) if (rv == CKR_USER_NOT_LOGGED_IN) { rv = pkcs11_authenticate(key, CKR_USER); if (rv == CKR_OK) rv = C_XXXXInit(...) } (rv is either OK or that last failure.)

replace every crypto Init call with something like the above. (Could be a macro) No need for force login. Above should work for every module and catch the first or every time module wants a new login.

mtrojnar commented 7 years ago

I was just asking, if FORCE_LOGIN could work for every session, regardless of activity.

It does. It just needs​ to be enabled, which you forgot to do.

mirozitnansky commented 7 years ago

So I ran same test again

list of slot 0

[root@mzcentos ~]# ctkmu l --slot-num=0 -v
ProtectToolkit C Key Management Utility 5.2.0
Copyright (c) Safenet, Inc. 2009-2016

logger: s_ctData->ft = 0x0x1c49390
logger: s_ctData->ft->C_Initialize = 0x0x7fa2ea647e20
Do you wish to view private (user) objects [y/N]: y   

Please enter User's PIN for the token in slot 0: 

Manufacturer      = SafeNet Inc.
Label             = OPENSSL                         
Flags             = 0x649 (RNG USER-PIN-INIT CLOCK TOKEN-INIT DUAL-CRYPTO)

Public and Private Objects:
CA_v1                            - PUBLIC_KEY      RSA         

Attribute list for object CA_v1, slot 0
Private      = 0
Sensitive    = 0
Modifiable   = 1
Import       = 0
Export       = 0
Wrap         = 0
Unwrap       = 0
Exportable   = 0
Extractable  = 0
Derive       = 0
Encrypt      = 0
Decrypt      = 0
Sign         = 0
Verify       = 0
Key Size     = 3072 bits

CA_v1                            - PRIVATE_KEY     RSA         

Attribute list for object CA_v1, slot 0
Private      = 1
Sensitive    = 1
Modifiable   = 1
Import       = 0
Export       = 0
Wrap         = 0
Unwrap       = 0
Exportable   = 0
Extractable  = 0
Derive       = 0
Encrypt      = 0
Decrypt      = 0
Sign         = 1
Verify       = 0
Sign Local   = 0
Key Size     = 3072 bits

private key is private object. NO_PUBLIC_CRYPTO is disabled

print of used openssl config - engine.cnf and sign attempt

[root@mzcentos ca_ptk]# cat engine.cnf
openssl_conf = openssl_def

[ openssl_def ]
engines = engine_section

[ engine_section ]
pkcs11 = pkcs11_section

[ pkcs11_section ]
engine_id = pkcs11

dynamic_path = /root/git/libp11/src/.libs/pkcs11.so
MODULE_PATH = /opt/safenet/protecttoolkit5/ptk/lib/libcryptoki.so
FORCE_LOGIN = EMPTY
VERBOSE = EMPTY
default_algorithms = ALL
init = 0
[root@mzcentos ca_ptk]# openssl req -config engine.cnf -key "pkcs11:token=OPENSSL;object=CA_v1;object-type=private" -keyform engine -engine pkcs11 -new -x509 -days 7300 -sha384 -out certs/ca.cert.pem
PKCS#11: Initializing the engine
logger: s_ctData->ft = 0x0x240ab20
logger: s_ctData->ft->C_Initialize = 0x0x7feff7cafe20
Found 2 slots
engine "pkcs11" set.
Loading private key "pkcs11:token=OPENSSL;object=CA_v1;object-type=private"
Looking in slot -1 for key: label=CA_v1
[0] SafeNet Software Only.:84                    (OPENSSL)
[1] SafeNet Software Only.     login             (AdminToken (0000))
Found slot:  SafeNet Software Only.:84324
Found token: OPENSSL
Found 0 certificate:
No private keys found.
PKCS11_get_private_key returned NULL
cannot load Private Key from engine
140668679198624:error:26096080:engine routines:ENGINE_load_private_key:failed loading private key:eng_pkey.c:126:
unable to load Private Key

pkcs11 log PC_PRIVOBJ.txt

there is no C_Login

mirozitnansky commented 7 years ago

It's actually pretty similar behavior to p11tool, even if is set parameter --login to force user login, key is still not found

[root@mzcentos ca_ptk]# GNUTLS_PIN=1111 p11tool -d 9999 --provider /opt/safenet/protecttoolkit5/ptk/lib/libcryptoki.so --list-all  "pkcs11:token=OPENSSL" --login
Setting log level to 9999
|<2>| p11: Initializing module: /opt/safenet/protecttoolkit5/ptk/lib/libcryptoki.so
logger: s_ctData->ft = 0x0x11363a0
logger: s_ctData->ft->C_Initialize = 0x0x7ffabb2a3e20
Trusted: no
Wrap: no
CA: no
Login: yes
SO Login: no
Detailed URLs: no

|<2>| Initializing PKCS #11 modules
|<3>| ASSERT: pkcs11.c:2257
|<2>| p11: No login required.
|<3>| ASSERT: pkcs11.c:1647
|<3>| ASSERT: pkcs11.c:2466
Object 0:
    URL: pkcs11:model=0000;manufacturer=SafeNet%20Inc.;serial=0000%3a84324;token=OPENSSL;id=%63%b0%56%d5%c8%90%57%89%87%79%11%0a%04%60%2a%85%2b%de%f8%52;object=CA_v1;object-type=public
    Type: Public key
    Label: CA_v1
    ID: 63:b0:56:d5:c8:90:57:89:87:79:11:0a:04:60:2a:85:2b:de:f8:52
nmav commented 7 years ago

It's actually pretty similar behavior to p11tool, even if is set parameter --login to force user login, key is still not found

That is because the CKF_LOGIN_REQUIRED is not set for that token. When this flag is absent p11tool will not attempt to login. I'm starting to think that this hsm has its own version of pkcs11 implemented.

However, could that been a key that was generated with "no public crypto" disabled and listed when "no public crypto" is enabled?

mirozitnansky commented 7 years ago

NO_PUBLIC_CRYPTO is one of the security flags which can be turned on/off for entire HSM, is has nothing to do with specific token or key generation process.

The following values are valid for security mode: flag Description F Fips 140-2 Mode (equivalent to -faclntu) a Only allow FIPS Approved Algorithms c No Public Crypto d Des Keys Even Parity Allowed e Entrust compliant E User Specified ECC Domain Parameters Allowed i Increased Security Level l Lock Security Mode n No Clear Pins allowed N Full Secure Messaging Encryption p Pure PKCS#11 Mode t Tamper before upgrade u Authentication Protection U Full Secure Messaging Signing 0 All flags cleared

If object in token is private or public, is a attribute of that object set in time of creation.

So I generate public object in when NO_PUBLIC_CRYPTO is set off (CKF_LOGIN_REQUIRED=0), then I set hsm flag NO_PUBLIC_CRYPTO on (that leads to CKF_LOGIN_REQUIRED=1 on every token), but object itself stays public. Same with private object. Private object which require call C_Login to be found, can exists even if NO_PUBLIC_CRYOTO is diabled. which is this case.

That's why I was asking to FORCE_LOGIN regardless of activity/setting/flags. In other words, if FORCE_LOGIN than always call C_Login second to C_Initialize. If force login works only when some conditions are met, than that's not really a FORCE.

But again, I am layman here who doesn't have in-depth knowledge of the topic.

mtrojnar commented 7 years ago

Okay. I'll fix it.