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/. */
5 #include "nsPKCS12Blob.h"
7 #include "mozilla/Assertions.h"
8 #include "mozilla/Casting.h"
9 #include "mozilla/Logging.h"
10 #include "mozilla/Preferences.h"
11 #include "mozilla/StaticPrefs_security.h"
12 #include "mozilla/Unused.h"
13 #include "mozpkix/pkixtypes.h"
15 #include "nsIInputStream.h"
16 #include "nsIX509CertDB.h"
17 #include "nsNetUtil.h"
18 #include "nsNSSCertHelper.h"
19 #include "nsNSSCertificate.h"
20 #include "nsNSSHelper.h"
21 #include "nsReadableUtils.h"
23 #include "nsThreadUtils.h"
25 #include "ScopedNSSTypes.h"
28 using namespace mozilla
;
29 extern LazyLogModule gPIPNSSLog
;
31 #define PIP_PKCS12_BUFFER_SIZE 2048
32 #define PIP_PKCS12_NOSMARTCARD_EXPORT 4
33 #define PIP_PKCS12_RESTORE_FAILED 5
34 #define PIP_PKCS12_BACKUP_FAILED 6
35 #define PIP_PKCS12_NSS_ERROR 7
37 nsPKCS12Blob::nsPKCS12Blob() : mUIContext(new PipUIContext()) {}
39 // Given a file handle, read a PKCS#12 blob from that file, decode it, and
40 // import the results into the internal database.
41 nsresult
nsPKCS12Blob::ImportFromFile(nsIFile
* aFile
,
42 const nsAString
& aPassword
,
44 uint32_t passwordBufferLength
;
45 UniquePtr
<uint8_t[]> passwordBuffer
;
47 UniquePK11SlotInfo
slot(PK11_GetInternalKeySlot());
49 return NS_ERROR_FAILURE
;
52 passwordBuffer
= stringToBigEndianBytes(aPassword
, passwordBufferLength
);
54 // initialize the decoder
55 SECItem unicodePw
= {siBuffer
, passwordBuffer
.get(), passwordBufferLength
};
56 UniqueSEC_PKCS12DecoderContext
dcx(
57 SEC_PKCS12DecoderStart(&unicodePw
, slot
.get(), nullptr, nullptr, nullptr,
58 nullptr, nullptr, nullptr));
60 return NS_ERROR_FAILURE
;
62 // read input aFile and feed it to the decoder
64 nsresult rv
= inputToDecoder(dcx
, aFile
, nssError
);
69 aError
= handlePRErrorCode(nssError
);
73 SECStatus srv
= SEC_PKCS12DecoderVerify(dcx
.get());
74 if (srv
!= SECSuccess
) {
75 aError
= handlePRErrorCode(PR_GetError());
79 srv
= SEC_PKCS12DecoderValidateBags(dcx
.get(), nicknameCollision
);
80 if (srv
!= SECSuccess
) {
81 aError
= handlePRErrorCode(PR_GetError());
84 // import cert and key
85 srv
= SEC_PKCS12DecoderImportBags(dcx
.get());
86 if (srv
!= SECSuccess
) {
87 aError
= handlePRErrorCode(PR_GetError());
90 aError
= nsIX509CertDB::Success
;
94 static bool isExtractable(UniqueSECKEYPrivateKey
& privKey
) {
95 ScopedAutoSECItem value
;
96 SECStatus rv
= PK11_ReadRawAttribute(PK11_TypePrivKey
, privKey
.get(),
97 CKA_EXTRACTABLE
, &value
);
98 if (rv
!= SECSuccess
) {
102 bool isExtractable
= false;
103 if ((value
.len
== 1) && value
.data
) {
104 isExtractable
= !!(*(CK_BBOOL
*)value
.data
);
106 return isExtractable
;
109 // Having already loaded the certs, form them into a blob (loading the keys
110 // also), encode the blob, and stuff it into the file.
111 nsresult
nsPKCS12Blob::ExportToFile(nsIFile
* aFile
,
112 const nsTArray
<RefPtr
<nsIX509Cert
>>& aCerts
,
113 const nsAString
& aPassword
,
115 nsCString passwordUtf8
= NS_ConvertUTF16toUTF8(aPassword
);
116 uint32_t passwordBufferLength
= passwordUtf8
.Length();
117 aError
= nsIX509CertDB::Success
;
118 // The conversion to UCS2 is executed by sec_pkcs12_encode_password when
119 // necessary (for some older PKCS12 algorithms). The NSS 3.31 and newer
120 // expects password to be in the utf8 encoding to support modern encoders.
121 UniquePtr
<unsigned char[]> passwordBuffer(
122 reinterpret_cast<unsigned char*>(ToNewCString(passwordUtf8
)));
123 if (!passwordBuffer
.get()) {
126 UniqueSEC_PKCS12ExportContext
ecx(
127 SEC_PKCS12CreateExportContext(nullptr, nullptr, nullptr, nullptr));
129 aError
= nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED
;
132 bool useModernCrypto
=
133 StaticPrefs::security_pki_use_modern_crypto_with_pkcs12();
134 // add password integrity
135 SECItem unicodePw
= {siBuffer
, passwordBuffer
.get(), passwordBufferLength
};
136 SECStatus srv
= SEC_PKCS12AddPasswordIntegrity(
137 ecx
.get(), &unicodePw
, useModernCrypto
? SEC_OID_SHA256
: SEC_OID_SHA1
);
138 if (srv
!= SECSuccess
) {
139 aError
= nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED
;
142 for (auto& cert
: aCerts
) {
143 UniqueCERTCertificate
nssCert(cert
->GetCert());
145 aError
= nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED
;
148 // We can probably only successfully export certs that are on the internal
149 // token. Most, if not all, smart card vendors won't let you extract the
150 // private key (in any way shape or form) from the card. So let's punt if
151 // the cert is not in the internal db.
152 if (nssCert
->slot
&& !PK11_IsInternal(nssCert
->slot
)) {
153 // We aren't the internal token, see if the key is extractable.
154 UniqueSECKEYPrivateKey
privKey(
155 PK11_FindKeyByDERCert(nssCert
->slot
, nssCert
.get(), mUIContext
));
156 if (privKey
&& !isExtractable(privKey
)) {
157 // This is informative. If a serious error occurs later it will
158 // override it later and return.
159 aError
= nsIX509CertDB::ERROR_PKCS12_NOSMARTCARD_EXPORT
;
164 // certSafe and keySafe are owned by ecx.
165 SEC_PKCS12SafeInfo
* certSafe
;
166 SEC_PKCS12SafeInfo
* keySafe
= SEC_PKCS12CreateUnencryptedSafe(ecx
.get());
167 // We use SEC_OID_AES_128_CBC for the password and SEC_OID_AES_256_CBC
168 // for the certificate because it's a default for openssl an pk12util
170 if (!SEC_PKCS12IsEncryptionAllowed() || PK11_IsFIPS()) {
174 useModernCrypto
? SEC_OID_AES_128_CBC
175 : SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC
;
177 SEC_PKCS12CreatePasswordPrivSafe(ecx
.get(), &unicodePw
, privAlg
);
179 if (!certSafe
|| !keySafe
) {
180 aError
= nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED
;
183 // add the cert and key to the blob
184 SECOidTag algorithm
=
186 ? SEC_OID_AES_256_CBC
187 : SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC
;
188 srv
= SEC_PKCS12AddCertAndKey(ecx
.get(), certSafe
, nullptr, nssCert
.get(),
189 CERT_GetDefaultCertDB(), keySafe
, nullptr,
190 true, &unicodePw
, algorithm
);
191 if (srv
!= SECSuccess
) {
192 aError
= nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED
;
197 UniquePRFileDesc prFile
;
198 PRFileDesc
* rawPRFile
;
199 nsresult rv
= aFile
->OpenNSPRFileDesc(PR_RDWR
| PR_CREATE_FILE
| PR_TRUNCATE
,
201 if (NS_FAILED(rv
) || !rawPRFile
) {
202 aError
= nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED
;
205 prFile
.reset(rawPRFile
);
207 srv
= SEC_PKCS12Encode(ecx
.get(), writeExportFile
, prFile
.get());
208 if (srv
!= SECSuccess
) {
209 aError
= nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED
;
214 // For the NSS PKCS#12 library, must convert PRUnichars (shorts) to a buffer of
215 // octets. Must handle byte order correctly.
216 UniquePtr
<uint8_t[]> nsPKCS12Blob::stringToBigEndianBytes(
217 const nsAString
& uni
, uint32_t& bytesLength
) {
223 uint32_t wideLength
= uni
.Length() + 1; // +1 for the null terminator.
224 bytesLength
= wideLength
* 2;
225 auto buffer
= MakeUnique
<uint8_t[]>(bytesLength
);
227 // We have to use a cast here because on Windows, uni.get() returns
228 // char16ptr_t instead of char16_t*.
229 mozilla::NativeEndian::copyAndSwapToBigEndian(
230 buffer
.get(), static_cast<const char16_t
*>(uni
.BeginReading()),
236 // Given a decoder, read bytes from file and input them to the decoder.
237 nsresult
nsPKCS12Blob::inputToDecoder(UniqueSEC_PKCS12DecoderContext
& dcx
,
238 nsIFile
* file
, PRErrorCode
& nssError
) {
241 nsCOMPtr
<nsIInputStream
> fileStream
;
242 nsresult rv
= NS_NewLocalFileInputStream(getter_AddRefs(fileStream
), file
);
247 char buf
[PIP_PKCS12_BUFFER_SIZE
];
250 rv
= fileStream
->Read(buf
, PIP_PKCS12_BUFFER_SIZE
, &amount
);
254 // feed the file data into the decoder
256 SEC_PKCS12DecoderUpdate(dcx
.get(), (unsigned char*)buf
, amount
);
257 if (srv
!= SECSuccess
) {
258 nssError
= PR_GetError();
261 if (amount
< PIP_PKCS12_BUFFER_SIZE
) {
268 // What to do when the nickname collides with one already in the db.
269 SECItem
* nsPKCS12Blob::nicknameCollision(SECItem
* oldNick
, PRBool
* cancel
,
274 nsAutoString nickFromProp
;
275 nsresult rv
= GetPIPNSSBundleString("P12DefaultNickname", nickFromProp
);
279 NS_ConvertUTF16toUTF8
nickFromPropC(nickFromProp
);
280 // The user is trying to import a PKCS#12 file that doesn't have the
281 // attribute we use to set the nickname. So in order to reduce the
282 // number of interactions we require with the user, we'll build a nickname
283 // for the user. The nickname isn't prominently displayed in the UI,
284 // so it's OK if we generate one on our own here.
285 // XXX If the NSS API were smarter and actually passed a pointer to
286 // the CERTCertificate* we're importing we could actually just
287 // call default_nickname (which is what the issuance code path
288 // does) and come up with a reasonable nickname. Alas, the NSS
289 // API limits our ability to produce a useful nickname without
290 // bugging the user. :(
292 // If we've gotten this far, that means there isn't a certificate
293 // in the database that has the same subject name as the cert we're
294 // trying to import. So we need to come up with a "nickname" to
295 // satisfy the NSS requirement or fail in trying to import.
296 // Basically we use a default nickname from a properties file and
297 // see if a certificate exists with that nickname. If there isn't, then
298 // create update the count by one and append the string '#1' Or
299 // whatever the count currently is, and look for a cert with
300 // that nickname. Keep updating the count until we find a nickname
301 // without a corresponding cert.
302 // XXX If a user imports *many* certs without the 'friendly name'
303 // attribute, then this may take a long time. :(
304 nickname
= nickFromPropC
;
306 nickname
.AppendPrintf(" #%d", count
);
308 UniqueCERTCertificate
cert(
309 CERT_FindCertByNickname(CERT_GetDefaultCertDB(), nickname
.get()));
315 UniqueSECItem
newNick(
316 SECITEM_AllocItem(nullptr, nullptr, nickname
.Length() + 1));
320 memcpy(newNick
->data
, nickname
.get(), nickname
.Length());
321 newNick
->data
[nickname
.Length()] = 0;
323 return newNick
.release();
326 // write bytes to the exported PKCS#12 file
327 void nsPKCS12Blob::writeExportFile(void* arg
, const char* buf
,
329 PRFileDesc
* file
= static_cast<PRFileDesc
*>(arg
);
330 MOZ_RELEASE_ASSERT(file
);
331 PR_Write(file
, buf
, len
);
334 // Translate PRErrorCode to nsIX509CertDB error
335 uint32_t nsPKCS12Blob::handlePRErrorCode(PRErrorCode aPrerr
) {
336 MOZ_ASSERT(aPrerr
!= 0);
337 uint32_t error
= nsIX509CertDB::ERROR_UNKNOWN
;
339 case SEC_ERROR_PKCS12_CERT_COLLISION
:
340 error
= nsIX509CertDB::ERROR_PKCS12_DUPLICATE_DATA
;
342 // INVALID_ARGS is returned on bad password when importing cert
343 // exported from firefox or generated by openssl
344 case SEC_ERROR_INVALID_ARGS
:
345 case SEC_ERROR_BAD_PASSWORD
:
346 error
= nsIX509CertDB::ERROR_BAD_PASSWORD
;
348 case SEC_ERROR_BAD_DER
:
349 case SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE
:
350 case SEC_ERROR_PKCS12_INVALID_MAC
:
351 error
= nsIX509CertDB::ERROR_DECODE_ERROR
;
353 case SEC_ERROR_PKCS12_DUPLICATE_DATA
:
354 error
= nsIX509CertDB::ERROR_PKCS12_DUPLICATE_DATA
;