// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/base/keygen_handler.h" #include <pk11pub.h> #include <secmod.h> #include <ssl.h> #include <nssb64.h> // NSSBase64_EncodeItem() #include <secder.h> // DER_Encode() #include <cryptohi.h> // SEC_DerSignData() #include <keyhi.h> // SECKEY_CreateSubjectPublicKeyInfo() #include "base/nss_util.h" #include "base/logging.h" namespace net { const int64 DEFAULT_RSA_PUBLIC_EXPONENT = 0x10001; // Template for creating the signed public key structure to be sent to the CA. DERTemplate SECAlgorithmIDTemplate[] = { { DER_SEQUENCE, 0, NULL, sizeof(SECAlgorithmID) }, { DER_OBJECT_ID, offsetof(SECAlgorithmID, algorithm), }, { DER_OPTIONAL | DER_ANY, offsetof(SECAlgorithmID, parameters), }, { 0, } }; DERTemplate CERTSubjectPublicKeyInfoTemplate[] = { { DER_SEQUENCE, 0, NULL, sizeof(CERTSubjectPublicKeyInfo) }, { DER_INLINE, offsetof(CERTSubjectPublicKeyInfo, algorithm), SECAlgorithmIDTemplate, }, { DER_BIT_STRING, offsetof(CERTSubjectPublicKeyInfo, subjectPublicKey), }, { 0, } }; DERTemplate CERTPublicKeyAndChallengeTemplate[] = { { DER_SEQUENCE, 0, NULL, sizeof(CERTPublicKeyAndChallenge) }, { DER_ANY, offsetof(CERTPublicKeyAndChallenge, spki), }, { DER_IA5_STRING, offsetof(CERTPublicKeyAndChallenge, challenge), }, { 0, } }; // This maps displayed strings indicating level of keysecurity in the <keygen> // menu to the key size in bits. // TODO(gauravsh): Should this mapping be moved else where? int RSAkeySizeMap[] = {2048, 1024}; KeygenHandler::KeygenHandler(int key_size_index, const std::string& challenge) : key_size_index_(key_size_index), challenge_(challenge) { if (key_size_index_ < 0 || key_size_index_ >= static_cast<int>(sizeof(RSAkeySizeMap) / sizeof(RSAkeySizeMap[0]))) key_size_index_ = 0; } // This function is largely copied from the Firefox's // <keygen> implementation in security/manager/ssl/src/nsKeygenHandler.cpp // FIXME(gauravsh): Do we need a copy of the Mozilla license here? std::string KeygenHandler::GenKeyAndSignChallenge() { // Key pair generation mechanism - only RSA is supported at present. PRUint32 keyGenMechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; // from nss/pkcs11t.h char *keystring = NULL; // Temporary store for result/ // Temporary structures used for generating the result // in the right format. PK11SlotInfo *slot = NULL; PK11RSAGenParams rsaKeyGenParams; // Keygen parameters. SECOidTag algTag; // used by SEC_DerSignData(). SECKEYPrivateKey *privateKey = NULL; SECKEYPublicKey *publicKey = NULL; CERTSubjectPublicKeyInfo *spkInfo = NULL; PRArenaPool *arena = NULL; SECStatus sec_rv =SECFailure; SECItem spkiItem; SECItem pkacItem; SECItem signedItem; CERTPublicKeyAndChallenge pkac; void *keyGenParams; pkac.challenge.data = NULL; bool isSuccess = true; // Set to false as soon as a step fails. std::string result_blob; // the result. // Ensure NSS is initialized. base::EnsureNSSInit(); slot = PK11_GetInternalKeySlot(); if (!slot) { LOG(ERROR) << "Couldn't get Internal key slot!"; isSuccess = false; goto failure; } switch (keyGenMechanism) { case CKM_RSA_PKCS_KEY_PAIR_GEN: rsaKeyGenParams.keySizeInBits = RSAkeySizeMap[key_size_index_]; rsaKeyGenParams.pe = DEFAULT_RSA_PUBLIC_EXPONENT; keyGenParams = &rsaKeyGenParams; algTag = SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION; // from <nss/secoidt.h>. break; default: // TODO(gauravsh): If we ever support other mechanisms, // this can be changed. LOG(ERROR) << "Only RSA keygen mechanism is supported"; isSuccess = false; goto failure; break; } // Need to make sure that the token was initialized. // Assume a null password. sec_rv = PK11_Authenticate(slot, PR_TRUE, NULL); if (SECSuccess != sec_rv) { LOG(ERROR) << "Couldn't initialze PK11 token!"; isSuccess = false; goto failure; } LOG(INFO) << "Creating key pair..."; privateKey = PK11_GenerateKeyPair(slot, keyGenMechanism, keyGenParams, &publicKey, PR_TRUE, // isPermanent? PR_TRUE, // isSensitive? NULL); LOG(INFO) << "done."; if (!privateKey) { LOG(INFO) << "Generation of Keypair failed!"; isSuccess = false; goto failure; } // The CA expects the signed public key in a specific format // Let's create that now. // Create a subject public key info from the public key. spkInfo = SECKEY_CreateSubjectPublicKeyInfo(publicKey); if (!spkInfo) { LOG(ERROR) << "Couldn't create SubjectPublicKeyInfo from public key"; isSuccess = false; goto failure; } // Temporary work store used by NSS. arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (!arena) { LOG(ERROR) << "PORT_NewArena: Couldn't allocate memory"; isSuccess = false; goto failure; } // DER encode the whole subjectPublicKeyInfo. sec_rv = DER_Encode(arena, &spkiItem, CERTSubjectPublicKeyInfoTemplate, spkInfo); if (SECSuccess != sec_rv) { LOG(ERROR) << "Couldn't DER Encode subjectPublicKeyInfo"; isSuccess = false; goto failure; } // Set up the PublicKeyAndChallenge data structure, then DER encode it. pkac.spki = spkiItem; pkac.challenge.len = challenge_.length(); pkac.challenge.data = (unsigned char *)strdup(challenge_.c_str()); if (!pkac.challenge.data) { LOG(ERROR) << "Out of memory while making a copy of challenge data"; isSuccess = false; goto failure; } sec_rv = DER_Encode(arena, &pkacItem, CERTPublicKeyAndChallengeTemplate, &pkac); if (SECSuccess != sec_rv) { LOG(ERROR) << "Couldn't DER Encode PublicKeyAndChallenge"; isSuccess = false; goto failure; } // Sign the DER encoded PublicKeyAndChallenge. sec_rv = SEC_DerSignData(arena, &signedItem, pkacItem.data, pkacItem.len, privateKey, algTag); if (SECSuccess != sec_rv) { LOG(ERROR) << "Couldn't sign the DER encoded PublicKeyandChallenge"; isSuccess = false; goto failure; } // Convert the signed public key and challenge into base64/ascii. keystring = NSSBase64_EncodeItem(arena, NULL, // NSS will allocate a buffer for us. 0, &signedItem); if (!keystring) { LOG(ERROR) << "Couldn't convert signed public key into base64"; isSuccess = false; goto failure; } result_blob = keystring; failure: if (!isSuccess) { LOG(ERROR) << "SSL Keygen failed!"; } else { LOG(INFO) << "SSl Keygen succeeded!"; } // Do cleanups if (privateKey) { // TODO(gauravsh): We still need to maintain the private key because it's // used for certificate enrollment checks. // PK11_DestroyTokenObject(privateKey->pkcs11Slot,privateKey->pkcs11ID); // SECKEY_DestroyPrivateKey(privateKey); } if (publicKey) { PK11_DestroyTokenObject(publicKey->pkcs11Slot, publicKey->pkcs11ID); } if (spkInfo) { SECKEY_DestroySubjectPublicKeyInfo(spkInfo); } if (publicKey) { SECKEY_DestroyPublicKey(publicKey); } if (arena) { PORT_FreeArena(arena, PR_TRUE); } if (slot != NULL) { PK11_FreeSlot(slot); } if (pkac.challenge.data) { free(pkac.challenge.data); } return (isSuccess ? result_blob : std::string()); } } // namespace net