/* 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/. */ #include "PKCS11Token.h" #include "ScopedNSSTypes.h" #include "mozilla/Casting.h" #include "mozilla/ErrorResult.h" #include "mozilla/Logging.h" #include "mozilla/dom/Promise.h" #include "nsISupports.h" #include "nsNSSCertHelper.h" #include "nsNSSComponent.h" #include "nsPromiseFlatString.h" #include "nsReadableUtils.h" #include "nsServiceManagerUtils.h" #include "prerror.h" #include "secerr.h" using namespace mozilla; using mozilla::ErrorResult; using mozilla::dom::Promise; extern mozilla::LazyLogModule gPIPNSSLog; NS_IMPL_ISUPPORTS(PKCS11Token, nsIPKCS11Token) nsresult PKCS11Token::Init() { static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); nsCOMPtr nss(do_GetService(kNSSComponentCID)); if (!nss) { return NS_ERROR_FAILURE; } mSlot.reset(PK11_GetInternalKeySlot()); mIsInternalCryptoToken = false; mIsInternalKeyToken = true; mSeries = PK11_GetSlotSeries(mSlot.get()); return refreshTokenInfo(); } PKCS11Token::PKCS11Token(PK11SlotInfo* slot) { MOZ_ASSERT(slot); mSlot.reset(PK11_ReferenceSlot(slot)); mIsInternalCryptoToken = PK11_IsInternal(mSlot.get()) && !PK11_IsInternalKeySlot(mSlot.get()); mIsInternalKeyToken = PK11_IsInternalKeySlot(mSlot.get()); mSeries = PK11_GetSlotSeries(slot); (void)refreshTokenInfo(); } nsresult PKCS11Token::refreshTokenInfo() { if (mIsInternalCryptoToken) { nsresult rv; if (PK11_IsFIPS()) { rv = GetPIPNSSBundleString("Fips140TokenDescription", mTokenName); } else { rv = GetPIPNSSBundleString("TokenDescription", mTokenName); } if (NS_FAILED(rv)) { return rv; } } else if (mIsInternalKeyToken) { nsresult rv = GetPIPNSSBundleString("PrivateTokenDescription", mTokenName); if (NS_FAILED(rv)) { return rv; } } else { mTokenName.Assign(PK11_GetTokenName(mSlot.get())); } CK_TOKEN_INFO tokInfo; nsresult rv = mozilla::MapSECStatus(PK11_GetTokenInfo(mSlot.get(), &tokInfo)); if (NS_FAILED(rv)) { return rv; } // Set the Manufacturer field if (mIsInternalCryptoToken || mIsInternalKeyToken) { rv = GetPIPNSSBundleString("ManufacturerID", mTokenManufacturerID); if (NS_FAILED(rv)) { return rv; } } else { const char* ccManID = mozilla::BitwiseCast(tokInfo.manufacturerID); mTokenManufacturerID.Assign( ccManID, strnlen(ccManID, sizeof(tokInfo.manufacturerID))); mTokenManufacturerID.Trim(" ", false, true); } // Set the Hardware Version field mTokenHWVersion.Truncate(); mTokenHWVersion.AppendInt(tokInfo.hardwareVersion.major); mTokenHWVersion.Append('.'); mTokenHWVersion.AppendInt(tokInfo.hardwareVersion.minor); // Set the Firmware Version field mTokenFWVersion.Truncate(); mTokenFWVersion.AppendInt(tokInfo.firmwareVersion.major); mTokenFWVersion.Append('.'); mTokenFWVersion.AppendInt(tokInfo.firmwareVersion.minor); // Set the Serial Number field const char* ccSerial = mozilla::BitwiseCast(tokInfo.serialNumber); mTokenSerialNum.Assign(ccSerial, strnlen(ccSerial, sizeof(tokInfo.serialNumber))); mTokenSerialNum.Trim(" ", false, true); return NS_OK; } nsresult PKCS11Token::GetAttributeHelper(const nsACString& attribute, /*out*/ nsACString& xpcomOutParam) { // Handle removals/insertions. if (PK11_GetSlotSeries(mSlot.get()) != mSeries) { nsresult rv = refreshTokenInfo(); if (NS_FAILED(rv)) { return rv; } } xpcomOutParam = attribute; return NS_OK; } NS_IMETHODIMP PKCS11Token::GetTokenName(/*out*/ nsACString& tokenName) { return GetAttributeHelper(mTokenName, tokenName); } NS_IMETHODIMP PKCS11Token::GetIsInternalKeyToken(/*out*/ bool* _retval) { NS_ENSURE_ARG_POINTER(_retval); *_retval = mIsInternalKeyToken; return NS_OK; } NS_IMETHODIMP PKCS11Token::GetTokenManID(/*out*/ nsACString& tokenManufacturerID) { return GetAttributeHelper(mTokenManufacturerID, tokenManufacturerID); } NS_IMETHODIMP PKCS11Token::GetTokenHWVersion(/*out*/ nsACString& tokenHWVersion) { return GetAttributeHelper(mTokenHWVersion, tokenHWVersion); } NS_IMETHODIMP PKCS11Token::GetTokenFWVersion(/*out*/ nsACString& tokenFWVersion) { return GetAttributeHelper(mTokenFWVersion, tokenFWVersion); } NS_IMETHODIMP PKCS11Token::GetTokenSerialNumber(/*out*/ nsACString& tokenSerialNum) { return GetAttributeHelper(mTokenSerialNum, tokenSerialNum); } NS_IMETHODIMP PKCS11Token::GetIsLoggedIn(bool* isLoggedIn) { *isLoggedIn = PK11_IsLoggedIn(mSlot.get(), nullptr); return NS_OK; } NS_IMETHODIMP PKCS11Token::Login(JSContext* aCx, Promise** aPromise) { MOZ_ASSERT(NS_IsMainThread()); if (!NS_IsMainThread()) { return NS_ERROR_NOT_SAME_THREAD; } ErrorResult result; RefPtr promise = Promise::Create(xpc::CurrentNativeGlobal(aCx), result); if (result.Failed()) { return result.StealNSResult(); } auto promiseHolder = MakeRefPtr>("Login promise", promise); nsCOMPtr runnable(NS_NewRunnableFunction( "Login runnable", [promiseHolder, slot = UniquePK11SlotInfo(PK11_ReferenceSlot(mSlot.get()))]() { nsresult rv = mozilla::MapSECStatus(PK11_Authenticate(slot.get(), true, nullptr)); NS_DispatchToMainThread( NS_NewRunnableFunction("Login callback", [rv, promiseHolder] { if (NS_SUCCEEDED(rv)) { promiseHolder->get()->MaybeResolveWithUndefined(); } else { promiseHolder->get()->MaybeReject(rv); } })); })); promise.forget(aPromise); return NS_DispatchBackgroundTask(runnable.forget()); } NS_IMETHODIMP PKCS11Token::Logout(JSContext* aCx, Promise** aPromise) { MOZ_ASSERT(NS_IsMainThread()); if (!NS_IsMainThread()) { return NS_ERROR_NOT_SAME_THREAD; } ErrorResult result; RefPtr promise = Promise::Create(xpc::CurrentNativeGlobal(aCx), result); if (result.Failed()) { return result.StealNSResult(); } auto promiseHolder = MakeRefPtr>("Logout promise", promise); nsCOMPtr runnable(NS_NewRunnableFunction( "Logout runnable", [promiseHolder, slot = UniquePK11SlotInfo(PK11_ReferenceSlot(mSlot.get()))]() { // PK11_Logout() can fail if the user wasn't logged in beforehand. We // want this method to succeed even in this case, so we ignore the // return value. (void)PK11_Logout(slot.get()); NS_DispatchToMainThread( NS_NewRunnableFunction("Logout callback", [promiseHolder] { promiseHolder->get()->MaybeResolveWithUndefined(); })); })); promise.forget(aPromise); return NS_DispatchBackgroundTask(runnable.forget()); } nsresult DoReset(const UniquePK11SlotInfo& slot, bool isInternalKeyToken) { SECStatus rv = PK11_ResetToken(slot.get(), nullptr); if (rv != SECSuccess) { return mozilla::MapSECStatus(rv); } // If this is the internal key token, set an empty password to enable the SQL // DB to work properly. if (isInternalKeyToken) { rv = PK11_InitPin(slot.get(), nullptr, nullptr); if (rv != SECSuccess) { return mozilla::MapSECStatus(rv); } } return NS_OK; } NS_IMETHODIMP PKCS11Token::Reset(JSContext* aCx, Promise** aPromise) { MOZ_ASSERT(NS_IsMainThread()); if (!NS_IsMainThread()) { return NS_ERROR_NOT_SAME_THREAD; } ErrorResult result; RefPtr promise = Promise::Create(xpc::CurrentNativeGlobal(aCx), result); if (result.Failed()) { return result.StealNSResult(); } auto promiseHolder = MakeRefPtr>("Reset promise", promise); nsCOMPtr runnable(NS_NewRunnableFunction( "Reset runnable", [promiseHolder, slot = UniquePK11SlotInfo(PK11_ReferenceSlot(mSlot.get())), isInternalKeyToken(mIsInternalKeyToken)]() { nsresult rv = DoReset(slot, isInternalKeyToken); NS_DispatchToMainThread( NS_NewRunnableFunction("Reset callback", [rv, promiseHolder] { if (NS_SUCCEEDED(rv)) { promiseHolder->get()->MaybeResolveWithUndefined(); } else { promiseHolder->get()->MaybeReject(rv); } })); })); promise.forget(aPromise); return NS_DispatchBackgroundTask(runnable.forget()); } nsresult DoChangePassword(const UniquePK11SlotInfo& slot, const nsACString& oldPassword, const nsACString& newPassword) { if (oldPassword.IsEmpty() && PK11_NeedUserInit(slot.get())) { return mozilla::MapSECStatus( PK11_InitPin(slot.get(), "", PromiseFlatCString(newPassword).get())); } SECStatus rv = PK11_CheckUserPassword(slot.get(), PromiseFlatCString(oldPassword).get()); if (rv != SECSuccess) { return mozilla::MapSECStatus(rv); } return mozilla::MapSECStatus( PK11_ChangePW(slot.get(), PromiseFlatCString(oldPassword).get(), PromiseFlatCString(newPassword).get())); } NS_IMETHODIMP PKCS11Token::ChangePassword(const nsACString& oldPassword, const nsACString& newPassword, JSContext* aCx, Promise** aPromise) { MOZ_ASSERT(NS_IsMainThread()); if (!NS_IsMainThread()) { return NS_ERROR_NOT_SAME_THREAD; } ErrorResult result; RefPtr promise = Promise::Create(xpc::CurrentNativeGlobal(aCx), result); if (result.Failed()) { return result.StealNSResult(); } auto promiseHolder = MakeRefPtr>( "ChangePassword promise", promise); nsCOMPtr runnable(NS_NewRunnableFunction( "ChangePassword runnable", [promiseHolder, oldPassword = nsCString(oldPassword), newPassword = nsCString(newPassword), slot = UniquePK11SlotInfo(PK11_ReferenceSlot(mSlot.get()))]() { nsresult rv = DoChangePassword(slot, oldPassword, newPassword); NS_DispatchToMainThread(NS_NewRunnableFunction( "ChangePassword callback", [rv, promiseHolder] { if (NS_SUCCEEDED(rv)) { promiseHolder->get()->MaybeResolveWithUndefined(); } else { promiseHolder->get()->MaybeReject(rv); } })); })); promise.forget(aPromise); return NS_DispatchBackgroundTask(runnable.forget()); } // Two PKCS#11 flags are relevant here: CKF_LOGIN_REQUIRED and // CKF_USER_PIN_INITIALIZED. // CKF_LOGIN_REQUIRED is set if there are some cryptographic operations on the // token that require logging in to perform. If this flag is not set, no // password is required. // CKF_USER_PIN_INITIALIZED is set if the token's user pin (password) has been // set. // CKF_LOGIN_REQUIRED is obtained by calling PK11_NeedLogin. // CKF_USER_PIN_INITIALIZED is obtained by negating the result of calling // PK11_NeedUserInit. NS_IMETHODIMP PKCS11Token::GetCanHavePassword(bool* canHavePassword) { // A token is considered able to have a password if CKF_LOGIN_REQUIRED is set // or if CKF_USER_PIN_INITIALIZED is set. *canHavePassword = PK11_NeedLogin(mSlot.get()) || !PK11_NeedUserInit(mSlot.get()); ; return NS_OK; } NS_IMETHODIMP PKCS11Token::GetHasPassword(bool* hasPassword) { // A token has a password if CKF_LOGIN_REQUIRED is set and // CKF_USER_PIN_INITIALIZED is set. *hasPassword = PK11_NeedLogin(mSlot.get()) && !PK11_NeedUserInit(mSlot.get()); return NS_OK; }