Bug 1399867, pkcs12: Add a compat option for password encoding, r=rrelyea
authorDaiki Ueno <dueno@redhat.com>
Thu, 14 Sep 2017 14:42:48 +0200
changeset 13599 222ed4d2deb63f5977aef6da3e37d8a24bff35b4
parent 13598 90466c1d10ccab654543ba26958e12c15d987db5
child 13600 835f791a0918bea762acdb606fff8b50c9aadac7
push id2383
push userkaie@kuix.de
push dateWed, 20 Sep 2017 10:06:29 +0000
reviewersrrelyea
bugs1399867
Bug 1399867, pkcs12: Add a compat option for password encoding, r=rrelyea
lib/nss/nss.h
lib/nss/nssoptions.c
lib/pkcs12/p12d.c
--- a/lib/nss/nss.h
+++ b/lib/nss/nss.h
@@ -286,16 +286,25 @@ SECStatus NSS_UnregisterShutdown(NSS_Shu
 #define NSS_RSA_MIN_KEY_SIZE 0x001
 #define NSS_DH_MIN_KEY_SIZE 0x002
 #define NSS_DSA_MIN_KEY_SIZE 0x004
 #define NSS_TLS_VERSION_MIN_POLICY 0x008
 #define NSS_TLS_VERSION_MAX_POLICY 0x009
 #define NSS_DTLS_VERSION_MIN_POLICY 0x00a
 #define NSS_DTLS_VERSION_MAX_POLICY 0x00b
 
+/* Until NSS 3.30, the PKCS#12 implementation used BMPString encoding
+ * for all passwords.  This changed to use UTF-8 for non-PKCS#12 PBEs
+ * in NSS 3.31.
+ *
+ * For backward compatibility, this option reverts the behavior to the
+ * old NSS versions.  This option might be removed in the future NSS
+ * releases; don't rely on it. */
+#define __NSS_PKCS12_DECODE_FORCE_UNICODE 0x00c
+
 /*
  * Set and get global options for the NSS library.
  */
 SECStatus NSS_OptionSet(PRInt32 which, PRInt32 value);
 SECStatus NSS_OptionGet(PRInt32 which, PRInt32 *value);
 
 /*
  * Close the Cert, Key databases.
--- a/lib/nss/nssoptions.c
+++ b/lib/nss/nssoptions.c
@@ -18,26 +18,28 @@
 struct nssOps {
     PRInt32 rsaMinKeySize;
     PRInt32 dhMinKeySize;
     PRInt32 dsaMinKeySize;
     PRInt32 tlsVersionMinPolicy;
     PRInt32 tlsVersionMaxPolicy;
     PRInt32 dtlsVersionMinPolicy;
     PRInt32 dtlsVersionMaxPolicy;
+    PRInt32 pkcs12DecodeForceUnicode;
 };
 
 static struct nssOps nss_ops = {
     SSL_RSA_MIN_MODULUS_BITS,
     SSL_DH_MIN_P_BITS,
     SSL_DSA_MIN_P_BITS,
     1,      /* Set TLS min to less the the smallest legal SSL value */
     0xffff, /* set TLS max to more than the largest legal SSL value */
     1,
     0xffff,
+    PR_FALSE
 };
 
 SECStatus
 NSS_OptionSet(PRInt32 which, PRInt32 value)
 {
     SECStatus rv = SECSuccess;
 
     switch (which) {
@@ -57,16 +59,19 @@ NSS_OptionSet(PRInt32 which, PRInt32 val
             nss_ops.tlsVersionMaxPolicy = value;
             break;
         case NSS_DTLS_VERSION_MIN_POLICY:
             nss_ops.dtlsVersionMinPolicy = value;
             break;
         case NSS_DTLS_VERSION_MAX_POLICY:
             nss_ops.dtlsVersionMaxPolicy = value;
             break;
+        case __NSS_PKCS12_DECODE_FORCE_UNICODE:
+            nss_ops.pkcs12DecodeForceUnicode = value;
+            break;
         default:
             rv = SECFailure;
     }
 
     return rv;
 }
 
 SECStatus
@@ -91,14 +96,17 @@ NSS_OptionGet(PRInt32 which, PRInt32 *va
             *value = nss_ops.tlsVersionMaxPolicy;
             break;
         case NSS_DTLS_VERSION_MIN_POLICY:
             *value = nss_ops.dtlsVersionMinPolicy;
             break;
         case NSS_DTLS_VERSION_MAX_POLICY:
             *value = nss_ops.dtlsVersionMaxPolicy;
             break;
+        case __NSS_PKCS12_DECODE_FORCE_UNICODE:
+            *value = nss_ops.pkcs12DecodeForceUnicode;
+            break;
         default:
             rv = SECFailure;
     }
 
     return rv;
 }
--- a/lib/pkcs12/p12d.c
+++ b/lib/pkcs12/p12d.c
@@ -1,13 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nssrenam.h"
+#include "nss.h"
 #include "p12t.h"
 #include "p12.h"
 #include "plarena.h"
 #include "secitem.h"
 #include "secoid.h"
 #include "seccomon.h"
 #include "secport.h"
 #include "cert.h"
@@ -121,16 +122,17 @@ struct SEC_PKCS12DecoderContextStr {
     digestIOFn dRead, dWrite;
     void *dArg;
     PRBool dIsOpen; /* is the temp file created? */
 
     /* helper functions */
     SECKEYGetPasswordKey pwfn;
     void *pwfnarg;
     PRBool swapUnicodeBytes;
+    PRBool forceUnicode;
 
     /* import information */
     PRBool bagsVerified;
 
     /* buffer management for the default callbacks implementation */
     void *buffer;       /* storage area */
     PRInt32 filesize;   /* actual data size */
     PRInt32 allocated;  /* total buffer size allocated */
@@ -187,18 +189,28 @@ sec_pkcs12_decoder_get_decrypt_key(void 
     /* if no slot specified, use the internal key slot */
     if (p12dcx->slot) {
         slot = PK11_ReferenceSlot(p12dcx->slot);
     } else {
         slot = PK11_GetInternalKeySlot();
     }
 
     algorithm = SECOID_GetAlgorithmTag(algid);
-    if (!sec_pkcs12_decode_password(NULL, &pwitem, algorithm, p12dcx->pwitem))
-        return NULL;
+
+    if (p12dcx->forceUnicode) {
+        if (SECITEM_CopyItem(NULL, &pwitem, p12dcx->pwitem) != SECSuccess) {
+            PK11_FreeSlot(slot);
+            return NULL;
+        }
+    } else {
+        if (!sec_pkcs12_decode_password(NULL, &pwitem, algorithm, p12dcx->pwitem)) {
+            PK11_FreeSlot(slot);
+            return NULL;
+        }
+    }
 
     bulkKey = PK11_PBEKeyGen(slot, algid, &pwitem, PR_FALSE, p12dcx->wincx);
     /* some tokens can't generate PBE keys on their own, generate the
      * key in the internal slot, and let the Import code deal with it,
      * (if the slot can't generate PBEs, then we need to use the internal
      * slot anyway to unwrap). */
     if (!bulkKey && !PK11_IsInternal(slot)) {
         PK11_FreeSlot(slot);
@@ -1159,16 +1171,18 @@ p12u_DigestWrite(void *arg, unsigned cha
  */
 SEC_PKCS12DecoderContext *
 SEC_PKCS12DecoderStart(SECItem *pwitem, PK11SlotInfo *slot, void *wincx,
                        digestOpenFn dOpen, digestCloseFn dClose,
                        digestIOFn dRead, digestIOFn dWrite, void *dArg)
 {
     SEC_PKCS12DecoderContext *p12dcx;
     PLArenaPool *arena;
+    PRInt32 forceUnicode = PR_FALSE;
+    SECStatus rv;
 
     arena = PORT_NewArena(2048); /* different size? */
     if (!arena) {
         return NULL; /* error is already set */
     }
 
     /* allocate the decoder context and set the state variables */
     p12dcx = PORT_ArenaZNew(arena, SEC_PKCS12DecoderContext);
@@ -1191,16 +1205,21 @@ SEC_PKCS12DecoderStart(SECItem *pwitem, 
                          : PK11_GetInternalKeySlot());
     p12dcx->wincx = wincx;
     p12dcx->tokenCAs = SECPKCS12TargetTokenNoCAs;
 #ifdef IS_LITTLE_ENDIAN
     p12dcx->swapUnicodeBytes = PR_TRUE;
 #else
     p12dcx->swapUnicodeBytes = PR_FALSE;
 #endif
+    rv = NSS_OptionGet(__NSS_PKCS12_DECODE_FORCE_UNICODE, &forceUnicode);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    p12dcx->forceUnicode = forceUnicode;
     p12dcx->errorValue = 0;
     p12dcx->error = PR_FALSE;
 
     /* start the decoding of the PFX and set the notify proc
      * for the PFX item.
      */
     p12dcx->pfxA1Dcx = SEC_ASN1DecoderStart(p12dcx->arena, &p12dcx->pfx,
                                             sec_PKCS12PFXItemTemplate);
@@ -2423,17 +2442,17 @@ sec_pkcs12_add_cert(sec_PKCS12SafeBag *c
 }
 
 static SECItem *
 sec_pkcs12_get_public_value_and_type(SECKEYPublicKey *pubKey, KeyType *type);
 
 static SECStatus
 sec_pkcs12_add_key(sec_PKCS12SafeBag *key, SECKEYPublicKey *pubKey,
                    unsigned int keyUsage,
-                   SECItem *nickName, void *wincx)
+                   SECItem *nickName, PRBool forceUnicode, void *wincx)
 {
     SECStatus rv;
     SECItem *publicValue = NULL;
     KeyType keyType;
 
     /* We should always have values for "key" and "pubKey"
        so they can be dereferenced later. */
     if (!key || !pubKey) {
@@ -2461,19 +2480,31 @@ sec_pkcs12_add_key(sec_PKCS12SafeBag *ke
                                            keyUsage, wincx);
             break;
         case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID: {
             SECItem pwitem = { 0 };
             SECAlgorithmID *algid =
                 &key->safeBagContent.pkcs8ShroudedKeyBag->algorithm;
             SECOidTag algorithm = SECOID_GetAlgorithmTag(algid);
 
-            if (!sec_pkcs12_decode_password(NULL, &pwitem, algorithm,
-                                            key->pwitem))
-                return SECFailure;
+            if (forceUnicode) {
+                if (SECITEM_CopyItem(NULL, &pwitem, key->pwitem) != SECSuccess) {
+                    key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY;
+                    key->problem = PR_TRUE;
+                    return SECFailure;
+                }
+            } else {
+                if (!sec_pkcs12_decode_password(NULL, &pwitem, algorithm,
+                                                key->pwitem)) {
+                    key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY;
+                    key->problem = PR_TRUE;
+                    return SECFailure;
+                }
+            }
+
             rv = PK11_ImportEncryptedPrivateKeyInfo(key->slot,
                                                     key->safeBagContent.pkcs8ShroudedKeyBag,
                                                     &pwitem, nickName, publicValue,
                                                     PR_TRUE, PR_TRUE, keyType, keyUsage,
                                                     wincx);
             if (pwitem.data) {
                 SECITEM_ZfreeItem(&pwitem, PR_FALSE);
             }
@@ -2918,17 +2949,18 @@ sec_pkcs12_get_public_value_and_type(SEC
     return pubValue;
 }
 
 /* This function takes two passes over the bags, installing them in the
  * desired slot.  The two passes are intended to mirror exactly the
  * two passes in sec_pkcs12_validate_bags.
  */
 static SECStatus
-sec_pkcs12_install_bags(sec_PKCS12SafeBag **safeBags, void *wincx)
+sec_pkcs12_install_bags(sec_PKCS12SafeBag **safeBags, PRBool forceUnicode,
+                        void *wincx)
 {
     sec_PKCS12SafeBag **keyList;
     int i;
     int failedKeys = 0;
 
     if (!safeBags) {
         PORT_SetError(SEC_ERROR_INVALID_ARGS);
         return SECFailure;
@@ -2971,17 +3003,18 @@ sec_pkcs12_install_bags(sec_PKCS12SafeBa
                 key->error = SEC_ERROR_BAD_NICKNAME;
                 key->problem = PR_TRUE;
                 rv = SECFailure;
             } else if (!pubKey) {
                 key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY;
                 key->problem = PR_TRUE;
                 rv = SECFailure;
             } else {
-                rv = sec_pkcs12_add_key(key, pubKey, keyUsage, nickName, wincx);
+                rv = sec_pkcs12_add_key(key, pubKey, keyUsage, nickName,
+                                        forceUnicode, wincx);
             }
             if (pubKey) {
                 SECKEY_DestroyPublicKey(pubKey);
                 pubKey = NULL;
             }
             if (nickName) {
                 SECITEM_FreeItem(nickName, PR_TRUE);
                 nickName = NULL;
@@ -3048,26 +3081,38 @@ sec_pkcs12_install_bags(sec_PKCS12SafeBa
     }
 
     return SECSuccess;
 }
 
 SECStatus
 SEC_PKCS12DecoderImportBags(SEC_PKCS12DecoderContext *p12dcx)
 {
+    PRBool forceUnicode = PR_FALSE;
+    SECStatus rv;
+
     if (!p12dcx || p12dcx->error) {
         PORT_SetError(SEC_ERROR_INVALID_ARGS);
         return SECFailure;
     }
 
     if (!p12dcx->bagsVerified) {
         return SECFailure;
     }
 
-    return sec_pkcs12_install_bags(p12dcx->safeBags, p12dcx->wincx);
+    /* We need to check the option here as well as in
+     * SEC_PKCS12DecoderStart, because different PBE's could be used
+     * for PKCS #7 and PKCS #8 */
+    rv = NSS_OptionGet(__NSS_PKCS12_DECODE_FORCE_UNICODE, &forceUnicode);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    return sec_pkcs12_install_bags(p12dcx->safeBags, forceUnicode,
+                                   p12dcx->wincx);
 }
 
 PRBool
 sec_pkcs12_bagHasKey(SEC_PKCS12DecoderContext *p12dcx, sec_PKCS12SafeBag *bag)
 {
     int i;
     SECItem *keyId;
     SECItem *certKeyId;