diff -r 000000000000 -r 6474c204b198 security/manager/ssl/src/nsCrypto.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/security/manager/ssl/src/nsCrypto.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,2939 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "nsCrypto.h" +#include "nsNSSComponent.h" +#include "secmod.h" + +#include "nsReadableUtils.h" +#include "nsCRT.h" +#include "nsXPIDLString.h" +#include "nsISaveAsCharset.h" +#include "nsNativeCharsetUtils.h" +#include "nsServiceManagerUtils.h" + +#ifndef MOZ_DISABLE_CRYPTOLEGACY +#include "nsKeygenHandler.h" +#include "nsKeygenThread.h" +#include "nsNSSCertificate.h" +#include "nsNSSCertificateDB.h" +#include "nsPKCS12Blob.h" +#include "nsPK11TokenDB.h" +#include "nsThreadUtils.h" +#include "nsIServiceManager.h" +#include "nsIMemory.h" +#include "nsAlgorithm.h" +#include "prprf.h" +#include "nsDOMCID.h" +#include "nsIDOMWindow.h" +#include "nsIDOMClassInfo.h" +#include "nsIDOMDocument.h" +#include "nsIDocument.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptContext.h" +#include "nsIScriptGlobalObject.h" +#include "nsContentUtils.h" +#include "nsCxPusher.h" +#include "nsDOMJSUtils.h" +#include "nsJSUtils.h" +#include "nsIXPConnect.h" +#include "nsIRunnable.h" +#include "nsIWindowWatcher.h" +#include "nsIPrompt.h" +#include "nsIFilePicker.h" +#include "nsJSPrincipals.h" +#include "nsJSUtils.h" +#include "nsIPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsIGenKeypairInfoDlg.h" +#include "nsIDOMCryptoDialogs.h" +#include "nsIFormSigningDialog.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIURI.h" +#include "jsapi.h" +#include "js/OldDebugAPI.h" +#include +#include "pk11func.h" +#include "keyhi.h" +#include "cryptohi.h" +#include "seccomon.h" +#include "secerr.h" +#include "sechash.h" +#include "crmf.h" +#include "pk11pqg.h" +#include "cmmf.h" +#include "nssb64.h" +#include "base64.h" +#include "cert.h" +#include "certdb.h" +#include "secmod.h" +#include "ScopedNSSTypes.h" +#include "pkix/pkixtypes.h" + +#include "ssl.h" // For SSL_ClearSessionCache + +#include "nsNSSCleaner.h" + +#include "nsNSSCertHelper.h" +#include +#include "nsWrapperCacheInlines.h" +#endif +#ifndef MOZ_DISABLE_CRYPTOLEGACY +#include "mozilla/dom/CRMFObjectBinding.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; + +/* + * These are the most common error strings that are returned + * by the JavaScript methods in case of error. + */ + +#define JS_ERROR "error:" +#define JS_ERROR_INTERNAL JS_ERROR"internalError" + +#undef REPORT_INCORRECT_NUM_ARGS + +#define JS_OK_ADD_MOD 3 +#define JS_OK_DEL_EXTERNAL_MOD 2 +#define JS_OK_DEL_INTERNAL_MOD 1 + +#define JS_ERR_INTERNAL -1 +#define JS_ERR_USER_CANCEL_ACTION -2 +#define JS_ERR_INCORRECT_NUM_OF_ARGUMENTS -3 +#define JS_ERR_DEL_MOD -4 +#define JS_ERR_ADD_MOD -5 +#define JS_ERR_BAD_MODULE_NAME -6 +#define JS_ERR_BAD_DLL_NAME -7 +#define JS_ERR_BAD_MECHANISM_FLAGS -8 +#define JS_ERR_BAD_CIPHER_ENABLE_FLAGS -9 +#define JS_ERR_ADD_DUPLICATE_MOD -10 + +#ifndef MOZ_DISABLE_CRYPTOLEGACY + +NSSCleanupAutoPtrClass_WithParam(PK11Context, PK11_DestroyContext, TrueParam, true) + +/* + * This structure is used to store information for one key generation. + * The nsCrypto::GenerateCRMFRequest method parses the inputs and then + * stores one of these structures for every key generation that happens. + * The information stored in this structure is then used to set some + * values in the CRMF request. + */ +typedef enum { + rsaEnc, rsaDualUse, rsaSign, rsaNonrepudiation, rsaSignNonrepudiation, + ecEnc, ecDualUse, ecSign, ecNonrepudiation, ecSignNonrepudiation, + dhEx, dsaSignNonrepudiation, dsaSign, dsaNonrepudiation, invalidKeyGen +} nsKeyGenType; + +bool isECKeyGenType(nsKeyGenType kgt) +{ + switch (kgt) + { + case ecEnc: + case ecDualUse: + case ecSign: + case ecNonrepudiation: + case ecSignNonrepudiation: + return true; + + default: + break; + } + + return false; +} + +typedef struct nsKeyPairInfoStr { + SECKEYPublicKey *pubKey; /* The putlic key associated with gen'd + priv key. */ + SECKEYPrivateKey *privKey; /* The private key we generated */ + nsKeyGenType keyGenType; /* What type of key gen are we doing.*/ + + CERTCertificate *ecPopCert; + /* null: use signing for pop + other than null: a cert that defines EC keygen params + and will be used for dhMac PoP. */ + + SECKEYPublicKey *ecPopPubKey; + /* extracted public key from ecPopCert */ +} nsKeyPairInfo; + + +//This class is just used to pass arguments +//to the nsCryptoRunnable event. +class nsCryptoRunArgs : public nsISupports { +public: + nsCryptoRunArgs(JSContext *aCx); + virtual ~nsCryptoRunArgs(); + nsCOMPtr m_kungFuDeathGrip; + JSContext *m_cx; + JS::PersistentRooted m_scope; + nsCOMPtr m_principals; + nsXPIDLCString m_jsCallback; + NS_DECL_ISUPPORTS +}; + +//This class is used to run the callback code +//passed to crypto.generateCRMFRequest +//We have to do that for backwards compatibility +//reasons w/ PSM 1.x and Communciator 4.x +class nsCryptoRunnable : public nsIRunnable { +public: + nsCryptoRunnable(nsCryptoRunArgs *args); + virtual ~nsCryptoRunnable(); + + NS_IMETHOD Run (); + NS_DECL_ISUPPORTS +private: + nsCryptoRunArgs *m_args; +}; + + +//We're going to inherit the memory passed +//into us. +//This class backs up an array of certificates +//as an event. +class nsP12Runnable : public nsIRunnable { +public: + nsP12Runnable(nsIX509Cert **certArr, int32_t numCerts, nsIPK11Token *token); + virtual ~nsP12Runnable(); + + NS_IMETHOD Run(); + NS_DECL_ISUPPORTS +private: + nsCOMPtr mToken; + nsIX509Cert **mCertArr; + int32_t mNumCerts; +}; + +// QueryInterface implementation for nsCrypto +NS_INTERFACE_MAP_BEGIN(nsCrypto) + NS_INTERFACE_MAP_ENTRY(nsIDOMCrypto) +NS_INTERFACE_MAP_END_INHERITING(mozilla::dom::Crypto) + +NS_IMPL_ADDREF_INHERITED(nsCrypto, mozilla::dom::Crypto) +NS_IMPL_RELEASE_INHERITED(nsCrypto, mozilla::dom::Crypto) + +// QueryInterface implementation for nsPkcs11 +#endif // MOZ_DISABLE_CRYPTOLEGACY + +NS_INTERFACE_MAP_BEGIN(nsPkcs11) + NS_INTERFACE_MAP_ENTRY(nsIPKCS11) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsPkcs11) +NS_IMPL_RELEASE(nsPkcs11) + +#ifndef MOZ_DISABLE_CRYPTOLEGACY + +// ISupports implementation for nsCryptoRunnable +NS_IMPL_ISUPPORTS(nsCryptoRunnable, nsIRunnable) + +// ISupports implementation for nsP12Runnable +NS_IMPL_ISUPPORTS(nsP12Runnable, nsIRunnable) + +// ISupports implementation for nsCryptoRunArgs +NS_IMPL_ISUPPORTS0(nsCryptoRunArgs) + +nsCrypto::nsCrypto() : + mEnableSmartCardEvents(false) +{ +} + +nsCrypto::~nsCrypto() +{ +} + +void +nsCrypto::Init(nsIDOMWindow* aWindow) +{ + mozilla::dom::Crypto::Init(aWindow); +} + +void +nsCrypto::SetEnableSmartCardEvents(bool aEnable, ErrorResult& aRv) +{ + NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); + + nsresult rv = NS_OK; + + // this has the side effect of starting the nssComponent (and initializing + // NSS) even if it isn't already going. Starting the nssComponent is a + // prerequisite for getting smartCard events. + if (aEnable) { + nsCOMPtr nssComponent(do_GetService(kNSSComponentCID, &rv)); + } + + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return; + } + + mEnableSmartCardEvents = aEnable; +} + +NS_IMETHODIMP +nsCrypto::SetEnableSmartCardEvents(bool aEnable) +{ + ErrorResult rv; + SetEnableSmartCardEvents(aEnable, rv); + return rv.ErrorCode(); +} + +bool +nsCrypto::EnableSmartCardEvents() +{ + return mEnableSmartCardEvents; +} + +NS_IMETHODIMP +nsCrypto::GetEnableSmartCardEvents(bool *aEnable) +{ + *aEnable = EnableSmartCardEvents(); + return NS_OK; +} + +//A quick function to let us know if the key we're trying to generate +//can be escrowed. +static bool +ns_can_escrow(nsKeyGenType keyGenType) +{ + /* For now, we only escrow rsa-encryption and ec-encryption keys. */ + return (bool)(keyGenType == rsaEnc || keyGenType == ecEnc); +} + +//Retrieve crypto.version so that callers know what +//version of PSM this is. +void +nsCrypto::GetVersion(nsString& aVersion) +{ + aVersion.Assign(NS_LITERAL_STRING(PSM_VERSION_STRING)); +} + +/* + * Given an nsKeyGenType, return the PKCS11 mechanism that will + * perform the correct key generation. + */ +static uint32_t +cryptojs_convert_to_mechanism(nsKeyGenType keyGenType) +{ + uint32_t retMech; + + switch (keyGenType) { + case rsaEnc: + case rsaDualUse: + case rsaSign: + case rsaNonrepudiation: + case rsaSignNonrepudiation: + retMech = CKM_RSA_PKCS_KEY_PAIR_GEN; + break; + case ecEnc: + case ecDualUse: + case ecSign: + case ecNonrepudiation: + case ecSignNonrepudiation: + retMech = CKM_EC_KEY_PAIR_GEN; + break; + case dhEx: + retMech = CKM_DH_PKCS_KEY_PAIR_GEN; + break; + case dsaSign: + case dsaSignNonrepudiation: + case dsaNonrepudiation: + retMech = CKM_DSA_KEY_PAIR_GEN; + break; + default: + retMech = CKM_INVALID_MECHANISM; + } + return retMech; +} + +/* + * This function takes a string read through JavaScript parameters + * and translates it to the internal enumeration representing the + * key gen type. Leading and trailing whitespace must be already removed. + */ +static nsKeyGenType +cryptojs_interpret_key_gen_type(const nsAString& keyAlg) +{ + if (keyAlg.EqualsLiteral("rsa-ex")) { + return rsaEnc; + } + if (keyAlg.EqualsLiteral("rsa-dual-use")) { + return rsaDualUse; + } + if (keyAlg.EqualsLiteral("rsa-sign")) { + return rsaSign; + } + if (keyAlg.EqualsLiteral("rsa-sign-nonrepudiation")) { + return rsaSignNonrepudiation; + } + if (keyAlg.EqualsLiteral("rsa-nonrepudiation")) { + return rsaNonrepudiation; + } + if (keyAlg.EqualsLiteral("ec-ex")) { + return ecEnc; + } + if (keyAlg.EqualsLiteral("ec-dual-use")) { + return ecDualUse; + } + if (keyAlg.EqualsLiteral("ec-sign")) { + return ecSign; + } + if (keyAlg.EqualsLiteral("ec-sign-nonrepudiation")) { + return ecSignNonrepudiation; + } + if (keyAlg.EqualsLiteral("ec-nonrepudiation")) { + return ecNonrepudiation; + } + if (keyAlg.EqualsLiteral("dsa-sign-nonrepudiation")) { + return dsaSignNonrepudiation; + } + if (keyAlg.EqualsLiteral("dsa-sign")) { + return dsaSign; + } + if (keyAlg.EqualsLiteral("dsa-nonrepudiation")) { + return dsaNonrepudiation; + } + if (keyAlg.EqualsLiteral("dh-ex")) { + return dhEx; + } + return invalidKeyGen; +} + +/* + * input: null terminated char* pointing to (the remainder of) an + * EC key param string. + * + * bool return value, false means "no more name=value pair found", + * true means "found, see out params" + * + * out param name: char * pointing to name (not zero terminated) + * out param name_len: length of found name + * out param value: char * pointing to value (not zero terminated) + * out param value_len: length of found value + * out param next_pair: to be used for a follow up call to this function + */ + +bool getNextNameValueFromECKeygenParamString(char *input, + char *&name, + int &name_len, + char *&value, + int &value_len, + char *&next_call) +{ + if (!input || !*input) + return false; + + // we allow leading ; and leading space in front of each name value pair + + while (*input && *input == ';') + ++input; + + while (*input && *input == ' ') + ++input; + + name = input; + + while (*input && *input != '=') + ++input; + + if (*input != '=') + return false; + + name_len = input - name; + ++input; + + value = input; + + while (*input && *input != ';') + ++input; + + value_len = input - value; + next_call = input; + + return true; +} + +//Take the string passed into us via crypto.generateCRMFRequest +//as the keygen type parameter and convert it to parameters +//we can actually pass to the PKCS#11 layer. +static void* +nsConvertToActualKeyGenParams(uint32_t keyGenMech, char *params, + uint32_t paramLen, int32_t keySize, + nsKeyPairInfo *keyPairInfo) +{ + void *returnParams = nullptr; + + + switch (keyGenMech) { + case CKM_RSA_PKCS_KEY_PAIR_GEN: + { + // For RSA, we don't support passing in key generation arguments from + // the JS code just yet. + if (params) + return nullptr; + + PK11RSAGenParams *rsaParams; + rsaParams = static_cast + (nsMemory::Alloc(sizeof(PK11RSAGenParams))); + + if (!rsaParams) { + return nullptr; + } + /* I'm just taking the same parameters used in + * certdlgs.c:GenKey + */ + if (keySize > 0) { + rsaParams->keySizeInBits = keySize; + } else { + rsaParams->keySizeInBits = 1024; + } + rsaParams->pe = DEFAULT_RSA_KEYGEN_PE; + returnParams = rsaParams; + break; + } + case CKM_EC_KEY_PAIR_GEN: + { + /* + * keygen params for generating EC keys must be composed of name=value pairs, + * multiple pairs allowed, separated using semicolon ; + * + * Either param "curve" or param "popcert" must be specified. + * curve=name-of-curve + * popcert=base64-encoded-cert + * + * When both params are specified, popcert will be used. + * If no popcert param is given, or if popcert can not be decoded, + * we will fall back to the curve param. + * + * Additional name=value pairs may be defined in the future. + * + * If param popcert is present and valid, the given certificate will be used + * to determine the key generation params. In addition the certificate + * will be used to produce a dhMac based Proof of Posession, + * using the cert's public key, subject and issuer names, + * as specified in RFC 2511 section 4.3 paragraph 2 and Appendix A. + * + * If neither param popcert nor param curve could be used, + * tse a curve based on the keysize param. + * NOTE: Here keysize is used only as an indication of + * High/Medium/Low strength; elliptic curve + * cryptography uses smaller keys than RSA to provide + * equivalent security. + */ + + char *curve = nullptr; + + { + // extract components of name=value list + + char *next_input = params; + char *name = nullptr; + char *value = nullptr; + int name_len = 0; + int value_len = 0; + + while (getNextNameValueFromECKeygenParamString( + next_input, name, name_len, value, value_len, + next_input)) + { + // use only the first specified curve + if (!curve && PL_strncmp(name, "curve", std::min(name_len, 5)) == 0) + { + curve = PL_strndup(value, value_len); + } + // use only the first specified popcert + else if (!keyPairInfo->ecPopCert && + PL_strncmp(name, "popcert", std::min(name_len, 7)) == 0) + { + char *certstr = PL_strndup(value, value_len); + if (certstr) { + keyPairInfo->ecPopCert = CERT_ConvertAndDecodeCertificate(certstr); + PL_strfree(certstr); + + if (keyPairInfo->ecPopCert) + { + keyPairInfo->ecPopPubKey = CERT_ExtractPublicKey(keyPairInfo->ecPopCert); + } + } + } + } + } + + // first try to use the params of the provided CA cert + if (keyPairInfo->ecPopPubKey && keyPairInfo->ecPopPubKey->keyType == ecKey) + { + returnParams = SECITEM_DupItem(&keyPairInfo->ecPopPubKey->u.ec.DEREncodedParams); + } + + // if we did not yet find good params, do we have a curve name? + if (!returnParams && curve) + { + returnParams = decode_ec_params(curve); + } + + // if we did not yet find good params, do something based on keysize + if (!returnParams) + { + switch (keySize) { + case 512: + case 1024: + returnParams = decode_ec_params("secp256r1"); + break; + case 2048: + default: + returnParams = decode_ec_params("secp384r1"); + break; + } + } + + if (curve) + PL_strfree(curve); + + break; + } + case CKM_DSA_KEY_PAIR_GEN: + { + // For DSA, we don't support passing in key generation arguments from + // the JS code just yet. + if (params) + return nullptr; + + PQGParams *pqgParams = nullptr; + PQGVerify *vfy = nullptr; + SECStatus rv; + int index; + + index = PQG_PBITS_TO_INDEX(keySize); + if (index == -1) { + returnParams = nullptr; + break; + } + rv = PK11_PQG_ParamGen(0, &pqgParams, &vfy); + if (vfy) { + PK11_PQG_DestroyVerify(vfy); + } + if (rv != SECSuccess) { + if (pqgParams) { + PK11_PQG_DestroyParams(pqgParams); + } + return nullptr; + } + returnParams = pqgParams; + break; + } + default: + returnParams = nullptr; + } + return returnParams; +} + +//We need to choose which PKCS11 slot we're going to generate +//the key on. Calls the default implementation provided by +//nsKeygenHandler.cpp +static PK11SlotInfo* +nsGetSlotForKeyGen(nsKeyGenType keyGenType, nsIInterfaceRequestor *ctx) +{ + nsNSSShutDownPreventionLock locker; + uint32_t mechanism = cryptojs_convert_to_mechanism(keyGenType); + PK11SlotInfo *slot = nullptr; + nsresult rv = GetSlotWithMechanism(mechanism,ctx, &slot); + if (NS_FAILED(rv)) { + if (slot) + PK11_FreeSlot(slot); + slot = nullptr; + } + return slot; +} + +//Free the parameters that were passed into PK11_GenerateKeyPair +//depending on the mechanism type used. +static void +nsFreeKeyGenParams(CK_MECHANISM_TYPE keyGenMechanism, void *params) +{ + switch (keyGenMechanism) { + case CKM_RSA_PKCS_KEY_PAIR_GEN: + nsMemory::Free(params); + break; + case CKM_EC_KEY_PAIR_GEN: + SECITEM_FreeItem(reinterpret_cast(params), true); + break; + case CKM_DSA_KEY_PAIR_GEN: + PK11_PQG_DestroyParams(static_cast(params)); + break; + } +} + +//Function that is used to generate a single key pair. +//Once all the arguments have been parsed and processed, this +//function gets called and takes care of actually generating +//the key pair passing the appopriate parameters to the NSS +//functions. +static nsresult +cryptojs_generateOneKeyPair(JSContext *cx, nsKeyPairInfo *keyPairInfo, + int32_t keySize, char *params, + nsIInterfaceRequestor *uiCxt, + PK11SlotInfo *slot, bool willEscrow) + +{ + const PK11AttrFlags sensitiveFlags = (PK11_ATTR_SENSITIVE | PK11_ATTR_PRIVATE); + const PK11AttrFlags temporarySessionFlags = PK11_ATTR_SESSION; + const PK11AttrFlags permanentTokenFlags = PK11_ATTR_TOKEN; + const PK11AttrFlags extractableFlags = PK11_ATTR_EXTRACTABLE; + + nsIGeneratingKeypairInfoDialogs * dialogs; + nsKeygenThread *KeygenRunnable = 0; + nsCOMPtr runnable; + + uint32_t mechanism = cryptojs_convert_to_mechanism(keyPairInfo->keyGenType); + void *keyGenParams = nsConvertToActualKeyGenParams(mechanism, params, + (params) ? strlen(params):0, + keySize, keyPairInfo); + + if (!keyGenParams || !slot) { + return NS_ERROR_INVALID_ARG; + } + + // Make sure the token has password already set on it before trying + // to generate the key. + + nsresult rv = setPassword(slot, uiCxt); + if (NS_FAILED(rv)) + return rv; + + if (PK11_Authenticate(slot, true, uiCxt) != SECSuccess) + return NS_ERROR_FAILURE; + + // Smart cards will not let you extract a private key once + // it is on the smart card. If we've been told to escrow + // a private key that will be stored on a smart card, + // then we'll use the following strategy to ensure we can escrow it. + // We'll attempt to generate the key on our internal token, + // because this is expected to avoid some problems. + // If it works, we'll escrow, move the key to the smartcard, done. + // If it didn't work (or the internal key doesn't support the desired + // mechanism), then we'll attempt to generate the key on + // the destination token, with the EXTRACTABLE flag set. + // If it works, we'll extract, escrow, done. + // If it failed, then we're unable to escrow and return failure. + // NOTE: We call PK11_GetInternalSlot instead of PK11_GetInternalKeySlot + // so that the key has zero chance of being store in the + // user's key3.db file. Which the slot returned by + // PK11_GetInternalKeySlot has access to and PK11_GetInternalSlot + // does not. + ScopedPK11SlotInfo intSlot; + + if (willEscrow && !PK11_IsInternal(slot)) { + intSlot = PK11_GetInternalSlot(); + NS_ASSERTION(intSlot,"Couldn't get the internal slot"); + + if (!PK11_DoesMechanism(intSlot, mechanism)) { + // Set to null, and the subsequent code will not attempt to use it. + intSlot = nullptr; + } + } + + rv = getNSSDialogs((void**)&dialogs, + NS_GET_IID(nsIGeneratingKeypairInfoDialogs), + NS_GENERATINGKEYPAIRINFODIALOGS_CONTRACTID); + + if (NS_SUCCEEDED(rv)) { + KeygenRunnable = new nsKeygenThread(); + if (KeygenRunnable) { + NS_ADDREF(KeygenRunnable); + } + } + + // "firstAttemptSlot" and "secondAttemptSlot" are alternative names + // for better code readability, we don't increase the reference counts. + + PK11SlotInfo *firstAttemptSlot = nullptr; + PK11AttrFlags firstAttemptFlags = 0; + + PK11SlotInfo *secondAttemptSlot = slot; + PK11AttrFlags secondAttemptFlags = sensitiveFlags | permanentTokenFlags; + + if (willEscrow) { + secondAttemptFlags |= extractableFlags; + } + + if (!intSlot || PK11_IsInternal(slot)) { + // if we cannot use the internal slot, then there is only one attempt + // if the destination slot is the internal slot, then there is only one attempt + firstAttemptSlot = secondAttemptSlot; + firstAttemptFlags = secondAttemptFlags; + secondAttemptSlot = nullptr; + secondAttemptFlags = 0; + } + else { + firstAttemptSlot = intSlot; + firstAttemptFlags = sensitiveFlags | temporarySessionFlags; + + // We always need the extractable flag on the first attempt, + // because we want to move the key to another slot - ### is this correct? + firstAttemptFlags |= extractableFlags; + } + + bool mustMoveKey = false; + + if (NS_FAILED(rv) || !KeygenRunnable) { + /* execute key generation on this thread */ + rv = NS_OK; + + keyPairInfo->privKey = + PK11_GenerateKeyPairWithFlags(firstAttemptSlot, mechanism, + keyGenParams, &keyPairInfo->pubKey, + firstAttemptFlags, uiCxt); + + if (keyPairInfo->privKey) { + // success on first attempt + if (secondAttemptSlot) { + mustMoveKey = true; + } + } + else { + keyPairInfo->privKey = + PK11_GenerateKeyPairWithFlags(secondAttemptSlot, mechanism, + keyGenParams, &keyPairInfo->pubKey, + secondAttemptFlags, uiCxt); + } + + } else { + /* execute key generation on separate thread */ + KeygenRunnable->SetParams( firstAttemptSlot, firstAttemptFlags, + secondAttemptSlot, secondAttemptFlags, + mechanism, keyGenParams, uiCxt ); + + runnable = do_QueryInterface(KeygenRunnable); + + if (runnable) { + { + nsPSMUITracker tracker; + if (tracker.isUIForbidden()) { + rv = NS_ERROR_NOT_AVAILABLE; + } + else { + rv = dialogs->DisplayGeneratingKeypairInfo(uiCxt, runnable); + // We call join on the thread, + // so we can be sure that no simultaneous access to the passed parameters will happen. + KeygenRunnable->Join(); + } + } + + NS_RELEASE(dialogs); + if (NS_SUCCEEDED(rv)) { + PK11SlotInfo *used_slot = nullptr; + rv = KeygenRunnable->ConsumeResult(&used_slot, + &keyPairInfo->privKey, &keyPairInfo->pubKey); + + if (NS_SUCCEEDED(rv)) { + if ((used_slot == firstAttemptSlot) && secondAttemptSlot) { + mustMoveKey = true; + } + + if (used_slot) { + PK11_FreeSlot(used_slot); + } + } + } + } + } + + firstAttemptSlot = nullptr; + secondAttemptSlot = nullptr; + + nsFreeKeyGenParams(mechanism, keyGenParams); + + if (KeygenRunnable) { + NS_RELEASE(KeygenRunnable); + } + + if (!keyPairInfo->privKey || !keyPairInfo->pubKey) { + return NS_ERROR_FAILURE; + } + + //If we generated the key pair on the internal slot because the + // keys were going to be escrowed, move the keys over right now. + if (mustMoveKey) { + ScopedSECKEYPrivateKey newPrivKey(PK11_LoadPrivKey(slot, + keyPairInfo->privKey, + keyPairInfo->pubKey, + true, true)); + if (!newPrivKey) + return NS_ERROR_FAILURE; + + // The private key is stored on the selected slot now, and the copy we + // ultimately use for escrowing when the time comes lives + // in the internal slot. We will delete it from that slot + // after the requests are made. + } + + return NS_OK; +} + +/* + * FUNCTION: cryptojs_ReadArgsAndGenerateKey + * ------------------------------------- + * INPUTS: + * cx + * The JSContext associated with the execution of the corresponging + * crypto.generateCRMFRequest call + * argv + * A pointer to an array of JavaScript parameters passed to the + * method crypto.generateCRMFRequest. The array should have the + * 3 arguments keySize, "keyParams", and "keyGenAlg" mentioned in + * the definition of crypto.generateCRMFRequest at the following + * document http://docs.iplanet.com/docs/manuals/psm/11/cmcjavascriptapi.html + * keyGenType + * A structure used to store the information about the newly created + * key pair. + * uiCxt + * An interface requestor that would be used to get an nsIPrompt + * if we need to ask the user for a password. + * slotToUse + * The PKCS11 slot to use for generating the key pair. If nullptr, then + * this function should select a slot that can do the key generation + * from the keytype associted with the keyPairInfo, and pass it back to + * the caller so that subsequence key generations can use the same slot. + * willEscrow + * If true, then that means we will try to escrow the generated + * private key when building the CRMF request. If false, then + * we will not try to escrow the private key. + * + * NOTES: + * This function takes care of reading a set of 3 parameters that define + * one key generation. The argv pointer should be one that originates + * from the argv parameter passed in to the method nsCrypto::GenerateCRMFRequest. + * The function interprets the argument in the first index as an integer and + * passes that as the key size for the key generation-this parameter is + * mandatory. The second parameter is read in as a string. This value can + * be null in JavaScript world and everything will still work. The third + * parameter is a mandatory string that indicates what kind of key to generate. + * There should always be 1-to-1 correspondence between the strings compared + * in the function cryptojs_interpret_key_gen_type and the strings listed in + * document at http://docs.iplanet.com/docs/manuals/psm/11/cmcjavascriptapi.html + * under the definition of the method generateCRMFRequest, for the parameter + * "keyGenAlgN". After reading the parameters, the function then + * generates the key pairs passing the parameters parsed from the JavaScript i + * routine. + * + * RETURN: + * NS_OK if creating the Key was successful. Any other return value + * indicates an error. + */ + +static nsresult +cryptojs_ReadArgsAndGenerateKey(JSContext *cx, + JS::Value *argv, + nsKeyPairInfo *keyGenType, + nsIInterfaceRequestor *uiCxt, + PK11SlotInfo **slot, bool willEscrow) +{ + JSString *jsString; + JSAutoByteString params; + int keySize; + nsresult rv; + + if (!JSVAL_IS_INT(argv[0])) { + JS_ReportError(cx, "%s%s", JS_ERROR, + "passed in non-integer for key size"); + return NS_ERROR_FAILURE; + } + keySize = JSVAL_TO_INT(argv[0]); + if (!JSVAL_IS_NULL(argv[1])) { + JS::Rooted v(cx, argv[1]); + jsString = JS::ToString(cx, v); + NS_ENSURE_TRUE(jsString, NS_ERROR_OUT_OF_MEMORY); + argv[1] = STRING_TO_JSVAL(jsString); + params.encodeLatin1(cx, jsString); + NS_ENSURE_TRUE(!!params, NS_ERROR_OUT_OF_MEMORY); + } + + if (JSVAL_IS_NULL(argv[2])) { + JS_ReportError(cx,"%s%s", JS_ERROR, + "key generation type not specified"); + return NS_ERROR_FAILURE; + } + JS::Rooted v(cx, argv[2]); + jsString = JS::ToString(cx, v); + NS_ENSURE_TRUE(jsString, NS_ERROR_OUT_OF_MEMORY); + argv[2] = STRING_TO_JSVAL(jsString); + nsDependentJSString dependentKeyGenAlg; + NS_ENSURE_TRUE(dependentKeyGenAlg.init(cx, jsString), NS_ERROR_UNEXPECTED); + nsAutoString keyGenAlg(dependentKeyGenAlg); + keyGenAlg.Trim("\r\n\t "); + keyGenType->keyGenType = cryptojs_interpret_key_gen_type(keyGenAlg); + if (keyGenType->keyGenType == invalidKeyGen) { + NS_LossyConvertUTF16toASCII keyGenAlgNarrow(dependentKeyGenAlg); + JS_ReportError(cx, "%s%s%s", JS_ERROR, + "invalid key generation argument:", + keyGenAlgNarrow.get()); + goto loser; + } + if (!*slot) { + *slot = nsGetSlotForKeyGen(keyGenType->keyGenType, uiCxt); + if (!*slot) + goto loser; + } + + rv = cryptojs_generateOneKeyPair(cx,keyGenType,keySize,params.ptr(),uiCxt, + *slot,willEscrow); + + if (rv != NS_OK) { + NS_LossyConvertUTF16toASCII keyGenAlgNarrow(dependentKeyGenAlg); + JS_ReportError(cx,"%s%s%s", JS_ERROR, + "could not generate the key for algorithm ", + keyGenAlgNarrow.get()); + goto loser; + } + return NS_OK; +loser: + return NS_ERROR_FAILURE; +} + +//Utility funciton to free up the memory used by nsKeyPairInfo +//arrays. +static void +nsFreeKeyPairInfo(nsKeyPairInfo *keyids, int numIDs) +{ + NS_ASSERTION(keyids, "NULL pointer passed to nsFreeKeyPairInfo"); + if (!keyids) + return; + int i; + for (i=0; iGetCert()); + if (!cert) + return NS_ERROR_FAILURE; + + CRMFEncryptedKey *encrKey = + CRMF_CreateEncryptedKeyWithEncryptedValue(keyInfo->privKey, cert.get()); + if (!encrKey) + return NS_ERROR_FAILURE; + + CRMFPKIArchiveOptions *archOpt = + CRMF_CreatePKIArchiveOptions(crmfEncryptedPrivateKey, encrKey); + if (!archOpt) { + CRMF_DestroyEncryptedKey(encrKey); + return NS_ERROR_FAILURE; + } + SECStatus srv = CRMF_CertRequestSetPKIArchiveOptions(certReq, archOpt); + CRMF_DestroyEncryptedKey(encrKey); + CRMF_DestroyPKIArchiveOptions(archOpt); + if (srv != SECSuccess) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +//Set the Distinguished Name (Subject Name) for the cert +//being requested. +static nsresult +nsSetDNForRequest(CRMFCertRequest *certReq, char *reqDN) +{ + if (!reqDN || CRMF_CertRequestIsFieldPresent(certReq, crmfSubject)) { + return NS_ERROR_FAILURE; + } + ScopedCERTName subjectName(CERT_AsciiToName(reqDN)); + if (!subjectName) { + return NS_ERROR_FAILURE; + } + SECStatus srv = CRMF_CertRequestSetTemplateField(certReq, crmfSubject, + static_cast + (subjectName)); + return (srv == SECSuccess) ? NS_OK : NS_ERROR_FAILURE; +} + +//Set Registration Token Control on the request. +static nsresult +nsSetRegToken(CRMFCertRequest *certReq, char *regToken) +{ + // this should never happen, but might as well add this. + NS_ASSERTION(certReq, "A bogus certReq passed to nsSetRegToken"); + if (regToken){ + if (CRMF_CertRequestIsControlPresent(certReq, crmfRegTokenControl)) + return NS_ERROR_FAILURE; + + SECItem src; + src.data = (unsigned char*)regToken; + src.len = strlen(regToken); + SECItem *derEncoded = SEC_ASN1EncodeItem(nullptr, nullptr, &src, + SEC_ASN1_GET(SEC_UTF8StringTemplate)); + + if (!derEncoded) + return NS_ERROR_FAILURE; + + SECStatus srv = CRMF_CertRequestSetRegTokenControl(certReq, derEncoded); + SECITEM_FreeItem(derEncoded,true); + if (srv != SECSuccess) + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +//Set the Authenticator control on the cert reuest. It's just +//a string that gets passed along. +static nsresult +nsSetAuthenticator(CRMFCertRequest *certReq, char *authenticator) +{ + //This should never happen, but might as well check. + NS_ASSERTION(certReq, "Bogus certReq passed to nsSetAuthenticator"); + if (authenticator) { + if (CRMF_CertRequestIsControlPresent(certReq, crmfAuthenticatorControl)) + return NS_ERROR_FAILURE; + + SECItem src; + src.data = (unsigned char*)authenticator; + src.len = strlen(authenticator); + SECItem *derEncoded = SEC_ASN1EncodeItem(nullptr, nullptr, &src, + SEC_ASN1_GET(SEC_UTF8StringTemplate)); + if (!derEncoded) + return NS_ERROR_FAILURE; + + SECStatus srv = CRMF_CertRequestSetAuthenticatorControl(certReq, + derEncoded); + SECITEM_FreeItem(derEncoded, true); + if (srv != SECSuccess) + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +// ASN1 DER encoding rules say that when encoding a BIT string, +// the length in the header for the bit string is the number +// of "useful" bits in the BIT STRING. So the function finds +// it and sets accordingly for the returned item. +static void +nsPrepareBitStringForEncoding (SECItem *bitsmap, SECItem *value) +{ + unsigned char onebyte; + unsigned int i, len = 0; + + /* to prevent warning on some platform at compile time */ + onebyte = '\0'; + /* Get the position of the right-most turn-on bit */ + for (i = 0; i < (value->len ) * 8; ++i) { + if (i % 8 == 0) + onebyte = value->data[i/8]; + if (onebyte & 0x80) + len = i; + onebyte <<= 1; + } + + bitsmap->data = value->data; + /* Add one here since we work with base 1 */ + bitsmap->len = len + 1; +} + +//This next section defines all the functions that sets the +//keyUsageExtension for all the different types of key gens +//we handle. The keyUsageExtension is just a bit flag extension +//that we set in wrapper functions that call straight into +//nsSetKeyUsageExtension. There is one wrapper funciton for each +//keyGenType. The correct function will eventually be called +//by going through a switch statement based on the nsKeyGenType +//in the nsKeyPairInfo struct. +static nsresult +nsSetKeyUsageExtension(CRMFCertRequest *crmfReq, + unsigned char keyUsage) +{ + SECItem *encodedExt= nullptr; + SECItem keyUsageValue = { (SECItemType) 0, nullptr, 0 }; + SECItem bitsmap = { (SECItemType) 0, nullptr, 0 }; + SECStatus srv; + CRMFCertExtension *ext = nullptr; + CRMFCertExtCreationInfo extAddParams; + SEC_ASN1Template bitStrTemplate = {SEC_ASN1_BIT_STRING, 0, nullptr, + sizeof(SECItem)}; + + keyUsageValue.data = &keyUsage; + keyUsageValue.len = 1; + nsPrepareBitStringForEncoding(&bitsmap, &keyUsageValue); + + encodedExt = SEC_ASN1EncodeItem(nullptr, nullptr, &bitsmap,&bitStrTemplate); + if (!encodedExt) { + goto loser; + } + ext = CRMF_CreateCertExtension(SEC_OID_X509_KEY_USAGE, true, encodedExt); + if (!ext) { + goto loser; + } + extAddParams.numExtensions = 1; + extAddParams.extensions = &ext; + srv = CRMF_CertRequestSetTemplateField(crmfReq, crmfExtension, + &extAddParams); + if (srv != SECSuccess) { + goto loser; + } + CRMF_DestroyCertExtension(ext); + SECITEM_FreeItem(encodedExt, true); + return NS_OK; + loser: + if (ext) { + CRMF_DestroyCertExtension(ext); + } + if (encodedExt) { + SECITEM_FreeItem(encodedExt, true); + } + return NS_ERROR_FAILURE; +} + +static nsresult +nsSetRSADualUse(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_DIGITAL_SIGNATURE + | KU_NON_REPUDIATION + | KU_KEY_ENCIPHERMENT; + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetRSAKeyEx(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_KEY_ENCIPHERMENT; + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetRSASign(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_DIGITAL_SIGNATURE; + + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetRSANonRepudiation(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_NON_REPUDIATION; + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetRSASignNonRepudiation(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_DIGITAL_SIGNATURE | + KU_NON_REPUDIATION; + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetECDualUse(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_DIGITAL_SIGNATURE + | KU_NON_REPUDIATION + | KU_KEY_AGREEMENT; + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetECKeyEx(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_KEY_AGREEMENT; + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetECSign(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_DIGITAL_SIGNATURE; + + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetECNonRepudiation(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_NON_REPUDIATION; + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetECSignNonRepudiation(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_DIGITAL_SIGNATURE | + KU_NON_REPUDIATION; + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetDH(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_KEY_AGREEMENT; + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetDSASign(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_DIGITAL_SIGNATURE; + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetDSANonRepudiation(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_NON_REPUDIATION; + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetDSASignNonRepudiation(CRMFCertRequest *crmfReq) +{ + unsigned char keyUsage = KU_DIGITAL_SIGNATURE | + KU_NON_REPUDIATION; + + return nsSetKeyUsageExtension(crmfReq, keyUsage); +} + +static nsresult +nsSetKeyUsageExtension(CRMFCertRequest *crmfReq, nsKeyGenType keyGenType) +{ + nsresult rv; + + switch (keyGenType) { + case rsaDualUse: + rv = nsSetRSADualUse(crmfReq); + break; + case rsaEnc: + rv = nsSetRSAKeyEx(crmfReq); + break; + case rsaSign: + rv = nsSetRSASign(crmfReq); + break; + case rsaNonrepudiation: + rv = nsSetRSANonRepudiation(crmfReq); + break; + case rsaSignNonrepudiation: + rv = nsSetRSASignNonRepudiation(crmfReq); + break; + case ecDualUse: + rv = nsSetECDualUse(crmfReq); + break; + case ecEnc: + rv = nsSetECKeyEx(crmfReq); + break; + case ecSign: + rv = nsSetECSign(crmfReq); + break; + case ecNonrepudiation: + rv = nsSetECNonRepudiation(crmfReq); + break; + case ecSignNonrepudiation: + rv = nsSetECSignNonRepudiation(crmfReq); + break; + case dhEx: + rv = nsSetDH(crmfReq); + break; + case dsaSign: + rv = nsSetDSASign(crmfReq); + break; + case dsaNonrepudiation: + rv = nsSetDSANonRepudiation(crmfReq); + break; + case dsaSignNonrepudiation: + rv = nsSetDSASignNonRepudiation(crmfReq); + break; + default: + rv = NS_ERROR_FAILURE; + break; + } + return rv; +} + +//Create a single CRMFCertRequest with all of the necessary parts +//already installed. The request returned by this function will +//have all the parts necessary and can just be added to a +//Certificate Request Message. +static CRMFCertRequest* +nsCreateSingleCertReq(nsKeyPairInfo *keyInfo, char *reqDN, char *regToken, + char *authenticator, nsNSSCertificate *wrappingCert) +{ + uint32_t reqID; + nsresult rv; + + //The draft says the ID of the request should be a random + //number. We don't have a way of tracking this number + //to compare when the reply actually comes back,though. + PK11_GenerateRandom((unsigned char*)&reqID, sizeof(reqID)); + CRMFCertRequest *certReq = CRMF_CreateCertRequest(reqID); + if (!certReq) + return nullptr; + + long version = SEC_CERTIFICATE_VERSION_3; + SECStatus srv; + CERTSubjectPublicKeyInfo *spki = nullptr; + srv = CRMF_CertRequestSetTemplateField(certReq, crmfVersion, &version); + if (srv != SECSuccess) + goto loser; + + spki = SECKEY_CreateSubjectPublicKeyInfo(keyInfo->pubKey); + if (!spki) + goto loser; + + srv = CRMF_CertRequestSetTemplateField(certReq, crmfPublicKey, spki); + SECKEY_DestroySubjectPublicKeyInfo(spki); + if (srv != SECSuccess) + goto loser; + + if (wrappingCert && ns_can_escrow(keyInfo->keyGenType)) { + rv = nsSetEscrowAuthority(certReq, keyInfo, wrappingCert); + if (NS_FAILED(rv)) + goto loser; + } + rv = nsSetDNForRequest(certReq, reqDN); + if (NS_FAILED(rv)) + goto loser; + + rv = nsSetRegToken(certReq, regToken); + if (NS_FAILED(rv)) + goto loser; + + rv = nsSetAuthenticator(certReq, authenticator); + if (NS_FAILED(rv)) + goto loser; + + rv = nsSetKeyUsageExtension(certReq, keyInfo->keyGenType); + if (NS_FAILED(rv)) + goto loser; + + return certReq; +loser: + if (certReq) { + CRMF_DestroyCertRequest(certReq); + } + return nullptr; +} + +/* + * This function will set the Proof Of Possession (POP) for a request + * associated with a key pair intended to do Key Encipherment. Currently + * this means encryption only keys. + */ +static nsresult +nsSetKeyEnciphermentPOP(CRMFCertReqMsg *certReqMsg, bool isEscrowed) +{ + SECItem bitString; + unsigned char der[2]; + SECStatus srv; + + if (isEscrowed) { + /* For proof of possession on escrowed keys, we use the + * this Message option of POPOPrivKey and include a zero + * length bit string in the POP field. This is OK because the encrypted + * private key already exists as part of the PKIArchiveOptions + * Control and that for all intents and purposes proves that + * we do own the private key. + */ + der[0] = 0x03; /*We've got a bit string */ + der[1] = 0x00; /*We've got a 0 length bit string */ + bitString.data = der; + bitString.len = 2; + srv = CRMF_CertReqMsgSetKeyEnciphermentPOP(certReqMsg, crmfThisMessage, + crmfNoSubseqMess, &bitString); + } else { + /* If the encryption key is not being escrowed, then we set the + * Proof Of Possession to be a Challenge Response mechanism. + */ + srv = CRMF_CertReqMsgSetKeyEnciphermentPOP(certReqMsg, + crmfSubsequentMessage, + crmfChallengeResp, nullptr); + } + return (srv == SECSuccess) ? NS_OK : NS_ERROR_FAILURE; +} + +static void +nsCRMFEncoderItemCount(void *arg, const char *buf, unsigned long len); + +static void +nsCRMFEncoderItemStore(void *arg, const char *buf, unsigned long len); + +static nsresult +nsSet_EC_DHMAC_ProofOfPossession(CRMFCertReqMsg *certReqMsg, + nsKeyPairInfo *keyInfo, + CRMFCertRequest *certReq) +{ + // RFC 2511 Appendix A section 2 a) defines, + // the "text" input for HMAC shall be the DER encoded version of + // of the single cert request. + // We'll produce that encoding and destroy it afterwards, + // because when sending the complete package to the CA, + // we'll use a different encoding, one that includes POP and + // allows multiple requests to be sent in one step. + + unsigned long der_request_len = 0; + ScopedSECItem der_request; + + if (SECSuccess != CRMF_EncodeCertRequest(certReq, + nsCRMFEncoderItemCount, + &der_request_len)) + return NS_ERROR_FAILURE; + + der_request = SECITEM_AllocItem(nullptr, nullptr, der_request_len); + if (!der_request) + return NS_ERROR_FAILURE; + + // set len in returned SECItem back to zero, because it will + // be used as the destination offset inside the + // nsCRMFEncoderItemStore callback. + + der_request->len = 0; + + if (SECSuccess != CRMF_EncodeCertRequest(certReq, + nsCRMFEncoderItemStore, + der_request)) + return NS_ERROR_FAILURE; + + // RFC 2511 Appendix A section 2 c): + // "A key K is derived from the shared secret Kec and the subject and + // issuer names in the CA's certificate as follows: + // K = SHA1(DER-encoded-subjectName | Kec | DER-encoded-issuerName)" + + ScopedPK11SymKey shared_secret; + ScopedPK11SymKey subject_and_secret; + ScopedPK11SymKey subject_and_secret_and_issuer; + ScopedPK11SymKey sha1_of_subject_and_secret_and_issuer; + + shared_secret = + PK11_PubDeriveWithKDF(keyInfo->privKey, // SECKEYPrivateKey *privKey + keyInfo->ecPopPubKey, // SECKEYPublicKey *pubKey + false, // bool isSender + nullptr, // SECItem *randomA + nullptr, // SECItem *randomB + CKM_ECDH1_DERIVE, // CK_MECHANISM_TYPE derive + CKM_CONCATENATE_DATA_AND_BASE, // CK_MECHANISM_TYPE target + CKA_DERIVE, // CK_ATTRIBUTE_TYPE operation + 0, // int keySize + CKD_NULL, // CK_ULONG kdf + nullptr, // SECItem *sharedData + nullptr); // void *wincx + + if (!shared_secret) + return NS_ERROR_FAILURE; + + CK_KEY_DERIVATION_STRING_DATA concat_data_base; + concat_data_base.pData = keyInfo->ecPopCert->derSubject.data; + concat_data_base.ulLen = keyInfo->ecPopCert->derSubject.len; + SECItem concat_data_base_item; + concat_data_base_item.data = (unsigned char*)&concat_data_base; + concat_data_base_item.len = sizeof(CK_KEY_DERIVATION_STRING_DATA); + + subject_and_secret = + PK11_Derive(shared_secret, // PK11SymKey *baseKey + CKM_CONCATENATE_DATA_AND_BASE, // CK_MECHANISM_TYPE mechanism + &concat_data_base_item, // SECItem *param + CKM_CONCATENATE_BASE_AND_DATA, // CK_MECHANISM_TYPE target + CKA_DERIVE, // CK_ATTRIBUTE_TYPE operation + 0); // int keySize + + if (!subject_and_secret) + return NS_ERROR_FAILURE; + + CK_KEY_DERIVATION_STRING_DATA concat_base_data; + concat_base_data.pData = keyInfo->ecPopCert->derSubject.data; + concat_base_data.ulLen = keyInfo->ecPopCert->derSubject.len; + SECItem concat_base_data_item; + concat_base_data_item.data = (unsigned char*)&concat_base_data; + concat_base_data_item.len = sizeof(CK_KEY_DERIVATION_STRING_DATA); + + subject_and_secret_and_issuer = + PK11_Derive(subject_and_secret, // PK11SymKey *baseKey + CKM_CONCATENATE_BASE_AND_DATA, // CK_MECHANISM_TYPE mechanism + &concat_base_data_item, // SECItem *param + CKM_SHA1_KEY_DERIVATION, // CK_MECHANISM_TYPE target + CKA_DERIVE, // CK_ATTRIBUTE_TYPE operation + 0); // int keySize + + if (!subject_and_secret_and_issuer) + return NS_ERROR_FAILURE; + + sha1_of_subject_and_secret_and_issuer = + PK11_Derive(subject_and_secret_and_issuer, // PK11SymKey *baseKey + CKM_SHA1_KEY_DERIVATION, // CK_MECHANISM_TYPE mechanism + nullptr, // SECItem *param + CKM_SHA_1_HMAC, // CK_MECHANISM_TYPE target + CKA_SIGN, // CK_ATTRIBUTE_TYPE operation + 0); // int keySize + + if (!sha1_of_subject_and_secret_and_issuer) + return NS_ERROR_FAILURE; + + PK11Context *context = nullptr; + PK11ContextCleanerTrueParam context_cleaner(context); + + SECItem ignore; + ignore.data = 0; + ignore.len = 0; + + context = + PK11_CreateContextBySymKey(CKM_SHA_1_HMAC, // CK_MECHANISM_TYPE type + CKA_SIGN, // CK_ATTRIBUTE_TYPE operation + sha1_of_subject_and_secret_and_issuer, // PK11SymKey *symKey + &ignore); // SECItem *param + + if (!context) + return NS_ERROR_FAILURE; + + if (SECSuccess != PK11_DigestBegin(context)) + return NS_ERROR_FAILURE; + + if (SECSuccess != + PK11_DigestOp(context, der_request->data, der_request->len)) + return NS_ERROR_FAILURE; + + ScopedAutoSECItem result_hmac_sha1_item(SHA1_LENGTH); + + if (SECSuccess != + PK11_DigestFinal(context, + result_hmac_sha1_item.data, + &result_hmac_sha1_item.len, + SHA1_LENGTH)) + return NS_ERROR_FAILURE; + + if (SECSuccess != + CRMF_CertReqMsgSetKeyAgreementPOP(certReqMsg, crmfDHMAC, + crmfNoSubseqMess, &result_hmac_sha1_item)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +static nsresult +nsSetProofOfPossession(CRMFCertReqMsg *certReqMsg, + nsKeyPairInfo *keyInfo, + CRMFCertRequest *certReq) +{ + // Depending on the type of cert request we'll try + // POP mechanisms in different order, + // and add the result to the cert request message. + // + // For any signing or dual use cert, + // try signing first, + // fall back to DHMAC if we can + // (EC cert requests that provide keygen param "popcert"), + // otherwise fail. + // + // For encryption only certs that get escrowed, this is sufficient. + // + // For encryption only certs, that are not being escrowed, + // try DHMAC if we can + // (EC cert requests that provide keygen param "popcert"), + // otherwise we'll indicate challenge response should be used. + + bool isEncryptionOnlyCertRequest = false; + bool escrowEncryptionOnlyCert = false; + + switch (keyInfo->keyGenType) + { + case rsaEnc: + case ecEnc: + isEncryptionOnlyCertRequest = true; + break; + + case rsaSign: + case rsaDualUse: + case rsaNonrepudiation: + case rsaSignNonrepudiation: + case ecSign: + case ecDualUse: + case ecNonrepudiation: + case ecSignNonrepudiation: + case dsaSign: + case dsaNonrepudiation: + case dsaSignNonrepudiation: + break; + + case dhEx: + /* This case may be supported in the future, but for now, we just fall + * though to the default case and return an error for diffie-hellman keys. + */ + default: + return NS_ERROR_FAILURE; + }; + + if (isEncryptionOnlyCertRequest) + { + escrowEncryptionOnlyCert = + CRMF_CertRequestIsControlPresent(certReq,crmfPKIArchiveOptionsControl); + } + + bool gotDHMACParameters = false; + + if (isECKeyGenType(keyInfo->keyGenType) && + keyInfo->ecPopCert && + keyInfo->ecPopPubKey) + { + gotDHMACParameters = true; + } + + if (isEncryptionOnlyCertRequest) + { + if (escrowEncryptionOnlyCert) + return nsSetKeyEnciphermentPOP(certReqMsg, true); // escrowed + + if (gotDHMACParameters) + return nsSet_EC_DHMAC_ProofOfPossession(certReqMsg, keyInfo, certReq); + + return nsSetKeyEnciphermentPOP(certReqMsg, false); // not escrowed + } + + // !isEncryptionOnlyCertRequest + + SECStatus srv = CRMF_CertReqMsgSetSignaturePOP(certReqMsg, + keyInfo->privKey, + keyInfo->pubKey, nullptr, + nullptr, nullptr); + + if (srv == SECSuccess) + return NS_OK; + + if (!gotDHMACParameters) + return NS_ERROR_FAILURE; + + return nsSet_EC_DHMAC_ProofOfPossession(certReqMsg, keyInfo, certReq); +} + +static void +nsCRMFEncoderItemCount(void *arg, const char *buf, unsigned long len) +{ + unsigned long *count = (unsigned long *)arg; + *count += len; +} + +static void +nsCRMFEncoderItemStore(void *arg, const char *buf, unsigned long len) +{ + SECItem *dest = (SECItem *)arg; + memcpy(dest->data + dest->len, buf, len); + dest->len += len; +} + +static SECItem* +nsEncodeCertReqMessages(CRMFCertReqMsg **certReqMsgs) +{ + unsigned long len = 0; + if (CRMF_EncodeCertReqMessages(certReqMsgs, nsCRMFEncoderItemCount, &len) + != SECSuccess) { + return nullptr; + } + SECItem *dest = (SECItem *)PORT_Alloc(sizeof(SECItem)); + if (!dest) { + return nullptr; + } + dest->type = siBuffer; + dest->data = (unsigned char *)PORT_Alloc(len); + if (!dest->data) { + PORT_Free(dest); + return nullptr; + } + dest->len = 0; + + if (CRMF_EncodeCertReqMessages(certReqMsgs, nsCRMFEncoderItemStore, dest) + != SECSuccess) { + SECITEM_FreeItem(dest, true); + return nullptr; + } + return dest; +} + +//Create a Base64 encoded CRMFCertReqMsg that can be sent to a CA +//requesting one or more certificates to be issued. This function +//creates a single cert request per key pair and then appends it to +//a message that is ultimately sent off to a CA. +static char* +nsCreateReqFromKeyPairs(nsKeyPairInfo *keyids, int32_t numRequests, + char *reqDN, char *regToken, char *authenticator, + nsNSSCertificate *wrappingCert) +{ + // We'use the goto notation for clean-up purposes in this function + // that calls the C API of NSS. + int32_t i; + // The ASN1 encoder in NSS wants the last entry in the array to be + // nullptr so that it knows when the last element is. + CRMFCertReqMsg **certReqMsgs = new CRMFCertReqMsg*[numRequests+1]; + CRMFCertRequest *certReq; + if (!certReqMsgs) + return nullptr; + memset(certReqMsgs, 0, sizeof(CRMFCertReqMsg*)*(1+numRequests)); + SECStatus srv; + nsresult rv; + SECItem *encodedReq; + char *retString; + for (i=0; i(JS_GetContextPrivate(cx)); + + return nullptr; +} + +//The top level method which is a member of nsIDOMCrypto +//for generate a base64 encoded CRMF request. +CRMFObject* +nsCrypto::GenerateCRMFRequest(JSContext* aContext, + const nsCString& aReqDN, + const nsCString& aRegToken, + const nsCString& aAuthenticator, + const nsCString& aEaCert, + const nsCString& aJsCallback, + const Sequence& aArgs, + ErrorResult& aRv) +{ + nsNSSShutDownPreventionLock locker; + nsresult nrv; + + uint32_t argc = aArgs.Length(); + + /* + * Get all of the parameters. + */ + if (argc % 3 != 0) { + aRv.ThrowNotEnoughArgsError(); + return nullptr; + } + + if (aReqDN.IsVoid()) { + NS_WARNING("no DN specified"); + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + if (aJsCallback.IsVoid()) { + NS_WARNING("no completion function specified"); + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + JS::RootedObject script_obj(aContext, GetWrapper()); + if (MOZ_UNLIKELY(!script_obj)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + nsCOMPtr csp; + if (!nsContentUtils::GetContentSecurityPolicy(aContext, getter_AddRefs(csp))) { + NS_ERROR("Error: failed to get CSP"); + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + bool evalAllowed = true; + bool reportEvalViolations = false; + if (csp && NS_FAILED(csp->GetAllowsEval(&reportEvalViolations, &evalAllowed))) { + NS_WARNING("CSP: failed to get allowsEval"); + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + if (reportEvalViolations) { + NS_NAMED_LITERAL_STRING(scriptSample, "window.crypto.generateCRMFRequest: call to eval() or related function blocked by CSP"); + + const char *fileName; + uint32_t lineNum; + nsJSUtils::GetCallingLocation(aContext, &fileName, &lineNum); + csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, + NS_ConvertASCIItoUTF16(fileName), + scriptSample, + lineNum, + EmptyString(), + EmptyString()); + } + + if (!evalAllowed) { + NS_WARNING("eval() not allowed by Content Security Policy"); + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + //Put up some UI warning that someone is trying to + //escrow the private key. + //Don't addref this copy. That way ths reference goes away + //at the same the nsIX09Cert ref goes away. + nsNSSCertificate *escrowCert = nullptr; + nsCOMPtr nssCert; + bool willEscrow = false; + if (!aEaCert.IsVoid()) { + SECItem certDer = {siBuffer, nullptr, 0}; + SECStatus srv = ATOB_ConvertAsciiToItem(&certDer, aEaCert.get()); + if (srv != SECSuccess) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + mozilla::pkix::ScopedCERTCertificate cert( + CERT_NewTempCertificate(CERT_GetDefaultCertDB(), + &certDer, nullptr, false, true)); + if (!cert) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + escrowCert = nsNSSCertificate::Create(cert.get()); + nssCert = escrowCert; + if (!nssCert) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + nsCOMPtr dialogs; + nsresult rv = getNSSDialogs(getter_AddRefs(dialogs), + NS_GET_IID(nsIDOMCryptoDialogs), + NS_DOMCRYPTODIALOGS_CONTRACTID); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return nullptr; + } + + bool okay=false; + { + nsPSMUITracker tracker; + if (tracker.isUIForbidden()) { + okay = false; + } + else { + dialogs->ConfirmKeyEscrow(nssCert, &okay); + } + } + if (!okay) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + willEscrow = true; + } + nsCOMPtr uiCxt = new PipUIContext; + int32_t numRequests = argc / 3; + nsKeyPairInfo *keyids = new nsKeyPairInfo[numRequests]; + memset(keyids, 0, sizeof(nsKeyPairInfo)*numRequests); + int keyInfoIndex; + uint32_t i; + PK11SlotInfo *slot = nullptr; + // Go through all of the arguments and generate the appropriate key pairs. + for (i=0,keyInfoIndex=0; i(&aArgs[i]), + &keyids[keyInfoIndex], + uiCxt, &slot, willEscrow); + + if (NS_FAILED(nrv)) { + if (slot) + PK11_FreeSlot(slot); + nsFreeKeyPairInfo(keyids,numRequests); + aRv.Throw(nrv); + return nullptr; + } + } + // By this time we'd better have a slot for the key gen. + NS_ASSERTION(slot, "There was no slot selected for key generation"); + if (slot) + PK11_FreeSlot(slot); + + char *encodedRequest = nsCreateReqFromKeyPairs(keyids,numRequests, + const_cast(aReqDN.get()), + const_cast(aRegToken.get()), + const_cast(aAuthenticator.get()), + escrowCert); +#ifdef DEBUG_javi + printf ("Created the folloing CRMF request:\n%s\n", encodedRequest); +#endif + if (!encodedRequest) { + nsFreeKeyPairInfo(keyids, numRequests); + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + CRMFObject* newObject = new CRMFObject(); + newObject->SetCRMFRequest(encodedRequest); + PORT_Free(encodedRequest); + nsFreeKeyPairInfo(keyids, numRequests); + + // Post an event on the UI queue so that the JS gets called after + // we return control to the JS layer. Why do we have to this? + // Because when this API was implemented for PSM 1.x w/ Communicator, + // the only way to make this method work was to have a callback + // in the JS layer that got called after key generation had happened. + // So for backwards compatibility, we return control and then just post + // an event to call the JS the script provides as the code to execute + // when the request has been generated. + // + + nsCOMPtr secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + if (MOZ_UNLIKELY(!secMan)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + nsCOMPtr principals; + nsresult rv = secMan->GetSubjectPrincipal(getter_AddRefs(principals)); + if (NS_FAILED(nrv)) { + aRv.Throw(nrv); + return nullptr; + } + if (MOZ_UNLIKELY(!principals)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + nsCryptoRunArgs *args = new nsCryptoRunArgs(aContext); + + args->m_kungFuDeathGrip = GetISupportsFromContext(aContext); + args->m_scope = JS_GetParent(script_obj); + if (!aJsCallback.IsVoid()) { + args->m_jsCallback = aJsCallback; + } + args->m_principals = principals; + + nsCryptoRunnable *cryptoRunnable = new nsCryptoRunnable(args); + + rv = NS_DispatchToMainThread(cryptoRunnable); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + delete cryptoRunnable; + } + + return newObject; +} + +// Reminder that we inherit the memory passed into us here. +// An implementation to let us back up certs as an event. +nsP12Runnable::nsP12Runnable(nsIX509Cert **certArr, int32_t numCerts, + nsIPK11Token *token) +{ + mCertArr = certArr; + mNumCerts = numCerts; + mToken = token; +} + +nsP12Runnable::~nsP12Runnable() +{ + int32_t i; + for (i=0; i nssComponent(do_GetService(kNSSComponentCID, &rv)); + if (NS_FAILED(rv)) + return rv; + + //Build up the message that let's the user know we're trying to + //make PKCS12 backups of the new certs. + nssComponent->GetPIPNSSBundleString("ForcedBackup1", final); + final.Append(MOZ_UTF16("\n\n")); + nssComponent->GetPIPNSSBundleString("ForcedBackup2", temp); + final.Append(temp.get()); + final.Append(MOZ_UTF16("\n\n")); + + nssComponent->GetPIPNSSBundleString("ForcedBackup3", temp); + + final.Append(temp.get()); + nsNSSComponent::ShowAlertWithConstructedString(final); + + nsCOMPtr filePicker = + do_CreateInstance("@mozilla.org/filepicker;1", &rv); + if (!filePicker) { + NS_ERROR("Could not create a file picker when backing up certs."); + return rv; + } + + nsCOMPtr wwatch = + (do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr window; + wwatch->GetActiveWindow(getter_AddRefs(window)); + + nsString filePickMessage; + nssComponent->GetPIPNSSBundleString("chooseP12BackupFileDialog", + filePickMessage); + rv = filePicker->Init(window, filePickMessage, nsIFilePicker::modeSave); + NS_ENSURE_SUCCESS(rv, rv); + + filePicker->AppendFilter(NS_LITERAL_STRING("PKCS12"), + NS_LITERAL_STRING("*.p12")); + filePicker->AppendFilters(nsIFilePicker::filterAll); + + int16_t dialogReturn; + filePicker->Show(&dialogReturn); + if (dialogReturn == nsIFilePicker::returnCancel) + return NS_OK; //User canceled. It'd be nice if they couldn't, + //but oh well. + + nsCOMPtr localFile; + rv = filePicker->GetFile(getter_AddRefs(localFile)); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + nsPKCS12Blob p12Cxt; + + p12Cxt.SetToken(mToken); + p12Cxt.ExportToFile(localFile, mCertArr, mNumCerts); + return NS_OK; +} + +nsCryptoRunArgs::nsCryptoRunArgs(JSContext *cx) : m_cx(cx), m_scope(cx) {} + +nsCryptoRunArgs::~nsCryptoRunArgs() {} + +nsCryptoRunnable::nsCryptoRunnable(nsCryptoRunArgs *args) +{ + nsNSSShutDownPreventionLock locker; + NS_ASSERTION(args,"Passed nullptr to nsCryptoRunnable constructor."); + m_args = args; + NS_IF_ADDREF(m_args); +} + +nsCryptoRunnable::~nsCryptoRunnable() +{ + nsNSSShutDownPreventionLock locker; + NS_IF_RELEASE(m_args); +} + +//Implementation that runs the callback passed to +//crypto.generateCRMFRequest as an event. +NS_IMETHODIMP +nsCryptoRunnable::Run() +{ + nsNSSShutDownPreventionLock locker; + AutoPushJSContext cx(m_args->m_cx); + JSAutoRequest ar(cx); + JS::Rooted scope(cx, m_args->m_scope); + JSAutoCompartment ac(cx, scope); + + bool ok = + JS_EvaluateScript(cx, scope, m_args->m_jsCallback, + strlen(m_args->m_jsCallback), nullptr, 0); + return ok ? NS_OK : NS_ERROR_FAILURE; +} + +//Quick helper function to check if a newly issued cert +//already exists in the user's database. +static bool +nsCertAlreadyExists(SECItem *derCert) +{ + CERTCertDBHandle *handle = CERT_GetDefaultCertDB(); + bool retVal = false; + + mozilla::pkix::ScopedCERTCertificate cert( + CERT_FindCertByDERCert(handle, derCert)); + if (cert) { + if (cert->isperm && !cert->nickname && !cert->emailAddr) { + //If the cert doesn't have a nickname or email addr, it is + //bogus cruft, so delete it. + SEC_DeletePermCertificate(cert.get()); + } else if (cert->isperm) { + retVal = true; + } + } + return retVal; +} + +static int32_t +nsCertListCount(CERTCertList *certList) +{ + int32_t numCerts = 0; + CERTCertListNode *node; + + node = CERT_LIST_HEAD(certList); + while (!CERT_LIST_END(node, certList)) { + numCerts++; + node = CERT_LIST_NEXT(node); + } + return numCerts; +} + +//Import user certificates that arrive as a CMMF base64 encoded +//string. +void +nsCrypto::ImportUserCertificates(const nsAString& aNickname, + const nsAString& aCmmfResponse, + bool aDoForcedBackup, + nsAString& aReturn, + ErrorResult& aRv) +{ + nsNSSShutDownPreventionLock locker; + char *nickname=nullptr, *cmmfResponse=nullptr; + CMMFCertRepContent *certRepContent = nullptr; + int numResponses = 0; + nsIX509Cert **certArr = nullptr; + int i; + CMMFCertResponse *currResponse; + CMMFPKIStatus reqStatus; + CERTCertificate *currCert; + PK11SlotInfo *slot; + nsAutoCString localNick; + nsCOMPtr ctx = new PipUIContext(); + nsresult rv = NS_OK; + nsCOMPtr token; + + nickname = ToNewCString(aNickname); + cmmfResponse = ToNewCString(aCmmfResponse); + if (nsCRT::strcmp("null", nickname) == 0) { + nsMemory::Free(nickname); + nickname = nullptr; + } + + SECItem cmmfDer = {siBuffer, nullptr, 0}; + SECStatus srv = ATOB_ConvertAsciiToItem(&cmmfDer, cmmfResponse); + + if (srv != SECSuccess) { + rv = NS_ERROR_FAILURE; + goto loser; + } + + certRepContent = CMMF_CreateCertRepContentFromDER(CERT_GetDefaultCertDB(), + (const char*)cmmfDer.data, + cmmfDer.len); + if (!certRepContent) { + rv = NS_ERROR_FAILURE; + goto loser; + } + + numResponses = CMMF_CertRepContentGetNumResponses(certRepContent); + + if (aDoForcedBackup) { + //We've been asked to force the user to back up these + //certificates. Let's keep an array of them around which + //we pass along to the nsP12Runnable to use. + certArr = new nsIX509Cert*[numResponses]; + // If this is nullptr, chances are we're gonna fail really + // soon, but let's try to keep going just in case. + if (!certArr) + aDoForcedBackup = false; + + memset(certArr, 0, sizeof(nsIX509Cert*)*numResponses); + } + for (i=0; iderCert)) { + if (aDoForcedBackup) { + certArr[i] = nsNSSCertificate::Create(currCert); + if (!certArr[i]) + goto loser; + NS_ADDREF(certArr[i]); + } + CERT_DestroyCertificate(currCert); + CMMF_DestroyCertResponse(currResponse); + continue; + } + // Let's figure out which nickname to give the cert. If + // a certificate with the same subject name already exists, + // then just use that one, otherwise, get the default nickname. + if (currCert->nickname) { + localNick = currCert->nickname; + } + else if (!nickname || nickname[0] == '\0') { + nsNSSCertificateDB::get_default_nickname(currCert, ctx, localNick, locker); + } else { + //This is the case where we're getting a brand new + //cert that doesn't have the same subjectName as a cert + //that already exists in our db and the CA page has + //designated a nickname to use for the newly issued cert. + localNick = nickname; + } + { + char *cast_const_away = const_cast(localNick.get()); + slot = PK11_ImportCertForKey(currCert, cast_const_away, ctx); + } + if (!slot) { + rv = NS_ERROR_FAILURE; + goto loser; + } + if (aDoForcedBackup) { + certArr[i] = nsNSSCertificate::Create(currCert); + if (!certArr[i]) + goto loser; + NS_ADDREF(certArr[i]); + } + CERT_DestroyCertificate(currCert); + + if (!token) + token = new nsPK11Token(slot); + + PK11_FreeSlot(slot); + CMMF_DestroyCertResponse(currResponse); + } + //Let the loser: label take care of freeing up our reference to + //nickname (This way we don't free it twice and avoid crashing. + //That would be a good thing. + + //Import the root chain into the cert db. + { + mozilla::pkix::ScopedCERTCertList + caPubs(CMMF_CertRepContentGetCAPubs(certRepContent)); + if (caPubs) { + int32_t numCAs = nsCertListCount(caPubs.get()); + + NS_ASSERTION(numCAs > 0, "Invalid number of CA's"); + if (numCAs > 0) { + CERTCertListNode *node; + SECItem *derCerts; + + derCerts = static_cast + (nsMemory::Alloc(sizeof(SECItem)*numCAs)); + if (!derCerts) { + rv = NS_ERROR_OUT_OF_MEMORY; + goto loser; + } + for (node = CERT_LIST_HEAD(caPubs), i=0; + !CERT_LIST_END(node, caPubs); + node = CERT_LIST_NEXT(node), i++) { + derCerts[i] = node->cert->derCert; + } + nsNSSCertificateDB::ImportValidCACerts(numCAs, derCerts, ctx, locker); + nsMemory::Free(derCerts); + } + } + } + + if (aDoForcedBackup) { + // I can't pop up a file picker from the depths of JavaScript, + // so I'll just post an event on the UI queue to do the backups + // later. + nsCOMPtr p12Runnable = new nsP12Runnable(certArr, numResponses, + token); + if (!p12Runnable) { + rv = NS_ERROR_FAILURE; + goto loser; + } + + // null out the certArr pointer which has now been inherited by + // the nsP12Runnable instance so that we don't free up the + // memory on the way out. + certArr = nullptr; + + rv = NS_DispatchToMainThread(p12Runnable); + if (NS_FAILED(rv)) + goto loser; + } + + loser: + if (certArr) { + for (i=0; i domWindow = + do_QueryInterface(scriptContext->GetGlobalObject()); + if (!domWindow) { + return; + } + + nsCOMPtr domDocument; + domWindow->GetDocument(getter_AddRefs(domDocument)); + if (!domDocument) { + return; + } + + CallQueryInterface(domDocument, aDocument); + + return; +} + +void signTextOutputCallback(void *arg, const char *buf, unsigned long len) +{ + ((nsCString*)arg)->Append(buf, len); +} + +void +nsCrypto::SignText(JSContext* aContext, + const nsAString& aStringToSign, + const nsAString& aCaOption, + const Sequence& aArgs, + nsAString& aReturn) +{ + // XXX This code should return error codes, but we're keeping this + // backwards compatible with NS4.x and so we can't throw exceptions. + NS_NAMED_LITERAL_STRING(internalError, "error:internalError"); + + aReturn.Truncate(); + + uint32_t argc = aArgs.Length(); + + if (!aCaOption.EqualsLiteral("auto") && + !aCaOption.EqualsLiteral("ask")) { + NS_WARNING("caOption argument must be ask or auto"); + aReturn.Append(internalError); + + return; + } + + // It was decided to always behave as if "ask" were specified. + // XXX Should we warn in the JS Console for auto? + + nsCOMPtr uiContext = new PipUIContext; + if (!uiContext) { + aReturn.Append(internalError); + + return; + } + + bool bestOnly = true; + bool validOnly = true; + CERTCertList* certList = + CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(), certUsageEmailSigner, + bestOnly, validOnly, uiContext); + + uint32_t numCAs = argc; + if (numCAs > 0) { + nsAutoArrayPtr caNames(new char*[numCAs]); + if (!caNames) { + aReturn.Append(internalError); + return; + } + + uint32_t i; + for (i = 0; i < numCAs; ++i) + caNames[i] = const_cast(aArgs[i].get()); + + if (certList && + CERT_FilterCertListByCANames(certList, numCAs, caNames, + certUsageEmailSigner) != SECSuccess) { + aReturn.Append(internalError); + + return; + } + } + + if (!certList || CERT_LIST_EMPTY(certList)) { + aReturn.AppendLiteral("error:noMatchingCert"); + + return; + } + + nsCOMPtr fsd = + do_CreateInstance(NS_FORMSIGNINGDIALOG_CONTRACTID); + if (!fsd) { + aReturn.Append(internalError); + + return; + } + + nsCOMPtr document; + GetDocumentFromContext(aContext, getter_AddRefs(document)); + if (!document) { + aReturn.Append(internalError); + + return; + } + + // Get the hostname from the URL of the document. + nsIURI* uri = document->GetDocumentURI(); + if (!uri) { + aReturn.Append(internalError); + + return; + } + + nsresult rv; + + nsCString host; + rv = uri->GetHost(host); + if (NS_FAILED(rv)) { + aReturn.Append(internalError); + + return; + } + + int32_t numberOfCerts = 0; + CERTCertListNode* node; + for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList); + node = CERT_LIST_NEXT(node)) { + ++numberOfCerts; + } + + ScopedCERTCertNicknames nicknames(getNSSCertNicknamesFromCertList(certList)); + + if (!nicknames) { + aReturn.Append(internalError); + + return; + } + + NS_ASSERTION(nicknames->numnicknames == numberOfCerts, + "nicknames->numnicknames != numberOfCerts"); + + nsAutoArrayPtr certNicknameList(new char16_t*[nicknames->numnicknames * 2]); + if (!certNicknameList) { + aReturn.Append(internalError); + + return; + } + + char16_t** certDetailsList = certNicknameList.get() + nicknames->numnicknames; + + int32_t certsToUse; + for (node = CERT_LIST_HEAD(certList), certsToUse = 0; + !CERT_LIST_END(node, certList) && certsToUse < nicknames->numnicknames; + node = CERT_LIST_NEXT(node)) { + RefPtr tempCert(nsNSSCertificate::Create(node->cert)); + if (tempCert) { + nsAutoString nickWithSerial, details; + rv = tempCert->FormatUIStrings(NS_ConvertUTF8toUTF16(nicknames->nicknames[certsToUse]), + nickWithSerial, details); + if (NS_SUCCEEDED(rv)) { + certNicknameList[certsToUse] = ToNewUnicode(nickWithSerial); + if (certNicknameList[certsToUse]) { + certDetailsList[certsToUse] = ToNewUnicode(details); + if (!certDetailsList[certsToUse]) { + nsMemory::Free(certNicknameList[certsToUse]); + continue; + } + ++certsToUse; + } + } + } + } + + if (certsToUse == 0) { + aReturn.Append(internalError); + + return; + } + + NS_ConvertUTF8toUTF16 utf16Host(host); + + CERTCertificate *signingCert = nullptr; + bool tryAgain, canceled; + nsAutoString password; + do { + // Throw up the form signing confirmation dialog and get back the index + // of the selected cert. + int32_t selectedIndex = -1; + rv = fsd->ConfirmSignText(uiContext, utf16Host, aStringToSign, + const_cast(certNicknameList.get()), + const_cast(certDetailsList), + certsToUse, &selectedIndex, password, + &canceled); + if (NS_FAILED(rv) || canceled) { + break; // out of tryAgain loop + } + + int32_t j = 0; + for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList); + node = CERT_LIST_NEXT(node)) { + if (j == selectedIndex) { + signingCert = CERT_DupCertificate(node->cert); + break; // out of cert list iteration loop + } + ++j; + } + + if (!signingCert) { + rv = NS_ERROR_FAILURE; + break; // out of tryAgain loop + } + + NS_ConvertUTF16toUTF8 pwUtf8(password); + + tryAgain = + PK11_CheckUserPassword(signingCert->slot, + const_cast(pwUtf8.get())) != SECSuccess; + // XXX we should show an error dialog before retrying + } while (tryAgain); + + int32_t k; + for (k = 0; k < certsToUse; ++k) { + nsMemory::Free(certNicknameList[k]); + nsMemory::Free(certDetailsList[k]); + } + + if (NS_FAILED(rv)) { // something went wrong inside the tryAgain loop + aReturn.Append(internalError); + + return; + } + + if (canceled) { + aReturn.AppendLiteral("error:userCancel"); + + return; + } + + SECKEYPrivateKey* privKey = PK11_FindKeyByAnyCert(signingCert, uiContext); + if (!privKey) { + aReturn.Append(internalError); + + return; + } + + nsAutoCString charset(document->GetDocumentCharacterSet()); + + // XXX Doing what nsFormSubmission::GetEncoder does (see + // http://bugzilla.mozilla.org/show_bug.cgi?id=81203). + if (charset.EqualsLiteral("ISO-8859-1")) { + charset.AssignLiteral("windows-1252"); + } + + nsCOMPtr encoder = + do_CreateInstance(NS_SAVEASCHARSET_CONTRACTID); + if (encoder) { + rv = encoder->Init(charset.get(), + (nsISaveAsCharset::attr_EntityAfterCharsetConv + + nsISaveAsCharset::attr_FallbackDecimalNCR), + 0); + } + + nsXPIDLCString buffer; + if (aStringToSign.Length() > 0) { + if (encoder && NS_SUCCEEDED(rv)) { + rv = encoder->Convert(PromiseFlatString(aStringToSign).get(), + getter_Copies(buffer)); + if (NS_FAILED(rv)) { + aReturn.Append(internalError); + + return; + } + } + else { + AppendUTF16toUTF8(aStringToSign, buffer); + } + } + + HASHContext *hc = HASH_Create(HASH_AlgSHA1); + if (!hc) { + aReturn.Append(internalError); + + return; + } + + unsigned char hash[SHA1_LENGTH]; + + SECItem digest; + digest.data = hash; + + HASH_Begin(hc); + HASH_Update(hc, reinterpret_cast(buffer.get()), + buffer.Length()); + HASH_End(hc, digest.data, &digest.len, SHA1_LENGTH); + HASH_Destroy(hc); + + nsCString p7; + SECStatus srv = SECFailure; + + SEC_PKCS7ContentInfo *ci = SEC_PKCS7CreateSignedData(signingCert, + certUsageEmailSigner, + nullptr, SEC_OID_SHA1, + &digest, nullptr, uiContext); + if (ci) { + srv = SEC_PKCS7IncludeCertChain(ci, nullptr); + if (srv == SECSuccess) { + srv = SEC_PKCS7AddSigningTime(ci); + if (srv == SECSuccess) { + srv = SEC_PKCS7Encode(ci, signTextOutputCallback, &p7, nullptr, nullptr, + uiContext); + } + } + + SEC_PKCS7DestroyContentInfo(ci); + } + + if (srv != SECSuccess) { + aReturn.Append(internalError); + + return; + } + + SECItem binary_item; + binary_item.data = reinterpret_cast + (const_cast(p7.get())); + binary_item.len = p7.Length(); + + char *result = NSSBase64_EncodeItem(nullptr, nullptr, 0, &binary_item); + if (result) { + AppendASCIItoUTF16(result, aReturn); + } + else { + aReturn.Append(internalError); + } + + PORT_Free(result); + + return; +} + +void +nsCrypto::Logout(ErrorResult& aRv) +{ + NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); + + nsresult rv; + nsCOMPtr nssComponent(do_GetService(kNSSComponentCID, &rv)); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return; + } + + { + nsNSSShutDownPreventionLock locker; + PK11_LogoutAll(); + SSL_ClearSessionCache(); + } + + rv = nssComponent->LogoutAuthenticatedPK11(); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + } +} + +CRMFObject::CRMFObject() +{ + MOZ_COUNT_CTOR(CRMFObject); +} + +CRMFObject::~CRMFObject() +{ + MOZ_COUNT_DTOR(CRMFObject); +} + +JSObject* +CRMFObject::WrapObject(JSContext *aCx, bool* aTookOwnership) +{ + return CRMFObjectBinding::Wrap(aCx, this, aTookOwnership); +} + +void +CRMFObject::GetRequest(nsAString& aRequest) +{ + aRequest.Assign(mBase64Request); +} + +nsresult +CRMFObject::SetCRMFRequest(char *inRequest) +{ + mBase64Request.AssignWithConversion(inRequest); + return NS_OK; +} + +#endif // MOZ_DISABLE_CRYPTOLEGACY + +nsPkcs11::nsPkcs11() +{ +} + +nsPkcs11::~nsPkcs11() +{ +} + +//Delete a PKCS11 module from the user's profile. +NS_IMETHODIMP +nsPkcs11::DeleteModule(const nsAString& aModuleName) +{ + NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); + + nsNSSShutDownPreventionLock locker; + nsresult rv; + nsString errorMessage; + + nsCOMPtr nssComponent(do_GetService(kNSSComponentCID, &rv)); + if (NS_FAILED(rv)) + return rv; + + if (aModuleName.IsEmpty()) { + return NS_ERROR_ILLEGAL_VALUE; + } + + NS_ConvertUTF16toUTF8 modName(aModuleName); + int32_t modType; + SECStatus srv = SECMOD_DeleteModule(modName.get(), &modType); + if (srv == SECSuccess) { + SECMODModule *module = SECMOD_FindModule(modName.get()); + if (module) { +#ifndef MOZ_DISABLE_CRYPTOLEGACY + nssComponent->ShutdownSmartCardThread(module); +#endif + SECMOD_DestroyModule(module); + } + rv = NS_OK; + } else { + rv = NS_ERROR_FAILURE; + } + return rv; +} + +//Add a new PKCS11 module to the user's profile. +NS_IMETHODIMP +nsPkcs11::AddModule(const nsAString& aModuleName, + const nsAString& aLibraryFullPath, + int32_t aCryptoMechanismFlags, + int32_t aCipherFlags) +{ + NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); + + nsNSSShutDownPreventionLock locker; + nsresult rv; + nsCOMPtr nssComponent(do_GetService(kNSSComponentCID, &rv)); + + NS_ConvertUTF16toUTF8 moduleName(aModuleName); + nsCString fullPath; + // NSS doesn't support Unicode path. Use native charset + NS_CopyUnicodeToNative(aLibraryFullPath, fullPath); + uint32_t mechFlags = SECMOD_PubMechFlagstoInternal(aCryptoMechanismFlags); + uint32_t cipherFlags = SECMOD_PubCipherFlagstoInternal(aCipherFlags); + SECStatus srv = SECMOD_AddNewModule(moduleName.get(), fullPath.get(), + mechFlags, cipherFlags); + if (srv == SECSuccess) { + SECMODModule *module = SECMOD_FindModule(moduleName.get()); + if (module) { +#ifndef MOZ_DISABLE_CRYPTOLEGACY + nssComponent->LaunchSmartCardThread(module); +#endif + SECMOD_DestroyModule(module); + } + } + + // The error message we report to the user depends directly on + // what the return value for SEDMOD_AddNewModule is + switch (srv) { + case SECSuccess: + return NS_OK; + case SECFailure: + return NS_ERROR_FAILURE; + case -2: + return NS_ERROR_ILLEGAL_VALUE; + } + NS_ERROR("Bogus return value, this should never happen"); + return NS_ERROR_FAILURE; +} +