/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * This file deals with PKCS #11 passwords and authentication. */ #include "dev.h" #include "dev3hack.h" #include "seccomon.h" #include "secmod.h" #include "secmodi.h" #include "secmodti.h" #include "pkcs11t.h" #include "pk11func.h" #include "secitem.h" #include "secerr.h" #include "pkim.h" /************************************************************* * local static and global data *************************************************************/ /* * This structure keeps track of status that spans all the Slots. * NOTE: This is a global data structure. It semantics expect thread crosstalk * be very careful when you see it used. * It's major purpose in life is to allow the user to log in one PER * Tranaction, even if a transaction spans threads. The problem is the user * may have to enter a password one just to be able to look at the * personalities/certificates (s)he can use. Then if Auth every is one, they * may have to enter the password again to use the card. See PK11_StartTransac * and PK11_EndTransaction. */ static struct PK11GlobalStruct { int transaction; PRBool inTransaction; char *(PR_CALLBACK *getPass)(PK11SlotInfo *, PRBool, void *); PRBool(PR_CALLBACK *verifyPass)(PK11SlotInfo *, void *); PRBool(PR_CALLBACK *isLoggedIn)(PK11SlotInfo *, void *); } PK11_Global = { 1, PR_FALSE, NULL, NULL, NULL }; /*********************************************************** * Password Utilities ***********************************************************/ /* * Check the user's password. Log into the card if it's correct. * succeed if the user is already logged in. */ static SECStatus pk11_CheckPassword(PK11SlotInfo *slot, CK_SESSION_HANDLE session, char *pw, PRBool alreadyLocked, PRBool contextSpecific) { int len = 0; CK_RV crv; SECStatus rv; PRTime currtime = PR_Now(); PRBool mustRetry; int retry = 0; if (slot->protectedAuthPath) { len = 0; pw = NULL; } else if (pw == NULL) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } else { len = PORT_Strlen(pw); } do { if (!alreadyLocked) PK11_EnterSlotMonitor(slot); crv = PK11_GETTAB(slot)->C_Login(session, contextSpecific ? CKU_CONTEXT_SPECIFIC : CKU_USER, (unsigned char *)pw, len); slot->lastLoginCheck = 0; mustRetry = PR_FALSE; if (!alreadyLocked) PK11_ExitSlotMonitor(slot); switch (crv) { /* if we're already logged in, we're good to go */ case CKR_OK: /* TODO If it was for CKU_CONTEXT_SPECIFIC should we do this */ slot->authTransact = PK11_Global.transaction; /* Fall through */ case CKR_USER_ALREADY_LOGGED_IN: slot->authTime = currtime; rv = SECSuccess; break; case CKR_PIN_INCORRECT: PORT_SetError(SEC_ERROR_BAD_PASSWORD); rv = SECWouldBlock; /* everything else is ok, only the pin is bad */ break; /* someone called reset while we fetched the password, try again once * if the token is still there. */ case CKR_SESSION_HANDLE_INVALID: case CKR_SESSION_CLOSED: if (session != slot->session) { /* don't bother retrying, we were in a middle of an operation, * which is now lost. Just fail. */ PORT_SetError(PK11_MapError(crv)); rv = SECFailure; break; } if (retry++ == 0) { rv = PK11_InitToken(slot, PR_FALSE); if (rv == SECSuccess) { if (slot->session != CK_INVALID_HANDLE) { session = slot->session; /* we should have * a new session now */ mustRetry = PR_TRUE; } else { PORT_SetError(PK11_MapError(crv)); rv = SECFailure; } } break; } /* Fall through */ default: PORT_SetError(PK11_MapError(crv)); rv = SECFailure; /* some failure we can't fix by retrying */ } } while (mustRetry); return rv; } /* * Check the user's password. Logout before hand to make sure that * we are really checking the password. */ SECStatus PK11_CheckUserPassword(PK11SlotInfo *slot, const char *pw) { int len = 0; CK_RV crv; SECStatus rv; PRTime currtime = PR_Now(); if (slot->protectedAuthPath) { len = 0; pw = NULL; } else if (pw == NULL) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } else { len = PORT_Strlen(pw); } /* * If the token doesn't need a login, don't try to relogin because the * effect is undefined. It's not clear what it means to check a non-empty * password with such a token, so treat that as an error. */ if (!slot->needLogin) { if (len == 0) { rv = SECSuccess; } else { PORT_SetError(SEC_ERROR_BAD_PASSWORD); rv = SECFailure; } return rv; } /* force a logout */ PK11_EnterSlotMonitor(slot); PK11_GETTAB(slot)->C_Logout(slot->session); crv = PK11_GETTAB(slot)->C_Login(slot->session, CKU_USER, (unsigned char *)pw, len); slot->lastLoginCheck = 0; PK11_ExitSlotMonitor(slot); switch (crv) { /* if we're already logged in, we're good to go */ case CKR_OK: slot->authTransact = PK11_Global.transaction; slot->authTime = currtime; rv = SECSuccess; break; case CKR_PIN_INCORRECT: PORT_SetError(SEC_ERROR_BAD_PASSWORD); rv = SECWouldBlock; /* everything else is ok, only the pin is bad */ break; default: PORT_SetError(PK11_MapError(crv)); rv = SECFailure; /* some failure we can't fix by retrying */ } return rv; } SECStatus PK11_Logout(PK11SlotInfo *slot) { CK_RV crv; /* force a logout */ PK11_EnterSlotMonitor(slot); crv = PK11_GETTAB(slot)->C_Logout(slot->session); slot->lastLoginCheck = 0; PK11_ExitSlotMonitor(slot); if (crv != CKR_OK) { PORT_SetError(PK11_MapError(crv)); return SECFailure; } return SECSuccess; } /* * transaction stuff is for when we test for the need to do every * time auth to see if we already did it for this slot/transaction */ void PK11_StartAuthTransaction(void) { PK11_Global.transaction++; PK11_Global.inTransaction = PR_TRUE; } void PK11_EndAuthTransaction(void) { PK11_Global.transaction++; PK11_Global.inTransaction = PR_FALSE; } /* * before we do a private key op, we check to see if we * need to reauthenticate. */ void PK11_HandlePasswordCheck(PK11SlotInfo *slot, void *wincx) { int askpw = slot->askpw; PRBool NeedAuth = PR_FALSE; if (!slot->needLogin) return; if ((slot->defaultFlags & PK11_OWN_PW_DEFAULTS) == 0) { PK11SlotInfo *def_slot = PK11_GetInternalKeySlot(); if (def_slot) { askpw = def_slot->askpw; PK11_FreeSlot(def_slot); } } /* timeouts are handled by isLoggedIn */ if (!PK11_IsLoggedIn(slot, wincx)) { NeedAuth = PR_TRUE; } else if (askpw == -1) { if (!PK11_Global.inTransaction || (PK11_Global.transaction != slot->authTransact)) { PK11_EnterSlotMonitor(slot); PK11_GETTAB(slot)->C_Logout(slot->session); slot->lastLoginCheck = 0; PK11_ExitSlotMonitor(slot); NeedAuth = PR_TRUE; } } if (NeedAuth) PK11_DoPassword(slot, slot->session, PR_TRUE, wincx, PR_FALSE, PR_FALSE); } void PK11_SlotDBUpdate(PK11SlotInfo *slot) { SECMOD_UpdateModule(slot->module); } /* * set new askpw and timeout values */ void PK11_SetSlotPWValues(PK11SlotInfo *slot, int askpw, int timeout) { slot->askpw = askpw; slot->timeout = timeout; slot->defaultFlags |= PK11_OWN_PW_DEFAULTS; PK11_SlotDBUpdate(slot); } /* * Get the askpw and timeout values for this slot */ void PK11_GetSlotPWValues(PK11SlotInfo *slot, int *askpw, int *timeout) { *askpw = slot->askpw; *timeout = slot->timeout; if ((slot->defaultFlags & PK11_OWN_PW_DEFAULTS) == 0) { PK11SlotInfo *def_slot = PK11_GetInternalKeySlot(); if (def_slot) { *askpw = def_slot->askpw; *timeout = def_slot->timeout; PK11_FreeSlot(def_slot); } } } /* * Returns true if the token is needLogin and isn't logged in. * This function is used to determine if authentication is needed * before attempting a potentially privelleged operation. */ PRBool pk11_LoginStillRequired(PK11SlotInfo *slot, void *wincx) { return slot->needLogin && !PK11_IsLoggedIn(slot, wincx); } /* * make sure a slot is authenticated... * This function only does the authentication if it is needed. */ SECStatus PK11_Authenticate(PK11SlotInfo *slot, PRBool loadCerts, void *wincx) { if (!slot) { return SECFailure; } if (pk11_LoginStillRequired(slot, wincx)) { return PK11_DoPassword(slot, slot->session, loadCerts, wincx, PR_FALSE, PR_FALSE); } return SECSuccess; } /* * Authenticate to "unfriendly" tokens (tokens which need to be logged * in to find the certs. */ SECStatus pk11_AuthenticateUnfriendly(PK11SlotInfo *slot, PRBool loadCerts, void *wincx) { SECStatus rv = SECSuccess; if (!PK11_IsFriendly(slot)) { rv = PK11_Authenticate(slot, loadCerts, wincx); } return rv; } /* * NOTE: this assumes that we are logged out of the card before hand */ SECStatus PK11_CheckSSOPassword(PK11SlotInfo *slot, char *ssopw) { CK_SESSION_HANDLE rwsession; CK_RV crv; SECStatus rv = SECFailure; int len = 0; /* get a rwsession */ rwsession = PK11_GetRWSession(slot); if (rwsession == CK_INVALID_HANDLE) { PORT_SetError(SEC_ERROR_BAD_DATA); return rv; } if (slot->protectedAuthPath) { len = 0; ssopw = NULL; } else if (ssopw == NULL) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } else { len = PORT_Strlen(ssopw); } /* check the password */ crv = PK11_GETTAB(slot)->C_Login(rwsession, CKU_SO, (unsigned char *)ssopw, len); slot->lastLoginCheck = 0; switch (crv) { /* if we're already logged in, we're good to go */ case CKR_OK: rv = SECSuccess; break; case CKR_PIN_INCORRECT: PORT_SetError(SEC_ERROR_BAD_PASSWORD); rv = SECWouldBlock; /* everything else is ok, only the pin is bad */ break; default: PORT_SetError(PK11_MapError(crv)); rv = SECFailure; /* some failure we can't fix by retrying */ } PK11_GETTAB(slot)->C_Logout(rwsession); slot->lastLoginCheck = 0; /* release rwsession */ PK11_RestoreROSession(slot, rwsession); return rv; } /* * make sure the password conforms to your token's requirements. */ SECStatus PK11_VerifyPW(PK11SlotInfo *slot, char *pw) { int len = PORT_Strlen(pw); if ((slot->minPassword > len) || (slot->maxPassword < len)) { PORT_SetError(SEC_ERROR_BAD_DATA); return SECFailure; } return SECSuccess; } /* * initialize a user PIN Value */ SECStatus PK11_InitPin(PK11SlotInfo *slot, const char *ssopw, const char *userpw) { CK_SESSION_HANDLE rwsession = CK_INVALID_HANDLE; CK_RV crv; SECStatus rv = SECFailure; int len; int ssolen; if (userpw == NULL) userpw = ""; if (ssopw == NULL) ssopw = ""; len = PORT_Strlen(userpw); ssolen = PORT_Strlen(ssopw); /* get a rwsession */ rwsession = PK11_GetRWSession(slot); if (rwsession == CK_INVALID_HANDLE) { PORT_SetError(SEC_ERROR_BAD_DATA); slot->lastLoginCheck = 0; return rv; } if (slot->protectedAuthPath) { len = 0; ssolen = 0; ssopw = NULL; userpw = NULL; } /* check the password */ crv = PK11_GETTAB(slot)->C_Login(rwsession, CKU_SO, (unsigned char *)ssopw, ssolen); slot->lastLoginCheck = 0; if (crv != CKR_OK) { PORT_SetError(PK11_MapError(crv)); goto done; } crv = PK11_GETTAB(slot)->C_InitPIN(rwsession, (unsigned char *)userpw, len); if (crv != CKR_OK) { PORT_SetError(PK11_MapError(crv)); } else { rv = SECSuccess; } done: PK11_GETTAB(slot)->C_Logout(rwsession); slot->lastLoginCheck = 0; PK11_RestoreROSession(slot, rwsession); if (rv == SECSuccess) { /* update our view of the world */ PK11_InitToken(slot, PR_TRUE); if (slot->needLogin) { PK11_EnterSlotMonitor(slot); PK11_GETTAB(slot)->C_Login(slot->session, CKU_USER, (unsigned char *)userpw, len); slot->lastLoginCheck = 0; PK11_ExitSlotMonitor(slot); } } return rv; } /* * Change an existing user password */ SECStatus PK11_ChangePW(PK11SlotInfo *slot, const char *oldpw, const char *newpw) { CK_RV crv; SECStatus rv = SECFailure; int newLen = 0; int oldLen = 0; CK_SESSION_HANDLE rwsession; /* use NULL values to trigger the protected authentication path */ if (!slot->protectedAuthPath) { if (newpw == NULL) newpw = ""; if (oldpw == NULL) oldpw = ""; } if (newpw) newLen = PORT_Strlen(newpw); if (oldpw) oldLen = PORT_Strlen(oldpw); /* get a rwsession */ rwsession = PK11_GetRWSession(slot); if (rwsession == CK_INVALID_HANDLE) { PORT_SetError(SEC_ERROR_BAD_DATA); return rv; } crv = PK11_GETTAB(slot)->C_SetPIN(rwsession, (unsigned char *)oldpw, oldLen, (unsigned char *)newpw, newLen); if (crv == CKR_OK) { rv = SECSuccess; } else { PORT_SetError(PK11_MapError(crv)); } PK11_RestoreROSession(slot, rwsession); /* update our view of the world */ PK11_InitToken(slot, PR_TRUE); return rv; } static char * pk11_GetPassword(PK11SlotInfo *slot, PRBool retry, void *wincx) { if (PK11_Global.getPass == NULL) return NULL; return (*PK11_Global.getPass)(slot, retry, wincx); } void PK11_SetPasswordFunc(PK11PasswordFunc func) { PK11_Global.getPass = func; } void PK11_SetVerifyPasswordFunc(PK11VerifyPasswordFunc func) { PK11_Global.verifyPass = func; } void PK11_SetIsLoggedInFunc(PK11IsLoggedInFunc func) { PK11_Global.isLoggedIn = func; } /* * authenticate to a slot. This loops until we can't recover, the user * gives up, or we succeed. If we're already logged in and this function * is called we will still prompt for a password, but we will probably * succeed no matter what the password was (depending on the implementation * of the PKCS 11 module. */ SECStatus PK11_DoPassword(PK11SlotInfo *slot, CK_SESSION_HANDLE session, PRBool loadCerts, void *wincx, PRBool alreadyLocked, PRBool contextSpecific) { SECStatus rv = SECFailure; char *password; PRBool attempt = PR_FALSE; if (PK11_NeedUserInit(slot)) { PORT_SetError(SEC_ERROR_IO); return SECFailure; } /* * Central server type applications which control access to multiple * client applications to single crypto devices need to virtuallize the * login state. This is done by a callback out of PK11_IsLoggedIn and * here. If we are actually logged in, then we got here because the * higher level code told us that the particular client application may * still need to be logged in. If that is the case, we simply tell the * server code that it should now verify the clients password and tell us * the results. */ if (PK11_IsLoggedIn(slot, NULL) && (PK11_Global.verifyPass != NULL)) { if (!PK11_Global.verifyPass(slot, wincx)) { PORT_SetError(SEC_ERROR_BAD_PASSWORD); return SECFailure; } return SECSuccess; } /* get the password. This can drop out of the while loop * for the following reasons: * (1) the user refused to enter a password. * (return error to caller) * (2) the token user password is disabled [usually due to * too many failed authentication attempts]. * (return error to caller) * (3) the password was successful. */ while ((password = pk11_GetPassword(slot, attempt, wincx)) != NULL) { /* if the token has a protectedAuthPath, the application may have * already issued the C_Login as part of it's pk11_GetPassword call. * In this case the application will tell us what the results were in * the password value (retry or the authentication was successful) so * we can skip our own C_Login call (which would force the token to * try to login again). * * Applications that don't know about protectedAuthPath will return a * password, which we will ignore and trigger the token to * 'authenticate' itself anyway. Hopefully the blinking display on * the reader, or the flashing light under the thumbprint reader will * attract the user's attention */ attempt = PR_TRUE; if (slot->protectedAuthPath) { /* application tried to authenticate and failed. it wants to try * again, continue looping */ if (strcmp(password, PK11_PW_RETRY) == 0) { rv = SECWouldBlock; PORT_Free(password); continue; } /* applicaton tried to authenticate and succeeded we're done */ if (strcmp(password, PK11_PW_AUTHENTICATED) == 0) { rv = SECSuccess; PORT_Free(password); break; } } rv = pk11_CheckPassword(slot, session, password, alreadyLocked, contextSpecific); PORT_Memset(password, 0, PORT_Strlen(password)); PORT_Free(password); if (rv != SECWouldBlock) break; } if (rv == SECSuccess) { if (!contextSpecific && !PK11_IsFriendly(slot)) { NSSToken *token = PK11Slot_GetNSSToken(slot); if (token) { nssTrustDomain_UpdateCachedTokenCerts(token->trustDomain, token); (void)nssToken_Destroy(token); } } } else if (!attempt) PORT_SetError(SEC_ERROR_BAD_PASSWORD); return rv; } void PK11_LogoutAll(void) { SECMODListLock *lock = SECMOD_GetDefaultModuleListLock(); SECMODModuleList *modList; SECMODModuleList *mlp = NULL; int i; /* NSS is not initialized, there are not tokens to log out */ if (lock == NULL) { return; } SECMOD_GetReadLock(lock); modList = SECMOD_GetDefaultModuleList(); /* find the number of entries */ for (mlp = modList; mlp != NULL; mlp = mlp->next) { for (i = 0; i < mlp->module->slotCount; i++) { PK11_Logout(mlp->module->slots[i]); } } SECMOD_ReleaseReadLock(lock); } int PK11_GetMinimumPwdLength(PK11SlotInfo *slot) { return ((int)slot->minPassword); } /* Does this slot have a protected pin path? */ PRBool PK11_ProtectedAuthenticationPath(PK11SlotInfo *slot) { return slot->protectedAuthPath; } /* * we can initialize the password if 1) The toke is not inited * (need login == true and see need UserInit) or 2) the token has * a NULL password. (slot->needLogin = false & need user Init = false). */ PRBool PK11_NeedPWInitForSlot(PK11SlotInfo *slot) { if (slot->needLogin && PK11_NeedUserInit(slot)) { return PR_TRUE; } if (!slot->needLogin && !PK11_NeedUserInit(slot)) { return PR_TRUE; } return PR_FALSE; } PRBool PK11_NeedPWInit() { PK11SlotInfo *slot = PK11_GetInternalKeySlot(); PRBool ret = PR_FALSE; if (slot) { ret = PK11_NeedPWInitForSlot(slot); PK11_FreeSlot(slot); } return ret; } PRBool pk11_InDelayPeriod(PRIntervalTime lastTime, PRIntervalTime delayTime, PRIntervalTime *retTime) { PRIntervalTime time; *retTime = time = PR_IntervalNow(); return (PRBool)(lastTime) && ((time - lastTime) < delayTime); } /* * Determine if the token is logged in. We have to actually query the token, * because it's state can change without intervention from us. */ PRBool PK11_IsLoggedIn(PK11SlotInfo *slot, void *wincx) { CK_SESSION_INFO sessionInfo; int askpw = slot->askpw; int timeout = slot->timeout; CK_RV crv; PRIntervalTime curTime; static PRIntervalTime login_delay_time = 0; if (login_delay_time == 0) { login_delay_time = PR_SecondsToInterval(1); } /* If we don't have our own password default values, use the system * ones */ if ((slot->defaultFlags & PK11_OWN_PW_DEFAULTS) == 0) { PK11SlotInfo *def_slot = PK11_GetInternalKeySlot(); if (def_slot) { askpw = def_slot->askpw; timeout = def_slot->timeout; PK11_FreeSlot(def_slot); } } if ((wincx != NULL) && (PK11_Global.isLoggedIn != NULL) && (*PK11_Global.isLoggedIn)(slot, wincx) == PR_FALSE) { return PR_FALSE; } /* forget the password if we've been inactive too long */ if (askpw == 1) { PRTime currtime = PR_Now(); PRTime result; PRTime mult; LL_I2L(result, timeout); LL_I2L(mult, 60 * 1000 * 1000); LL_MUL(result, result, mult); LL_ADD(result, result, slot->authTime); if (LL_CMP(result, <, currtime)) { PK11_EnterSlotMonitor(slot); PK11_GETTAB(slot)->C_Logout(slot->session); slot->lastLoginCheck = 0; PK11_ExitSlotMonitor(slot); } else { slot->authTime = currtime; } } PK11_EnterSlotMonitor(slot); if (pk11_InDelayPeriod(slot->lastLoginCheck, login_delay_time, &curTime)) { sessionInfo.state = slot->lastState; crv = CKR_OK; } else { crv = PK11_GETTAB(slot)->C_GetSessionInfo(slot->session, &sessionInfo); if (crv == CKR_OK) { slot->lastState = sessionInfo.state; slot->lastLoginCheck = curTime; } } PK11_ExitSlotMonitor(slot); /* if we can't get session info, something is really wrong */ if (crv != CKR_OK) { slot->session = CK_INVALID_HANDLE; return PR_FALSE; } switch (sessionInfo.state) { case CKS_RW_PUBLIC_SESSION: case CKS_RO_PUBLIC_SESSION: default: break; /* fail */ case CKS_RW_USER_FUNCTIONS: case CKS_RW_SO_FUNCTIONS: case CKS_RO_USER_FUNCTIONS: return PR_TRUE; } return PR_FALSE; }