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