/src/mozilla-central/security/manager/ssl/nsPKCS12Blob.cpp
Line | Count | Source (jump to first uncovered line) |
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 "nsPKCS12Blob.h" |
6 | | |
7 | | #include "ScopedNSSTypes.h" |
8 | | #include "mozilla/Assertions.h" |
9 | | #include "mozilla/Casting.h" |
10 | | #include "mozilla/Unused.h" |
11 | | #include "nsICertificateDialogs.h" |
12 | | #include "nsIFile.h" |
13 | | #include "nsIInputStream.h" |
14 | | #include "nsIPrompt.h" |
15 | | #include "nsIWindowWatcher.h" |
16 | | #include "nsIX509CertDB.h" |
17 | | #include "nsNSSCertHelper.h" |
18 | | #include "nsNSSCertificate.h" |
19 | | #include "nsNSSHelper.h" |
20 | | #include "nsNetUtil.h" |
21 | | #include "nsReadableUtils.h" |
22 | | #include "nsThreadUtils.h" |
23 | | #include "p12plcy.h" |
24 | | #include "pkix/pkixtypes.h" |
25 | | #include "secerr.h" |
26 | | |
27 | | using namespace mozilla; |
28 | | extern LazyLogModule gPIPNSSLog; |
29 | | |
30 | 0 | #define PIP_PKCS12_BUFFER_SIZE 2048 |
31 | | #define PIP_PKCS12_NOSMARTCARD_EXPORT 4 |
32 | | #define PIP_PKCS12_RESTORE_FAILED 5 |
33 | | #define PIP_PKCS12_BACKUP_FAILED 6 |
34 | | #define PIP_PKCS12_NSS_ERROR 7 |
35 | | |
36 | | nsPKCS12Blob::nsPKCS12Blob() |
37 | | : mUIContext(new PipUIContext()) |
38 | 0 | { |
39 | 0 | } |
40 | | |
41 | | // Given a file handle, read a PKCS#12 blob from that file, decode it, and |
42 | | // import the results into the internal database. |
43 | | nsresult |
44 | | nsPKCS12Blob::ImportFromFile(nsIFile* aFile, const nsAString& aPassword, uint32_t& aError) |
45 | 0 | { |
46 | 0 | uint32_t passwordBufferLength; |
47 | 0 | UniquePtr<uint8_t[]> passwordBuffer; |
48 | 0 |
|
49 | 0 | UniquePK11SlotInfo slot(PK11_GetInternalKeySlot()); |
50 | 0 | if (!slot) { |
51 | 0 | return NS_ERROR_FAILURE; |
52 | 0 | } |
53 | 0 | |
54 | 0 | passwordBuffer = stringToBigEndianBytes(aPassword, passwordBufferLength); |
55 | 0 |
|
56 | 0 | // initialize the decoder |
57 | 0 | SECItem unicodePw = { siBuffer, passwordBuffer.get(), passwordBufferLength }; |
58 | 0 | UniqueSEC_PKCS12DecoderContext dcx( |
59 | 0 | SEC_PKCS12DecoderStart(&unicodePw, slot.get(), nullptr, nullptr, nullptr, |
60 | 0 | nullptr, nullptr, nullptr)); |
61 | 0 | if (!dcx) { |
62 | 0 | return NS_ERROR_FAILURE; |
63 | 0 | } |
64 | 0 | // read input aFile and feed it to the decoder |
65 | 0 | PRErrorCode nssError; |
66 | 0 | nsresult rv = inputToDecoder(dcx, aFile, nssError); |
67 | 0 | if (NS_FAILED(rv)) { |
68 | 0 | return rv; |
69 | 0 | } |
70 | 0 | if (nssError != 0) { |
71 | 0 | aError = handlePRErrorCode(nssError); |
72 | 0 | return NS_OK; |
73 | 0 | } |
74 | 0 | // verify the blob |
75 | 0 | SECStatus srv = SEC_PKCS12DecoderVerify(dcx.get()); |
76 | 0 | if (srv != SECSuccess) { |
77 | 0 | aError = handlePRErrorCode(PR_GetError()); |
78 | 0 | return NS_OK; |
79 | 0 | } |
80 | 0 | // validate bags |
81 | 0 | srv = SEC_PKCS12DecoderValidateBags(dcx.get(), nicknameCollision); |
82 | 0 | if (srv != SECSuccess) { |
83 | 0 | aError = handlePRErrorCode(PR_GetError()); |
84 | 0 | return NS_OK; |
85 | 0 | } |
86 | 0 | // import cert and key |
87 | 0 | srv = SEC_PKCS12DecoderImportBags(dcx.get()); |
88 | 0 | if (srv != SECSuccess) { |
89 | 0 | aError = handlePRErrorCode(PR_GetError()); |
90 | 0 | return NS_OK; |
91 | 0 | } |
92 | 0 | aError = nsIX509CertDB::Success; |
93 | 0 | return NS_OK; |
94 | 0 | } |
95 | | |
96 | | static bool |
97 | | isExtractable(UniqueSECKEYPrivateKey& privKey) |
98 | 0 | { |
99 | 0 | ScopedAutoSECItem value; |
100 | 0 | SECStatus rv = PK11_ReadRawAttribute( |
101 | 0 | PK11_TypePrivKey, privKey.get(), CKA_EXTRACTABLE, &value); |
102 | 0 | if (rv != SECSuccess) { |
103 | 0 | return false; |
104 | 0 | } |
105 | 0 | |
106 | 0 | bool isExtractable = false; |
107 | 0 | if ((value.len == 1) && value.data) { |
108 | 0 | isExtractable = !!(*(CK_BBOOL*)value.data); |
109 | 0 | } |
110 | 0 | return isExtractable; |
111 | 0 | } |
112 | | |
113 | | // Having already loaded the certs, form them into a blob (loading the keys |
114 | | // also), encode the blob, and stuff it into the file. |
115 | | nsresult |
116 | | nsPKCS12Blob::ExportToFile(nsIFile* aFile, nsIX509Cert** aCerts, int aNumCerts, |
117 | | const nsAString& aPassword, uint32_t& aError) |
118 | 0 | { |
119 | 0 |
|
120 | 0 | // get file password (unicode) |
121 | 0 | uint32_t passwordBufferLength; |
122 | 0 | UniquePtr<uint8_t[]> passwordBuffer; |
123 | 0 | passwordBuffer = stringToBigEndianBytes(aPassword, passwordBufferLength); |
124 | 0 | aError = nsIX509CertDB::Success; |
125 | 0 | if (!passwordBuffer) { |
126 | 0 | return NS_OK; |
127 | 0 | } |
128 | 0 | UniqueSEC_PKCS12ExportContext ecx( |
129 | 0 | SEC_PKCS12CreateExportContext(nullptr, nullptr, nullptr, nullptr)); |
130 | 0 | if (!ecx) { |
131 | 0 | aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; |
132 | 0 | return NS_OK; |
133 | 0 | } |
134 | 0 | // add password integrity |
135 | 0 | SECItem unicodePw = { siBuffer, passwordBuffer.get(), passwordBufferLength }; |
136 | 0 | SECStatus srv = SEC_PKCS12AddPasswordIntegrity(ecx.get(), &unicodePw, |
137 | 0 | SEC_OID_SHA1); |
138 | 0 | if (srv != SECSuccess) { |
139 | 0 | aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; |
140 | 0 | return NS_OK; |
141 | 0 | } |
142 | 0 | for (int i = 0; i < aNumCerts; i++) { |
143 | 0 | UniqueCERTCertificate nssCert(aCerts[i]->GetCert()); |
144 | 0 | if (!nssCert) { |
145 | 0 | aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; |
146 | 0 | return NS_OK; |
147 | 0 | } |
148 | 0 | // We can probably only successfully export certs that are on the internal |
149 | 0 | // token. Most, if not all, smart card vendors won't let you extract the |
150 | 0 | // private key (in any way shape or form) from the card. So let's punt if |
151 | 0 | // the cert is not in the internal db. |
152 | 0 | if (nssCert->slot && !PK11_IsInternal(nssCert->slot)) { |
153 | 0 | // We aren't the internal token, see if the key is extractable. |
154 | 0 | UniqueSECKEYPrivateKey privKey( |
155 | 0 | PK11_FindKeyByDERCert(nssCert->slot, nssCert.get(), mUIContext)); |
156 | 0 | if (privKey && !isExtractable(privKey)) { |
157 | 0 | // This is informative. If a serious error occurs later it will |
158 | 0 | // override it later and return. |
159 | 0 | aError = nsIX509CertDB::ERROR_PKCS12_NOSMARTCARD_EXPORT; |
160 | 0 | continue; |
161 | 0 | } |
162 | 0 | } |
163 | 0 | |
164 | 0 | // certSafe and keySafe are owned by ecx. |
165 | 0 | SEC_PKCS12SafeInfo* certSafe; |
166 | 0 | SEC_PKCS12SafeInfo* keySafe = SEC_PKCS12CreateUnencryptedSafe(ecx.get()); |
167 | 0 | if (!SEC_PKCS12IsEncryptionAllowed() || PK11_IsFIPS()) { |
168 | 0 | certSafe = keySafe; |
169 | 0 | } else { |
170 | 0 | certSafe = SEC_PKCS12CreatePasswordPrivSafe( |
171 | 0 | ecx.get(), &unicodePw, |
172 | 0 | SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC); |
173 | 0 | } |
174 | 0 | if (!certSafe || !keySafe) { |
175 | 0 | aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; |
176 | 0 | return NS_OK; |
177 | 0 | } |
178 | 0 | // add the cert and key to the blob |
179 | 0 | srv = SEC_PKCS12AddCertAndKey( |
180 | 0 | ecx.get(), |
181 | 0 | certSafe, |
182 | 0 | nullptr, |
183 | 0 | nssCert.get(), |
184 | 0 | CERT_GetDefaultCertDB(), |
185 | 0 | keySafe, |
186 | 0 | nullptr, |
187 | 0 | true, |
188 | 0 | &unicodePw, |
189 | 0 | SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC); |
190 | 0 | if (srv != SECSuccess) { |
191 | 0 | aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; |
192 | 0 | return NS_OK; |
193 | 0 | } |
194 | 0 | } |
195 | 0 |
|
196 | 0 | UniquePRFileDesc prFile; |
197 | 0 | PRFileDesc* rawPRFile; |
198 | 0 | nsresult rv = aFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, |
199 | 0 | 0664, &rawPRFile); |
200 | 0 | if (NS_FAILED(rv) || !rawPRFile) { |
201 | 0 | aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; |
202 | 0 | return NS_OK; |
203 | 0 | } |
204 | 0 | prFile.reset(rawPRFile); |
205 | 0 | // encode and write |
206 | 0 | srv = SEC_PKCS12Encode(ecx.get(), writeExportFile, prFile.get()); |
207 | 0 | if (srv != SECSuccess) { |
208 | 0 | aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; |
209 | 0 | } |
210 | 0 | return NS_OK; |
211 | 0 | } |
212 | | |
213 | | // For the NSS PKCS#12 library, must convert PRUnichars (shorts) to a buffer of |
214 | | // octets. Must handle byte order correctly. |
215 | | UniquePtr<uint8_t[]> |
216 | | nsPKCS12Blob::stringToBigEndianBytes(const nsAString& uni, uint32_t& bytesLength) |
217 | 0 | { |
218 | 0 | if (uni.IsVoid()) { |
219 | 0 | bytesLength = 0; |
220 | 0 | return nullptr; |
221 | 0 | } |
222 | 0 | |
223 | 0 | uint32_t wideLength = uni.Length() + 1; // +1 for the null terminator. |
224 | 0 | bytesLength = wideLength * 2; |
225 | 0 | auto buffer = MakeUnique<uint8_t[]>(bytesLength); |
226 | 0 |
|
227 | 0 | // We have to use a cast here because on Windows, uni.get() returns |
228 | 0 | // char16ptr_t instead of char16_t*. |
229 | 0 | mozilla::NativeEndian::copyAndSwapToBigEndian( |
230 | 0 | buffer.get(), static_cast<const char16_t*>(uni.BeginReading()), wideLength); |
231 | 0 |
|
232 | 0 | return buffer; |
233 | 0 | } |
234 | | |
235 | | // Given a decoder, read bytes from file and input them to the decoder. |
236 | | nsresult |
237 | | nsPKCS12Blob::inputToDecoder(UniqueSEC_PKCS12DecoderContext& dcx, nsIFile* file, |
238 | | PRErrorCode& nssError) |
239 | 0 | { |
240 | 0 | nssError = 0; |
241 | 0 |
|
242 | 0 | nsCOMPtr<nsIInputStream> fileStream; |
243 | 0 | nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file); |
244 | 0 | if (NS_FAILED(rv)) { |
245 | 0 | return rv; |
246 | 0 | } |
247 | 0 | |
248 | 0 | char buf[PIP_PKCS12_BUFFER_SIZE]; |
249 | 0 | uint32_t amount; |
250 | 0 | while (true) { |
251 | 0 | rv = fileStream->Read(buf, PIP_PKCS12_BUFFER_SIZE, &amount); |
252 | 0 | if (NS_FAILED(rv)) { |
253 | 0 | return rv; |
254 | 0 | } |
255 | 0 | // feed the file data into the decoder |
256 | 0 | SECStatus srv = SEC_PKCS12DecoderUpdate( |
257 | 0 | dcx.get(), (unsigned char*)buf, amount); |
258 | 0 | if (srv != SECSuccess) { |
259 | 0 | nssError = PR_GetError(); |
260 | 0 | return NS_OK; |
261 | 0 | } |
262 | 0 | if (amount < PIP_PKCS12_BUFFER_SIZE) { |
263 | 0 | break; |
264 | 0 | } |
265 | 0 | } |
266 | 0 | return NS_OK; |
267 | 0 | } |
268 | | |
269 | | // What to do when the nickname collides with one already in the db. |
270 | | SECItem* |
271 | | nsPKCS12Blob::nicknameCollision(SECItem* oldNick, PRBool* cancel, void* wincx) |
272 | 0 | { |
273 | 0 | *cancel = false; |
274 | 0 | int count = 1; |
275 | 0 | nsCString nickname; |
276 | 0 | nsAutoString nickFromProp; |
277 | 0 | nsresult rv = GetPIPNSSBundleString("P12DefaultNickname", nickFromProp); |
278 | 0 | if (NS_FAILED(rv)) { |
279 | 0 | return nullptr; |
280 | 0 | } |
281 | 0 | NS_ConvertUTF16toUTF8 nickFromPropC(nickFromProp); |
282 | 0 | // The user is trying to import a PKCS#12 file that doesn't have the |
283 | 0 | // attribute we use to set the nickname. So in order to reduce the |
284 | 0 | // number of interactions we require with the user, we'll build a nickname |
285 | 0 | // for the user. The nickname isn't prominently displayed in the UI, |
286 | 0 | // so it's OK if we generate one on our own here. |
287 | 0 | // XXX If the NSS API were smarter and actually passed a pointer to |
288 | 0 | // the CERTCertificate* we're importing we could actually just |
289 | 0 | // call default_nickname (which is what the issuance code path |
290 | 0 | // does) and come up with a reasonable nickname. Alas, the NSS |
291 | 0 | // API limits our ability to produce a useful nickname without |
292 | 0 | // bugging the user. :( |
293 | 0 | while (1) { |
294 | 0 | // If we've gotten this far, that means there isn't a certificate |
295 | 0 | // in the database that has the same subject name as the cert we're |
296 | 0 | // trying to import. So we need to come up with a "nickname" to |
297 | 0 | // satisfy the NSS requirement or fail in trying to import. |
298 | 0 | // Basically we use a default nickname from a properties file and |
299 | 0 | // see if a certificate exists with that nickname. If there isn't, then |
300 | 0 | // create update the count by one and append the string '#1' Or |
301 | 0 | // whatever the count currently is, and look for a cert with |
302 | 0 | // that nickname. Keep updating the count until we find a nickname |
303 | 0 | // without a corresponding cert. |
304 | 0 | // XXX If a user imports *many* certs without the 'friendly name' |
305 | 0 | // attribute, then this may take a long time. :( |
306 | 0 | nickname = nickFromPropC; |
307 | 0 | if (count > 1) { |
308 | 0 | nickname.AppendPrintf(" #%d", count); |
309 | 0 | } |
310 | 0 | UniqueCERTCertificate cert( |
311 | 0 | CERT_FindCertByNickname(CERT_GetDefaultCertDB(), nickname.get())); |
312 | 0 | if (!cert) { |
313 | 0 | break; |
314 | 0 | } |
315 | 0 | count++; |
316 | 0 | } |
317 | 0 | UniqueSECItem newNick(SECITEM_AllocItem(nullptr, nullptr, |
318 | 0 | nickname.Length() + 1)); |
319 | 0 | if (!newNick) { |
320 | 0 | return nullptr; |
321 | 0 | } |
322 | 0 | memcpy(newNick->data, nickname.get(), nickname.Length()); |
323 | 0 | newNick->data[nickname.Length()] = 0; |
324 | 0 |
|
325 | 0 | return newNick.release(); |
326 | 0 | } |
327 | | |
328 | | // write bytes to the exported PKCS#12 file |
329 | | void |
330 | | nsPKCS12Blob::writeExportFile(void* arg, const char* buf, unsigned long len) |
331 | 0 | { |
332 | 0 | PRFileDesc* file = static_cast<PRFileDesc*>(arg); |
333 | 0 | MOZ_RELEASE_ASSERT(file); |
334 | 0 | PR_Write(file, buf, len); |
335 | 0 | } |
336 | | |
337 | | // Translate PRErrorCode to nsIX509CertDB error |
338 | | uint32_t |
339 | | nsPKCS12Blob::handlePRErrorCode(PRErrorCode aPrerr) |
340 | 0 | { |
341 | 0 | MOZ_ASSERT(aPrerr != 0); |
342 | 0 | uint32_t error = nsIX509CertDB::ERROR_UNKNOWN; |
343 | 0 | switch (aPrerr) { |
344 | 0 | case SEC_ERROR_PKCS12_CERT_COLLISION: |
345 | 0 | error = nsIX509CertDB::ERROR_PKCS12_DUPLICATE_DATA; |
346 | 0 | break; |
347 | 0 | // INVALID_ARGS is returned on bad password when importing cert |
348 | 0 | // exported from firefox or generated by openssl |
349 | 0 | case SEC_ERROR_INVALID_ARGS: |
350 | 0 | case SEC_ERROR_BAD_PASSWORD: |
351 | 0 | error = nsIX509CertDB::ERROR_BAD_PASSWORD; |
352 | 0 | break; |
353 | 0 | case SEC_ERROR_BAD_DER: |
354 | 0 | case SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE: |
355 | 0 | case SEC_ERROR_PKCS12_INVALID_MAC: |
356 | 0 | error = nsIX509CertDB::ERROR_DECODE_ERROR; |
357 | 0 | break; |
358 | 0 | case SEC_ERROR_PKCS12_DUPLICATE_DATA: |
359 | 0 | error = nsIX509CertDB::ERROR_PKCS12_DUPLICATE_DATA; |
360 | 0 | break; |
361 | 0 | } |
362 | 0 | return error; |
363 | 0 | } |