Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not require repeated authentication in the pkcs15init layer when checking ACLs #2916

Merged
merged 1 commit into from
Nov 13, 2023

Conversation

Jakuje
Copy link
Member

@Jakuje Jakuje commented Oct 24, 2023

As discussed in the #2903 this basically moves the check out of the libopensc to prevent zero-lenght pin bypass there, but allow checking login status in the pkcs15init layer, which happens usually during card enrollment (key generation, ...).

Fixes: #2903

Checklist
  • PKCS#11 module is tested
  • Windows minidriver is tested
  • macOS tokend is tested

@frankmorgner
Copy link
Member

I still believe that a clean solution would track the login state on an upper layer. In PKCS#11, one should leverage slot->login_user; in pkcs15-init propably verify_pin should store some global authentication state that is used in the lower layers, and so on. If we apply the suggested solution, we cannot detect whether sc_pkcs15_get_pin_info returned "logged in", because the this process verified the PIN or because some other concurrently running process logged in.

That being said, maybe this risk is acceptable during the enrollment. Since I don't have a quick alternative solution, we may need to accept this as is.

@dengert
Copy link
Member

dengert commented Oct 26, 2023

How about if a process has not done a verify_pin, a check for login state would always return not logged in. Once a verify pin was successful, a check for login state would do what it does now. This flag would be saved per pin. If logout or disconnect was done, this would reset the flag to force the next verify to

A login process with or with or without PIN PAD reader would then always endup having to send a verify pin command.

Should also work for screen unlock.

@Jakuje
Copy link
Member Author

Jakuje commented Oct 26, 2023

How about if a process has not done a verify_pin, a check for login state would always return not logged in. Once a verify pin was successful, a check for login state would do what it does now. This flag would be saved per pin. If logout or disconnect was done, this would reset the flag to force the next verify to

This sounds to me as a best proposal so far. I had something like that in my mind, but did not manage to put it into the code yet. Do you want to propose a change for this or should I look into that?

@larssilven
Copy link
Contributor

I can confirm that my test is working now.
Thank you very much for the help.

@dengert
Copy link
Member

dengert commented Oct 26, 2023

This sounds to me as a best proposal so far. I had something like that in my mind, but did not manage to put it into the code yet. Do you want to propose a change for this or should I look into that?

Why don't you look at it.

Question would this be done in PKCS11 layer vs PKCS15 layer.
Login and screen unlock on linux and maybe MacOS use PKCS11. Windows minidriver makes pkcs15 calls, but not sure if it tests login state of card.

In #2903 script calls multiple pkcs15 tools so each process ends up testing card for login state.

If done at pkcs15 layer, user could set environment variable to use the old way in scripts or when calling
long running processes like firefox or thunderbird. This would never by seen by login or screen unlock.

@Jakuje
Copy link
Member Author

Jakuje commented Oct 26, 2023

Question would this be done in PKCS11 layer vs PKCS15 layer. Login and screen unlock on linux and maybe MacOS use PKCS11. Windows minidriver makes pkcs15 calls, but not sure if it tests login state of card.

I would say in pkcs15. The pkcs11 itself already has login state tracking as part of session if I am right. And I do not think there is a way to get trough readonly to authentication session without C_Login().

In #2903 script calls multiple pkcs15 tools so each process ends up testing card for login state.

Yes, but this is a setup script without pinpad yet. The logs that we looked at were produced by the following steps, where the keys are generated, for example as provided by Peter, which was single process, single call to pkcs15init and which was prompting for pins needlessly.

@dengert
Copy link
Member

dengert commented Oct 27, 2023

I would say in pkcs15. The pkcs11 itself already has login state tracking as part of session if I am right.

Correct. but PKCS15 can ask a driver for the login state: sc_pkcs15init_get_pin_info this ends up with pin cmd SC_PIN_CMD_GET_INFO card driver set SC_CARD_CAP_ISO7816_PIN_INFO

And I do not think there is a way to get trough readonly to authentication session without C_Login().

No, but for example: pkcs11-tool.c will only prompt user https://github.com/OpenSC/OpenSC/blob/master/src/tools/pkcs11-tool.c#L1816 and then will send a pin from --pin or NULL https://github.com/OpenSC/OpenSC/blob/master/src/tools/pkcs11-tool.c#L1855-L1858 This should then cause PIN PAD reader to prompt for pin.

Other applications may have similar code.

Problem was in order to cut out PIN PAD prompts e6f7373 would call sc_pkcs15_get_pin_info and some cards would query the card for the login state. Works well if user applications are sharing the login state. But causes login or screen saver to avoid checking if real user is still at the workstation.

So user sharing card login state vs is this the real user unlocking screen or logging are different.

@Jakuje
Copy link
Member Author

Jakuje commented Nov 6, 2023

Added explicit tracking of the login state in the pkcs15 layer. Needs more testing, I hope I will be able to do tomorrow.

Copy link
Member

@frankmorgner frankmorgner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my suggestion would be add a list of PINs that have been verified to struct sc_profile. If I read the code correctly, then the profile is available in all calls of the pkcs15init layer and it may be used to carry such volatile information. This could look like this:

struct sc_profile {
    ....
    struct {
        unsigned int type;
        int reference;
        int verified;
    } pin_status[SC_PKCS15_MAX_PINS];
    ...
}
  • If sc_pkcs15init_verify_secret() was successful, then save type and reference in the first unused triple and set verified to 1.
  • Before calling the PIN verification with a PIN pad in sc_pkcs15init_verify_secret() (in both cases, pin_obj and !pin_obj), then iterate through the list and check if the secret (type/reference) has been verified before and the PIN is still verified. If so, do not request the actual PIN verification at the PIN pad and continue with the rest.

src/pkcs15init/pkcs15-lib.c Outdated Show resolved Hide resolved
if (r == SC_SUCCESS &&
auth_info.logged_in == SC_PIN_STATE_LOGGED_IN &&
auth_info.pin_verified == 1) {
LOG_FUNC_RETURN(ctx, r);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in case of a failure, we should still try to call sc_pkcs15_verify_pin() as below

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik that is what is done now. We return only if we got the info, we are logged in according to the card and this PIN was previously provided.

src/libopensc/pkcs15.h Outdated Show resolved Hide resolved
@Jakuje
Copy link
Member Author

Jakuje commented Nov 9, 2023

my suggestion would be add a list of PINs that have been verified to struct sc_profile. If I read the code correctly, then the profile is available in all calls of the pkcs15init layer and it may be used to carry such volatile information. This could look like this:

What would be the advantage against tracking this inside of the auth (PIN) structure as I proposed in this PR?

* If `sc_pkcs15init_verify_secret()` was successful, then save `type` and `reference` in the first unused triple and set `verified` to `1`.

I think that the PIN does not need to be verified through the sc_pkcs15init_verify_secret() first. The general case as visible from the logs in #2903 (they are removed now though) is that the pin is provided through the C_Login() API, which does not go through this function. It goes only through sc_pkcs15_verify_pin_with_session_pin(), which does not have the sc_profile information. Here we have only the auth object which I am using.

* Before calling the PIN verification with a PIN pad in `sc_pkcs15init_verify_secret()` (in both cases, `pin_obj` and `!pin_obj`), then iterate through the list and check if the secret (`type`/`reference`) has been `verified` before and the PIN is still verified. If so, do **not** request the actual PIN verification at the PIN pad and continue with the rest.

The case !pin_obj means we have some pin reference and no auth object associated with this pin reference. I can not guess what cases is this going to solve as I mentioned above.

@frankmorgner
Copy link
Member

Before going into implementation details, I stumbled across your analysis of the log here:

The general case as visible from the logs in #2903 (they are removed now though) is that the pin is provided through the C_Login()

If I understand correctly, then, at least in the case of using pkcs15init via PKCS#11, then the required PIN is always verified before. So why, actually, should we bother of tracking the authentication place in the first place? In pkcs15init, we could add sc_pkcs15_get_pin_info() before every PIN validation and the PIN will not be asked multiple times and we're done. What would be the drawback? It is possible that OpenSC doesn't enforce a PIN validation before initializing the card (either via pkcs15init in PKCS#11 or in pkcs15-init). I don't consider this to be a big problem, since the card always checks the required ACLs.

So maybe we don't need any complicated state tracking after all and just add sc_pkcs15_get_pin_info() to pkcs15init directly without modifying anything else...

@Jakuje
Copy link
Member Author

Jakuje commented Nov 10, 2023

I have quite limited knowledge of the pkcs15init and its works from inside to the pkcs11, basically limited only to the myeid card's case we are solving now. Likely there will be other ways in other drivers that will bite us later.

If I understand correctly, then, at least in the case of using pkcs15init via PKCS#11, then the required PIN is always verified before.

That is my understanding. At least after we removed the pin bypass in the pkcs15-pin.c, which is called from the pkcs11, there should not be any other way to bypass from the readonly session to RW session, which is required to access all pkcs15init operations (did not check all but for example C_GenerateKeyPair has this check).

So why, actually, should we bother of tracking the authentication place in the first place? In pkcs15init, we could add sc_pkcs15_get_pin_info() before every PIN validation and the PIN will not be asked multiple times and we're done. What would be the drawback?
It is possible that OpenSC doesn't enforce a PIN validation before initializing the card (either via pkcs15init in PKCS#11 or in pkcs15-init). I don't consider this to be a big problem, since the card always checks the required ACLs.

My thinking would be that this would allow pin bypass in the pkcs15init tool, but that might not be an issue as the pkcs15init usually does not enforce the ALC, until the -F flag is used to finish the enrollment + activate the ACLs (again, at least with MyEID, which is the only car have reasonable experience enrolling with pkc1s5init).

So maybe we don't need any complicated state tracking after all and just add sc_pkcs15_get_pin_info() to pkcs15init directly without modifying anything else...

I am not against. I can drop the top commits and keep only 40f4b72 which was already confirmed that it solves this particular issue, if you agree. I am all in for simpler solutions.

@larssilven
Copy link
Contributor

@Jakuje : The #2903 logs are back.

@frankmorgner
Copy link
Member

I have quite limited knowledge of the pkcs15init and its works from inside to the pkcs11, basically limited only to the myeid card's case we are solving now. Likely there will be other ways in other drivers that will bite us later.

I only have limited knowledge as well, and would be happy to see more release testing on pkcs15init. Considering that e6f7373 was specifically introduced with myeid in mind, I think that bad side effects for other cards are unlikely if we basically just move the call of sc_pkcs15_get_pin_info() to a different (and hopefully more appropriate) place.

I am not against. I can drop the top commits and keep only 40f4b72 which was already confirmed that it solves this particular issue, if you agree. I am all in for simpler solutions.

Yes, please do so and I'd be happy if @larssilven could try this out then.

The original code block from e6f7373 is still needed when pkcs15init
layer checks ACLs for PKCS#15 objects, but it should be kept out of
the libopensc, which is used for more authentication code paths
and can be used for PIN bypass.
@Jakuje
Copy link
Member Author

Jakuje commented Nov 11, 2023

Done. @larssilven can you double-check?

@popovec
Copy link
Member

popovec commented Nov 11, 2023

EDIT: Please consider the text below irrelevant, I did the test in the wrong container.

I have tested Jakuje:bypass-pkcs15init with use_pin_caching = false; here is the result:

$ pkcs15-init -C --so-pin=00000000 --so-puk=00000000
$ pkcs15-init --store-pin --id 01 --pin 11111111 --puk 11111111 --so-pin=00000000
$ pkcs15-init -F
$ pkcs11-tool -k --key-type RSA:1024 -l --pin 11111111
Using slot 0 with a present token (0x0)
error: PKCS11 function C_GenerateKeyPair failed: rv = CKR_GENERAL_ERROR (0x5)
Aborting.
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:4010:sc_pkcs15init_authenticate: called
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:4012:sc_pkcs15init_authenticate: path '3f005015', op=3
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:4028:sc_pkcs15init_authenticate: acl 0x55d0b56c1820
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:4043:sc_pkcs15init_authenticate: verify acl(method:1,reference:1)
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:3855:sc_pkcs15init_verify_secret: called
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:3859:sc_pkcs15init_verify_secret: get and verify PIN('PIN',type:0x1,reference:0x1)
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:2327:sc_pkcs15init_get_pin_reference: called
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:2336:sc_pkcs15init_get_pin_reference: found 2 auth objects; looking for AUTH object(auth_method:1,reference:1)
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:2342:sc_pkcs15init_get_pin_reference: check PIN(Security Officer PIN,auth_method:1,type:1,reference:3,flags:B0)
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:2342:sc_pkcs15init_get_pin_reference: check PIN(,auth_method:1,type:1,reference:1,flags:30)
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:2347:sc_pkcs15init_get_pin_reference: returning with: 1
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:3879:sc_pkcs15init_verify_secret: found PIN reference 1
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:3904:sc_pkcs15init_verify_secret: found PIN object ''
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:3908:sc_pkcs15init_verify_secret: PIN object ''; pin_obj->content.len:0
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-pin.c:304:sc_pkcs15_verify_pin: called
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-pin.c:313:sc_pkcs15_verify_pin: returning with: -1304 (Invalid PIN length)
P:1218511; T:0x139845939570752 13:26:34.316 [opensc-pkcs11] pkcs15-lib.c:3962:sc_pkcs15init_verify_secret: Cannot validate pkcs15 PIN: -1304 (Invalid PIN length)
P:1218511; T:0x139845939570752 13:26:34.316 [opensc-pkcs11] pkcs15-lib.c:4049:sc_pkcs15init_authenticate: returning with: -1304 (Invalid PIN length)
P:1218511; T:0x139845939570752 13:26:34.316 [opensc-pkcs11] pkcs15-lib.c:4130:sc_pkcs15init_create_file: Cannot create file: 'CREATE' authentication failed: -1304 (Invalid PIN length)
P:1218511; T:0x139845939570752 13:26:34.316 [opensc-pkcs11] pkcs15-lib.c:4146:sc_pkcs15init_create_file: returning with: -1304 (Invalid PIN length)
P:1218511; T:0x139845939570752 13:26:34.316 [opensc-pkcs11] pkcs15-myeid.c:672:myeid_create_key: Cannot create MyEID key file: -1304 (Invalid PIN length)
P:1218511; T:0x139845939570752 13:26:34.316 [opensc-pkcs11] pkcs15-lib.c:1616:sc_pkcs15init_generate_key: Cannot generate key: create key failed: -1304 (Invalid PIN length)
P:1218511; T:0x139845939570752 13:26:34.316 [opensc-pkcs11] pkcs15-lib.c:1680:sc_pkcs15init_generate_key: returning with: -1304 (Invalid PIN length)
P:1218511; T:0x139845939570752 13:26:34.316 [opensc-pkcs11] framework-pkcs15.c:3429:pkcs15_gen_keypair: sc_pkcs15init_generate_key returned -1304

As long as pin_cache is used, everything works fine.

@larssilven
Copy link
Contributor

I have tested this a0b6223 (jakuje/bypass-pkcs15init). Had some problems to find it.
And it works. Both with PIN pad reader and no PIN cache.

@larssilven
Copy link
Contributor

I modified my test application to not call C_Login before C_GenerateKeyPair. Then if C_GenerateKeyPair fails with CKR_USER_NOT_LOGGED_IN: C_Login will be called and the C_GenerateKeyPair will be called again.
But this fails both with PIN pad enabled and PIN pad not enabled:

  • If PIN pad is enabled then opensc asks for PIN from the pad when calling C_GenerateKeyPair and logs in when it got it. This is wrong since C_Login must be used to change login state according to PKCS#11.
  • If PIN pad is not enabled C_GenerateKeyPair returns CKR_GENERAL_ERROR when it must return CKR_USER_NOT_LOGGED_IN.
    I did these tests with no PIN cache. Logs with PIN pad disabled:
    pkcs11
    opensc

@frankmorgner
Copy link
Member

  1. @popovec Could you provide the complete log, because I think we are missing essential blocks at the beginning of the interaction. Since you have specified -l, the standard PIN should have been verified before generating a keypair. In combination with a0b6223, this should mean that sc_pkcs15init_verify_secret() should not reach the point where the PIN is validated. Did you update this PR correctly in your local branch?
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:2336:sc_pkcs15init_get_pin_reference: found 2 auth objects; looking for AUTH object(auth_method:1,reference:1)
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:2342:sc_pkcs15init_get_pin_reference: check PIN(Security Officer PIN,auth_method:1,type:1,reference:3,flags:B0)
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:2342:sc_pkcs15init_get_pin_reference: check PIN(,auth_method:1,type:1,reference:1,flags:30)
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:2347:sc_pkcs15init_get_pin_reference: returning with: 1
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:3879:sc_pkcs15init_verify_secret: found PIN reference 1
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:3904:sc_pkcs15init_verify_secret: found PIN object ''
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-lib.c:3908:sc_pkcs15init_verify_secret: PIN object ''; pin_obj->content.len:0
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-pin.c:304:sc_pkcs15_verify_pin: called
P:1218511; T:0x139845939570752 13:26:34.315 [opensc-pkcs11] pkcs15-pin.c:313:sc_pkcs15_verify_pin: returning with: -1304 (Invalid PIN length)
  1. Your log shows that pkcs15init correctly selects the standard PIN as required for generating a keypair. The PKCS#11 code for supplying a cached PIN, however, was only designed to propagate the SO PIN. I wonder if your configuration has ever worked with some previous version of OpenSC...

@frankmorgner
Copy link
Member

@larssilven: If PIN pad is enabled then opensc asks for PIN from the pad when calling C_GenerateKeyPair and logs in when it got it. This is wrong since C_Login must be used to change login state according to PKCS#11.

This "problem" occurs due to the overloaded semantics of PIN length == 0. get_pin() returns "object not found", but pkcs15init decides to continue anyway if the PIN can verified via PIN pad:

if (r == SC_ERROR_OBJECT_NOT_FOUND) {
if (p15card->card->reader->capabilities & SC_READER_CAP_PIN_PAD)
r = 0, use_pinpad = 1;
else
r = SC_ERROR_SECURITY_STATUS_NOT_SATISFIED;
}

This approach dates back at least 11 years and I'm not sure if we should remove it. One could argue that we should remove it, because we now have a better understanding on how to plug things together, also with a PIN pad reader. On the other hand, you could also argue to leave it to keep backward compatibility - since the user needs to authorize the transaction with a PIN (on the PIN pad), she still is in full control.

@larssilven
Copy link
Contributor

@frankmorgner
I think the user must know why he logs in to the card. If the pad prompts you for a password you want to know what it is used for. If C_Login always precedes prompting for password the application knows when to inform a the user why he should authenticate either from a computer or from the display of the pad.
Also if login to the card could just happen from anywhere in the code it is possible that it is done by mistake. If you use the pkcs#11 API then you know that login could just happen after C_Login but if that is not true this could introduce bugs.
Probably most applications is already using C_Login before doing restricted calls so it should not be mush of a problem to correct the implementation. But for applications not using C_Login there could be a configuration option to disable it.

@popovec
Copy link
Member

popovec commented Nov 12, 2023

I apologize, I performed the test in a container that contained a different version of opensc than the one I compiled.
I repeatedly tested a0b6223 (applied to the current master - 8fc2c20), it works both with the PIN cache enabled/disabled.

@Jakuje
Copy link
Member Author

Jakuje commented Nov 13, 2023

Thanks for verbose testing! It was also my concern that asking for a pin on pinpad from anywhere in the code is bad so I would even consider dropping the call to sc_pkcs15_verify_pin() from sc_pkcs15init_verify_secret(), but as this was working previously, somebody might depend on that so if, we should consider this after the release. I filled an issue #2933 for that.

Regarding the wrong return value, I would assume the C_GenerateKeyPair will fail without login here:

if (!(session->flags & CKF_RW_SESSION)) {

Unfortunately the debug log does not show much information about this. I can probably try tomorrow.

I am wondering if we should put together some test coverage with the pin cache disabled (and interactive PIN prompts) to make sure this works ok in all cases.

Regarding to this PR, do we consider this good enough to merge and for a release, or do we have some more concerns that I should address as part of this PR?

@Jakuje Jakuje merged commit 80cc5d3 into OpenSC:master Nov 13, 2023
35 of 36 checks passed
OpenSC 0.24.0 automation moved this from In progress to Done Nov 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

Asking for user PIN when already logged in.
5 participants