1// Copyright (c) 2009 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "net/base/keygen_handler.h" 6 7#include <pk11pub.h> 8#include <secmod.h> 9#include <ssl.h> 10#include <nssb64.h> // NSSBase64_EncodeItem() 11#include <secder.h> // DER_Encode() 12#include <cryptohi.h> // SEC_DerSignData() 13#include <keyhi.h> // SECKEY_CreateSubjectPublicKeyInfo() 14 15#include "base/nss_util.h" 16#include "base/logging.h" 17 18namespace net { 19 20const int64 DEFAULT_RSA_PUBLIC_EXPONENT = 0x10001; 21 22// Template for creating the signed public key structure to be sent to the CA. 23DERTemplate SECAlgorithmIDTemplate[] = { 24 { DER_SEQUENCE, 25 0, NULL, sizeof(SECAlgorithmID) }, 26 { DER_OBJECT_ID, 27 offsetof(SECAlgorithmID, algorithm), }, 28 { DER_OPTIONAL | DER_ANY, 29 offsetof(SECAlgorithmID, parameters), }, 30 { 0, } 31}; 32 33DERTemplate CERTSubjectPublicKeyInfoTemplate[] = { 34 { DER_SEQUENCE, 35 0, NULL, sizeof(CERTSubjectPublicKeyInfo) }, 36 { DER_INLINE, 37 offsetof(CERTSubjectPublicKeyInfo, algorithm), 38 SECAlgorithmIDTemplate, }, 39 { DER_BIT_STRING, 40 offsetof(CERTSubjectPublicKeyInfo, subjectPublicKey), }, 41 { 0, } 42}; 43 44DERTemplate CERTPublicKeyAndChallengeTemplate[] = { 45 { DER_SEQUENCE, 46 0, NULL, sizeof(CERTPublicKeyAndChallenge) }, 47 { DER_ANY, 48 offsetof(CERTPublicKeyAndChallenge, spki), }, 49 { DER_IA5_STRING, 50 offsetof(CERTPublicKeyAndChallenge, challenge), }, 51 { 0, } 52}; 53 54// This maps displayed strings indicating level of keysecurity in the <keygen> 55// menu to the key size in bits. 56// TODO(gauravsh): Should this mapping be moved else where? 57int RSAkeySizeMap[] = {2048, 1024}; 58 59KeygenHandler::KeygenHandler(int key_size_index, 60 const std::string& challenge) 61 : key_size_index_(key_size_index), 62 challenge_(challenge) { 63 if (key_size_index_ < 0 || 64 key_size_index_ >= 65 static_cast<int>(sizeof(RSAkeySizeMap) / sizeof(RSAkeySizeMap[0]))) 66 key_size_index_ = 0; 67} 68 69// This function is largely copied from the Firefox's 70// <keygen> implementation in security/manager/ssl/src/nsKeygenHandler.cpp 71// FIXME(gauravsh): Do we need a copy of the Mozilla license here? 72 73std::string KeygenHandler::GenKeyAndSignChallenge() { 74 // Key pair generation mechanism - only RSA is supported at present. 75 PRUint32 keyGenMechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; // from nss/pkcs11t.h 76 char *keystring = NULL; // Temporary store for result/ 77 78 // Temporary structures used for generating the result 79 // in the right format. 80 PK11SlotInfo *slot = NULL; 81 PK11RSAGenParams rsaKeyGenParams; // Keygen parameters. 82 SECOidTag algTag; // used by SEC_DerSignData(). 83 SECKEYPrivateKey *privateKey = NULL; 84 SECKEYPublicKey *publicKey = NULL; 85 CERTSubjectPublicKeyInfo *spkInfo = NULL; 86 PRArenaPool *arena = NULL; 87 SECStatus sec_rv =SECFailure; 88 SECItem spkiItem; 89 SECItem pkacItem; 90 SECItem signedItem; 91 CERTPublicKeyAndChallenge pkac; 92 void *keyGenParams; 93 pkac.challenge.data = NULL; 94 bool isSuccess = true; // Set to false as soon as a step fails. 95 96 std::string result_blob; // the result. 97 98 // Ensure NSS is initialized. 99 base::EnsureNSSInit(); 100 101 slot = PK11_GetInternalKeySlot(); 102 if (!slot) { 103 LOG(ERROR) << "Couldn't get Internal key slot!"; 104 isSuccess = false; 105 goto failure; 106 } 107 108 switch (keyGenMechanism) { 109 case CKM_RSA_PKCS_KEY_PAIR_GEN: 110 rsaKeyGenParams.keySizeInBits = RSAkeySizeMap[key_size_index_]; 111 rsaKeyGenParams.pe = DEFAULT_RSA_PUBLIC_EXPONENT; 112 keyGenParams = &rsaKeyGenParams; 113 114 algTag = SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION; // from <nss/secoidt.h>. 115 break; 116 default: 117 // TODO(gauravsh): If we ever support other mechanisms, 118 // this can be changed. 119 LOG(ERROR) << "Only RSA keygen mechanism is supported"; 120 isSuccess = false; 121 goto failure; 122 break; 123 } 124 125 // Need to make sure that the token was initialized. 126 // Assume a null password. 127 sec_rv = PK11_Authenticate(slot, PR_TRUE, NULL); 128 if (SECSuccess != sec_rv) { 129 LOG(ERROR) << "Couldn't initialze PK11 token!"; 130 isSuccess = false; 131 goto failure; 132 } 133 134 LOG(INFO) << "Creating key pair..."; 135 privateKey = PK11_GenerateKeyPair(slot, 136 keyGenMechanism, 137 keyGenParams, 138 &publicKey, 139 PR_TRUE, // isPermanent? 140 PR_TRUE, // isSensitive? 141 NULL); 142 LOG(INFO) << "done."; 143 144 if (!privateKey) { 145 LOG(INFO) << "Generation of Keypair failed!"; 146 isSuccess = false; 147 goto failure; 148 } 149 150 // The CA expects the signed public key in a specific format 151 // Let's create that now. 152 153 // Create a subject public key info from the public key. 154 spkInfo = SECKEY_CreateSubjectPublicKeyInfo(publicKey); 155 if (!spkInfo) { 156 LOG(ERROR) << "Couldn't create SubjectPublicKeyInfo from public key"; 157 isSuccess = false; 158 goto failure; 159 } 160 161 // Temporary work store used by NSS. 162 arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); 163 if (!arena) { 164 LOG(ERROR) << "PORT_NewArena: Couldn't allocate memory"; 165 isSuccess = false; 166 goto failure; 167 } 168 169 // DER encode the whole subjectPublicKeyInfo. 170 sec_rv = DER_Encode(arena, &spkiItem, CERTSubjectPublicKeyInfoTemplate, 171 spkInfo); 172 if (SECSuccess != sec_rv) { 173 LOG(ERROR) << "Couldn't DER Encode subjectPublicKeyInfo"; 174 isSuccess = false; 175 goto failure; 176 } 177 178 // Set up the PublicKeyAndChallenge data structure, then DER encode it. 179 pkac.spki = spkiItem; 180 pkac.challenge.len = challenge_.length(); 181 pkac.challenge.data = (unsigned char *)strdup(challenge_.c_str()); 182 if (!pkac.challenge.data) { 183 LOG(ERROR) << "Out of memory while making a copy of challenge data"; 184 isSuccess = false; 185 goto failure; 186 } 187 sec_rv = DER_Encode(arena, &pkacItem, CERTPublicKeyAndChallengeTemplate, 188 &pkac); 189 if (SECSuccess != sec_rv) { 190 LOG(ERROR) << "Couldn't DER Encode PublicKeyAndChallenge"; 191 isSuccess = false; 192 goto failure; 193 } 194 195 // Sign the DER encoded PublicKeyAndChallenge. 196 sec_rv = SEC_DerSignData(arena, &signedItem, pkacItem.data, pkacItem.len, 197 privateKey, algTag); 198 if (SECSuccess != sec_rv) { 199 LOG(ERROR) << "Couldn't sign the DER encoded PublicKeyandChallenge"; 200 isSuccess = false; 201 goto failure; 202 } 203 204 // Convert the signed public key and challenge into base64/ascii. 205 keystring = NSSBase64_EncodeItem(arena, 206 NULL, // NSS will allocate a buffer for us. 207 0, 208 &signedItem); 209 if (!keystring) { 210 LOG(ERROR) << "Couldn't convert signed public key into base64"; 211 isSuccess = false; 212 goto failure; 213 } 214 215 result_blob = keystring; 216 217 failure: 218 if (!isSuccess) { 219 LOG(ERROR) << "SSL Keygen failed!"; 220 } else { 221 LOG(INFO) << "SSl Keygen succeeded!"; 222 } 223 224 // Do cleanups 225 if (privateKey) { 226 // TODO(gauravsh): We still need to maintain the private key because it's 227 // used for certificate enrollment checks. 228 229 // PK11_DestroyTokenObject(privateKey->pkcs11Slot,privateKey->pkcs11ID); 230 // SECKEY_DestroyPrivateKey(privateKey); 231 } 232 233 if (publicKey) { 234 PK11_DestroyTokenObject(publicKey->pkcs11Slot, publicKey->pkcs11ID); 235 } 236 if (spkInfo) { 237 SECKEY_DestroySubjectPublicKeyInfo(spkInfo); 238 } 239 if (publicKey) { 240 SECKEY_DestroyPublicKey(publicKey); 241 } 242 if (arena) { 243 PORT_FreeArena(arena, PR_TRUE); 244 } 245 if (slot != NULL) { 246 PK11_FreeSlot(slot); 247 } 248 if (pkac.challenge.data) { 249 free(pkac.challenge.data); 250 } 251 252 return (isSuccess ? result_blob : std::string()); 253} 254 255} // namespace net 256