Line data Source code
1 : /* This Source Code Form is subject to the terms of the Mozilla Public
2 : * License, v. 2.0. If a copy of the MPL was not distributed with this
3 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 :
5 : #include "LocalCertService.h"
6 :
7 : #include "CryptoTask.h"
8 : #include "ScopedNSSTypes.h"
9 : #include "cert.h"
10 : #include "mozilla/Casting.h"
11 : #include "mozilla/ModuleUtils.h"
12 : #include "mozilla/RefPtr.h"
13 : #include "nsIPK11Token.h"
14 : #include "nsIPK11TokenDB.h"
15 : #include "nsIX509Cert.h"
16 : #include "nsIX509CertValidity.h"
17 : #include "nsLiteralString.h"
18 : #include "nsProxyRelease.h"
19 : #include "nsServiceManagerUtils.h"
20 : #include "nsString.h"
21 : #include "pk11pub.h"
22 :
23 : namespace mozilla {
24 :
25 : // Given a name, searches the internal certificate/key database for a
26 : // self-signed certificate with subject and issuer distinguished name equal to
27 : // "CN={name}". This assumes that the user has already authenticated to the
28 : // internal DB if necessary.
29 : static nsresult
30 0 : FindLocalCertByName(const nsACString& aName,
31 : /*out*/ UniqueCERTCertificate& aResult)
32 : {
33 0 : aResult.reset(nullptr);
34 0 : NS_NAMED_LITERAL_CSTRING(commonNamePrefix, "CN=");
35 0 : nsAutoCString expectedDistinguishedName(commonNamePrefix + aName);
36 0 : UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
37 0 : if (!slot) {
38 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
39 : }
40 0 : UniqueCERTCertList certList(PK11_ListCertsInSlot(slot.get()));
41 0 : if (!certList) {
42 0 : return NS_ERROR_UNEXPECTED;
43 : }
44 0 : for (const CERTCertListNode* node = CERT_LIST_HEAD(certList);
45 0 : !CERT_LIST_END(node, certList); node = CERT_LIST_NEXT(node)) {
46 : // If this isn't a self-signed cert, it's not what we're interested in.
47 0 : if (!node->cert->isRoot) {
48 0 : continue;
49 : }
50 0 : if (!expectedDistinguishedName.Equals(node->cert->subjectName)) {
51 0 : continue; // Subject should match nickname
52 : }
53 0 : if (!expectedDistinguishedName.Equals(node->cert->issuerName)) {
54 0 : continue; // Issuer should match nickname
55 : }
56 : // We found a match.
57 0 : aResult.reset(CERT_DupCertificate(node->cert));
58 0 : return NS_OK;
59 : }
60 0 : return NS_OK;
61 : }
62 :
63 0 : class LocalCertTask : public CryptoTask
64 : {
65 : protected:
66 0 : explicit LocalCertTask(const nsACString& aNickname)
67 0 : : mNickname(aNickname)
68 : {
69 0 : }
70 :
71 0 : nsresult RemoveExisting()
72 : {
73 : // Search for any existing self-signed certs with this name and remove them
74 : for (;;) {
75 0 : UniqueCERTCertificate cert;
76 0 : nsresult rv = FindLocalCertByName(mNickname, cert);
77 0 : if (NS_FAILED(rv)) {
78 0 : return rv;
79 : }
80 : // If we didn't find a match, we're done.
81 0 : if (!cert) {
82 0 : return NS_OK;
83 : }
84 0 : rv = MapSECStatus(PK11_DeleteTokenCertAndKey(cert.get(), nullptr));
85 0 : if (NS_FAILED(rv)) {
86 0 : return rv;
87 : }
88 0 : }
89 : }
90 :
91 : nsCString mNickname;
92 : };
93 :
94 0 : class LocalCertGetTask final : public LocalCertTask
95 : {
96 : public:
97 0 : LocalCertGetTask(const nsACString& aNickname,
98 : nsILocalCertGetCallback* aCallback)
99 0 : : LocalCertTask(aNickname)
100 : , mCallback(new nsMainThreadPtrHolder<nsILocalCertGetCallback>(
101 0 : "LocalCertGetTask::mCallback", aCallback))
102 0 : , mCert(nullptr)
103 : {
104 0 : }
105 :
106 : private:
107 0 : virtual nsresult CalculateResult() override
108 : {
109 : // Try to lookup an existing cert in the DB
110 0 : nsresult rv = GetFromDB();
111 : // Make a new one if getting fails
112 0 : if (NS_FAILED(rv)) {
113 0 : rv = Generate();
114 : }
115 : // If generation fails, we're out of luck
116 0 : if (NS_FAILED(rv)) {
117 0 : return rv;
118 : }
119 :
120 : // Validate cert, make a new one if it fails
121 0 : rv = Validate();
122 0 : if (NS_FAILED(rv)) {
123 0 : rv = Generate();
124 : }
125 : // If generation fails, we're out of luck
126 0 : if (NS_FAILED(rv)) {
127 0 : return rv;
128 : }
129 :
130 0 : return NS_OK;
131 : }
132 :
133 0 : nsresult Generate()
134 : {
135 : nsresult rv;
136 :
137 : // Get the key slot for generation later
138 0 : UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
139 0 : if (!slot) {
140 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
141 : }
142 :
143 : // Remove existing certs with this name (if any)
144 0 : rv = RemoveExisting();
145 0 : if (NS_FAILED(rv)) {
146 0 : return rv;
147 : }
148 :
149 : // Generate a new cert
150 0 : NS_NAMED_LITERAL_CSTRING(commonNamePrefix, "CN=");
151 0 : nsAutoCString subjectNameStr(commonNamePrefix + mNickname);
152 0 : UniqueCERTName subjectName(CERT_AsciiToName(subjectNameStr.get()));
153 0 : if (!subjectName) {
154 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
155 : }
156 :
157 : // Use the well-known NIST P-256 curve
158 0 : SECOidData* curveOidData = SECOID_FindOIDByTag(SEC_OID_SECG_EC_SECP256R1);
159 0 : if (!curveOidData) {
160 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
161 : }
162 :
163 : // Get key params from the curve
164 0 : ScopedAutoSECItem keyParams(2 + curveOidData->oid.len);
165 0 : keyParams.data[0] = SEC_ASN1_OBJECT_ID;
166 0 : keyParams.data[1] = curveOidData->oid.len;
167 0 : memcpy(keyParams.data + 2, curveOidData->oid.data, curveOidData->oid.len);
168 :
169 : // Generate cert key pair
170 : SECKEYPublicKey* tempPublicKey;
171 : UniqueSECKEYPrivateKey privateKey(
172 : PK11_GenerateKeyPair(slot.get(), CKM_EC_KEY_PAIR_GEN, &keyParams,
173 : &tempPublicKey, true /* token */,
174 0 : true /* sensitive */, nullptr));
175 0 : UniqueSECKEYPublicKey publicKey(tempPublicKey);
176 0 : tempPublicKey = nullptr;
177 0 : if (!privateKey || !publicKey) {
178 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
179 : }
180 :
181 : // Create subject public key info and cert request
182 : UniqueCERTSubjectPublicKeyInfo spki(
183 0 : SECKEY_CreateSubjectPublicKeyInfo(publicKey.get()));
184 0 : if (!spki) {
185 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
186 : }
187 : UniqueCERTCertificateRequest certRequest(
188 0 : CERT_CreateCertificateRequest(subjectName.get(), spki.get(), nullptr));
189 0 : if (!certRequest) {
190 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
191 : }
192 :
193 : // Valid from one day before to 1 year after
194 : static const PRTime oneDay = PRTime(PR_USEC_PER_SEC)
195 : * PRTime(60) // sec
196 : * PRTime(60) // min
197 : * PRTime(24); // hours
198 :
199 0 : PRTime now = PR_Now();
200 0 : PRTime notBefore = now - oneDay;
201 0 : PRTime notAfter = now + (PRTime(365) * oneDay);
202 0 : UniqueCERTValidity validity(CERT_CreateValidity(notBefore, notAfter));
203 0 : if (!validity) {
204 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
205 : }
206 :
207 : // Generate random serial
208 : unsigned long serial;
209 : // This serial in principle could collide, but it's unlikely
210 0 : rv = MapSECStatus(PK11_GenerateRandomOnSlot(
211 : slot.get(), BitwiseCast<unsigned char*, unsigned long*>(&serial),
212 0 : sizeof(serial)));
213 0 : if (NS_FAILED(rv)) {
214 0 : return rv;
215 : }
216 :
217 : // Create the cert from these pieces
218 : UniqueCERTCertificate cert(
219 : CERT_CreateCertificate(serial, subjectName.get(), validity.get(),
220 0 : certRequest.get()));
221 0 : if (!cert) {
222 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
223 : }
224 :
225 : // Update the cert version to X509v3
226 0 : if (!cert->version.data) {
227 0 : return NS_ERROR_INVALID_POINTER;
228 : }
229 0 : *(cert->version.data) = SEC_CERTIFICATE_VERSION_3;
230 0 : cert->version.len = 1;
231 :
232 : // Set cert signature algorithm
233 0 : PLArenaPool* arena = cert->arena;
234 0 : if (!arena) {
235 0 : return NS_ERROR_INVALID_POINTER;
236 : }
237 0 : rv = MapSECStatus(
238 0 : SECOID_SetAlgorithmID(arena, &cert->signature,
239 0 : SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, 0));
240 0 : if (NS_FAILED(rv)) {
241 0 : return rv;
242 : }
243 :
244 : // Encode and self-sign the cert
245 : UniqueSECItem certDER(
246 0 : SEC_ASN1EncodeItem(nullptr, nullptr, cert.get(),
247 0 : SEC_ASN1_GET(CERT_CertificateTemplate)));
248 0 : if (!certDER) {
249 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
250 : }
251 0 : rv = MapSECStatus(
252 0 : SEC_DerSignData(arena, &cert->derCert, certDER->data, certDER->len,
253 : privateKey.get(),
254 0 : SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE));
255 0 : if (NS_FAILED(rv)) {
256 0 : return rv;
257 : }
258 :
259 : // Create a CERTCertificate from the signed data
260 : UniqueCERTCertificate certFromDER(
261 0 : CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &cert->derCert, nullptr,
262 0 : true /* perm */, true /* copyDER */));
263 0 : if (!certFromDER) {
264 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
265 : }
266 :
267 : // Save the cert in the DB
268 0 : rv = MapSECStatus(PK11_ImportCert(slot.get(), certFromDER.get(),
269 : CK_INVALID_HANDLE, mNickname.get(),
270 0 : false /* unused */));
271 0 : if (NS_FAILED(rv)) {
272 0 : return rv;
273 : }
274 :
275 : // We should now have cert in the DB, read it back in nsIX509Cert form
276 0 : return GetFromDB();
277 : }
278 :
279 0 : nsresult GetFromDB()
280 : {
281 0 : UniqueCERTCertificate cert;
282 0 : nsresult rv = FindLocalCertByName(mNickname, cert);
283 0 : if (NS_FAILED(rv)) {
284 0 : return rv;
285 : }
286 0 : if (!cert) {
287 0 : return NS_ERROR_FAILURE;
288 : }
289 0 : mCert = nsNSSCertificate::Create(cert.get());
290 0 : return NS_OK;
291 : }
292 :
293 0 : nsresult Validate()
294 : {
295 : // Verify cert is self-signed
296 : bool selfSigned;
297 0 : nsresult rv = mCert->GetIsSelfSigned(&selfSigned);
298 0 : if (NS_FAILED(rv)) {
299 0 : return rv;
300 : }
301 0 : if (!selfSigned) {
302 0 : return NS_ERROR_FAILURE;
303 : }
304 :
305 : // Check that subject and issuer match nickname
306 0 : nsXPIDLString subjectName;
307 0 : nsXPIDLString issuerName;
308 0 : mCert->GetSubjectName(subjectName);
309 0 : mCert->GetIssuerName(issuerName);
310 0 : if (!subjectName.Equals(issuerName)) {
311 0 : return NS_ERROR_FAILURE;
312 : }
313 0 : NS_NAMED_LITERAL_STRING(commonNamePrefix, "CN=");
314 : nsAutoString subjectNameFromNickname(
315 0 : commonNamePrefix + NS_ConvertASCIItoUTF16(mNickname));
316 0 : if (!subjectName.Equals(subjectNameFromNickname)) {
317 0 : return NS_ERROR_FAILURE;
318 : }
319 :
320 0 : nsCOMPtr<nsIX509CertValidity> validity;
321 0 : mCert->GetValidity(getter_AddRefs(validity));
322 :
323 : PRTime notBefore, notAfter;
324 0 : validity->GetNotBefore(¬Before);
325 0 : validity->GetNotAfter(¬After);
326 :
327 : // Ensure cert will last at least one more day
328 : static const PRTime oneDay = PRTime(PR_USEC_PER_SEC)
329 : * PRTime(60) // sec
330 : * PRTime(60) // min
331 : * PRTime(24); // hours
332 0 : PRTime now = PR_Now();
333 0 : if (notBefore > now ||
334 0 : notAfter < (now - oneDay)) {
335 0 : return NS_ERROR_FAILURE;
336 : }
337 :
338 0 : return NS_OK;
339 : }
340 :
341 0 : virtual void ReleaseNSSResources() override {}
342 :
343 0 : virtual void CallCallback(nsresult rv) override
344 : {
345 0 : (void) mCallback->HandleCert(mCert, rv);
346 0 : }
347 :
348 : nsMainThreadPtrHandle<nsILocalCertGetCallback> mCallback;
349 : nsCOMPtr<nsIX509Cert> mCert; // out
350 : };
351 :
352 0 : class LocalCertRemoveTask final : public LocalCertTask
353 : {
354 : public:
355 0 : LocalCertRemoveTask(const nsACString& aNickname,
356 : nsILocalCertCallback* aCallback)
357 0 : : LocalCertTask(aNickname)
358 : , mCallback(new nsMainThreadPtrHolder<nsILocalCertCallback>(
359 0 : "LocalCertRemoveTask::mCallback", aCallback))
360 : {
361 0 : }
362 :
363 : private:
364 0 : virtual nsresult CalculateResult() override
365 : {
366 0 : return RemoveExisting();
367 : }
368 :
369 0 : virtual void ReleaseNSSResources() override {}
370 :
371 0 : virtual void CallCallback(nsresult rv) override
372 : {
373 0 : (void) mCallback->HandleResult(rv);
374 0 : }
375 :
376 : nsMainThreadPtrHandle<nsILocalCertCallback> mCallback;
377 : };
378 :
379 0 : NS_IMPL_ISUPPORTS(LocalCertService, nsILocalCertService)
380 :
381 0 : LocalCertService::LocalCertService()
382 : {
383 0 : }
384 :
385 0 : LocalCertService::~LocalCertService()
386 : {
387 0 : }
388 :
389 : nsresult
390 0 : LocalCertService::LoginToKeySlot()
391 : {
392 : nsresult rv;
393 :
394 : // Get access to key slot
395 0 : UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
396 0 : if (!slot) {
397 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
398 : }
399 :
400 : // If no user password yet, set it an empty one
401 0 : if (PK11_NeedUserInit(slot.get())) {
402 0 : rv = MapSECStatus(PK11_InitPin(slot.get(), "", ""));
403 0 : if (NS_FAILED(rv)) {
404 0 : return rv;
405 : }
406 : }
407 :
408 : // If user has a password set, prompt to login
409 0 : if (PK11_NeedLogin(slot.get()) && !PK11_IsLoggedIn(slot.get(), nullptr)) {
410 : // Switching to XPCOM to get the UI prompt that PSM owns
411 : nsCOMPtr<nsIPK11TokenDB> tokenDB =
412 0 : do_GetService(NS_PK11TOKENDB_CONTRACTID);
413 0 : if (!tokenDB) {
414 0 : return NS_ERROR_FAILURE;
415 : }
416 0 : nsCOMPtr<nsIPK11Token> keyToken;
417 0 : tokenDB->GetInternalKeyToken(getter_AddRefs(keyToken));
418 0 : if (!keyToken) {
419 0 : return NS_ERROR_FAILURE;
420 : }
421 : // Prompt the user to login
422 0 : return keyToken->Login(false /* force */);
423 : }
424 :
425 0 : return NS_OK;
426 : }
427 :
428 : NS_IMETHODIMP
429 0 : LocalCertService::GetOrCreateCert(const nsACString& aNickname,
430 : nsILocalCertGetCallback* aCallback)
431 : {
432 0 : if (NS_WARN_IF(aNickname.IsEmpty())) {
433 0 : return NS_ERROR_INVALID_ARG;
434 : }
435 0 : if (NS_WARN_IF(!aCallback)) {
436 0 : return NS_ERROR_INVALID_POINTER;
437 : }
438 :
439 : // Before sending off the task, login to key slot if needed
440 0 : nsresult rv = LoginToKeySlot();
441 0 : if (NS_FAILED(rv)) {
442 0 : aCallback->HandleCert(nullptr, rv);
443 0 : return NS_OK;
444 : }
445 :
446 0 : RefPtr<LocalCertGetTask> task(new LocalCertGetTask(aNickname, aCallback));
447 0 : return task->Dispatch("LocalCertGet");
448 : }
449 :
450 : NS_IMETHODIMP
451 0 : LocalCertService::RemoveCert(const nsACString& aNickname,
452 : nsILocalCertCallback* aCallback)
453 : {
454 0 : if (NS_WARN_IF(aNickname.IsEmpty())) {
455 0 : return NS_ERROR_INVALID_ARG;
456 : }
457 0 : if (NS_WARN_IF(!aCallback)) {
458 0 : return NS_ERROR_INVALID_POINTER;
459 : }
460 :
461 : // Before sending off the task, login to key slot if needed
462 0 : nsresult rv = LoginToKeySlot();
463 0 : if (NS_FAILED(rv)) {
464 0 : aCallback->HandleResult(rv);
465 0 : return NS_OK;
466 : }
467 :
468 : RefPtr<LocalCertRemoveTask> task(
469 0 : new LocalCertRemoveTask(aNickname, aCallback));
470 0 : return task->Dispatch("LocalCertRm");
471 : }
472 :
473 : NS_IMETHODIMP
474 0 : LocalCertService::GetLoginPromptRequired(bool* aRequired)
475 : {
476 : nsresult rv;
477 :
478 : // Get access to key slot
479 0 : UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
480 0 : if (!slot) {
481 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
482 : }
483 :
484 : // If no user password yet, set it an empty one
485 0 : if (PK11_NeedUserInit(slot.get())) {
486 0 : rv = MapSECStatus(PK11_InitPin(slot.get(), "", ""));
487 0 : if (NS_FAILED(rv)) {
488 0 : return rv;
489 : }
490 : }
491 :
492 0 : *aRequired = PK11_NeedLogin(slot.get()) &&
493 0 : !PK11_IsLoggedIn(slot.get(), nullptr);
494 0 : return NS_OK;
495 : }
496 :
497 : #define LOCALCERTSERVICE_CID \
498 : { 0x47402be2, 0xe653, 0x45d0, \
499 : { 0x8d, 0xaa, 0x9f, 0x0d, 0xce, 0x0a, 0xc1, 0x48 } }
500 :
501 0 : NS_GENERIC_FACTORY_CONSTRUCTOR(LocalCertService)
502 :
503 : NS_DEFINE_NAMED_CID(LOCALCERTSERVICE_CID);
504 :
505 : static const Module::CIDEntry kLocalCertServiceCIDs[] = {
506 : { &kLOCALCERTSERVICE_CID, false, nullptr, LocalCertServiceConstructor },
507 : { nullptr }
508 : };
509 :
510 : static const Module::ContractIDEntry kLocalCertServiceContracts[] = {
511 : { LOCALCERTSERVICE_CONTRACTID, &kLOCALCERTSERVICE_CID },
512 : { nullptr }
513 : };
514 :
515 : static const Module kLocalCertServiceModule = {
516 : Module::kVersion,
517 : kLocalCertServiceCIDs,
518 : kLocalCertServiceContracts
519 : };
520 :
521 : NSMODULE_DEFN(LocalCertServiceModule) = &kLocalCertServiceModule;
522 :
523 : } // namespace mozilla
|