tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsPKCS12Blob.cpp (12872B)


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