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(&notBefore);
296     validity->GetNotAfter(&notAfter);
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