[Pki-devel] [PATCH] pki-0178, jss-0000..0002 - PKCS #12 key bag AES encryption

Fraser Tweedale ftweedal at redhat.com
Tue Apr 4 09:56:06 UTC 2017


Hi team,

Please review attached patches for JSS and Dogtag that:

- add some new EncryptedPrivateKeyInfo export and import functions
  to JSS

- update Dogtag's `pki pkcs12' command to use the new functions to
  achieve AES encryption of the key bags, with wrapping/unwrapping
  occurring on the token.

PKCS #12 files produced by current releases continue to import
properly (of course, this is an important test vector).

These patches do not address the PKCS #12 KRA recovery export; This
is my next task and separate patches will be produced.

Thanks,
Fraser
-------------- next part --------------
From de2d7f049eb4462c7442795a77a8a915ae70d216 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Mon, 3 Apr 2017 11:07:24 +1000
Subject: [PATCH 0/2] Add SEC_OID mappings for AES ECB/CBC algorithms

---
 org/mozilla/jss/crypto/Algorithm.c              |  8 +++++++-
 org/mozilla/jss/crypto/Algorithm.h              |  2 +-
 org/mozilla/jss/crypto/Algorithm.java           |  8 ++++++++
 org/mozilla/jss/crypto/EncryptionAlgorithm.java | 18 ++++++++++++------
 4 files changed, 28 insertions(+), 8 deletions(-)

diff --git a/org/mozilla/jss/crypto/Algorithm.c b/org/mozilla/jss/crypto/Algorithm.c
index 8679eadca573fdb2bc7903c3e5d0a1a05d4bbd2f..d32bcad469c45c9edcdd5bedfa5e98f2fab0e3a2 100644
--- a/org/mozilla/jss/crypto/Algorithm.c
+++ b/org/mozilla/jss/crypto/Algorithm.c
@@ -86,7 +86,13 @@ JSS_AlgInfo JSS_AlgTable[NUM_ALGS] = {
 /* 55 */    {SEC_OID_PKCS5_PBMAC1, SEC_OID_TAG},
 /* 56 */    {SEC_OID_ANSIX962_ECDSA_SIGNATURE_SPECIFIED_DIGEST, SEC_OID_TAG},
 /* 57 */    {CKM_NSS_AES_KEY_WRAP, PK11_MECH},
-/* 58 */    {CKM_NSS_AES_KEY_WRAP_PAD, PK11_MECH}
+/* 58 */    {CKM_NSS_AES_KEY_WRAP_PAD, PK11_MECH},
+/* 59 */    {SEC_OID_AES_128_ECB, SEC_OID_TAG},
+/* 60 */    {SEC_OID_AES_128_CBC, SEC_OID_TAG},
+/* 61 */    {SEC_OID_AES_192_ECB, SEC_OID_TAG},
+/* 62 */    {SEC_OID_AES_192_CBC, SEC_OID_TAG},
+/* 63 */    {SEC_OID_AES_256_ECB, SEC_OID_TAG},
+/* 64 */    {SEC_OID_AES_256_CBC, SEC_OID_TAG}
 /* REMEMBER TO UPDATE NUM_ALGS!!! */
 };
 
diff --git a/org/mozilla/jss/crypto/Algorithm.h b/org/mozilla/jss/crypto/Algorithm.h
index ec2dddb76e66187fce29051069d84293315199f0..c18623185184590799c3c2e0f0627579661051f7 100644
--- a/org/mozilla/jss/crypto/Algorithm.h
+++ b/org/mozilla/jss/crypto/Algorithm.h
@@ -24,7 +24,7 @@ typedef struct JSS_AlgInfoStr {
     JSS_AlgType type;
 } JSS_AlgInfo;
 
-#define NUM_ALGS 59
+#define NUM_ALGS 65
 
 extern JSS_AlgInfo JSS_AlgTable[];
 extern CK_ULONG JSS_symkeyUsage[];
diff --git a/org/mozilla/jss/crypto/Algorithm.java b/org/mozilla/jss/crypto/Algorithm.java
index 919c2ece0608418015a2f05e7c363cdd70a2b16a..1818bd4703b8d55ae81a64d468a5ade890b21382 100644
--- a/org/mozilla/jss/crypto/Algorithm.java
+++ b/org/mozilla/jss/crypto/Algorithm.java
@@ -212,4 +212,12 @@ public class Algorithm {
     protected static final short SEC_OID_ANSIX962_ECDSA_SIGNATURE_SPECIFIED_DIGEST=56;
     protected static final short CKM_NSS_AES_KEY_WRAP=57;
     protected static final short CKM_NSS_AES_KEY_WRAP_PAD=58;
+
+    // AES Encryption Algorithms
+    protected static final short SEC_OID_AES_128_ECB = 59;
+    protected static final short SEC_OID_AES_128_CBC = 60;
+    protected static final short SEC_OID_AES_192_ECB = 61;
+    protected static final short SEC_OID_AES_192_CBC = 62;
+    protected static final short SEC_OID_AES_256_ECB = 63;
+    protected static final short SEC_OID_AES_256_CBC = 64;
 }
diff --git a/org/mozilla/jss/crypto/EncryptionAlgorithm.java b/org/mozilla/jss/crypto/EncryptionAlgorithm.java
index db10305c14f7c5d75920624c1243feae09b0c92a..8e389b47035d51f073a9005756aed0cde915e024 100644
--- a/org/mozilla/jss/crypto/EncryptionAlgorithm.java
+++ b/org/mozilla/jss/crypto/EncryptionAlgorithm.java
@@ -347,12 +347,14 @@ public class EncryptionAlgorithm extends Algorithm {
             { 2, 16, 840, 1, 101, 3, 4, 1 } );
 
     public static final EncryptionAlgorithm
-    AES_128_ECB = new EncryptionAlgorithm(CKM_AES_ECB, Alg.AES, Mode.ECB,
+    AES_128_ECB = new EncryptionAlgorithm(SEC_OID_AES_128_ECB,
+        Alg.AES, Mode.ECB,
         Padding.NONE, (Class)null, 16,
         AES_ROOT_OID.subBranch(1), 128);
 
     public static final EncryptionAlgorithm
-    AES_128_CBC = new EncryptionAlgorithm(CKM_AES_CBC, Alg.AES, Mode.CBC,
+    AES_128_CBC = new EncryptionAlgorithm(SEC_OID_AES_128_CBC,
+        Alg.AES, Mode.CBC,
         Padding.NONE, IVParameterSpecClasses, 16,
         AES_ROOT_OID.subBranch(2), 128);
 
@@ -361,11 +363,13 @@ public class EncryptionAlgorithm extends Algorithm {
         Padding.PKCS5, IVParameterSpecClasses, 16, null, 128); // no oid
     
     public static final EncryptionAlgorithm
-    AES_192_ECB = new EncryptionAlgorithm(CKM_AES_ECB, Alg.AES, Mode.ECB,
+    AES_192_ECB = new EncryptionAlgorithm(SEC_OID_AES_192_ECB,
+        Alg.AES, Mode.ECB,
         Padding.NONE, (Class)null, 16, AES_ROOT_OID.subBranch(21), 192);
 
     public static final EncryptionAlgorithm
-    AES_192_CBC = new EncryptionAlgorithm(CKM_AES_CBC, Alg.AES, Mode.CBC,
+    AES_192_CBC = new EncryptionAlgorithm(SEC_OID_AES_192_CBC,
+        Alg.AES, Mode.CBC,
         Padding.NONE, IVParameterSpecClasses, 16,
         AES_ROOT_OID.subBranch(22), 192);
     
@@ -374,11 +378,13 @@ public class EncryptionAlgorithm extends Algorithm {
         Padding.PKCS5, IVParameterSpecClasses, 16, null, 192); // no oid
 
     public static final EncryptionAlgorithm
-    AES_256_ECB = new EncryptionAlgorithm(CKM_AES_ECB, Alg.AES, Mode.ECB,
+    AES_256_ECB = new EncryptionAlgorithm(SEC_OID_AES_256_ECB,
+        Alg.AES, Mode.ECB,
         Padding.NONE, (Class)null, 16, AES_ROOT_OID.subBranch(41), 256);
 
     public static final EncryptionAlgorithm
-    AES_256_CBC = new EncryptionAlgorithm(CKM_AES_CBC, Alg.AES, Mode.CBC,
+    AES_256_CBC = new EncryptionAlgorithm(SEC_OID_AES_256_CBC,
+        Alg.AES, Mode.CBC,
         Padding.NONE, IVParameterSpecClasses, 16,
         AES_ROOT_OID.subBranch(42), 256);
     
-- 
2.9.3

-------------- next part --------------
From 85f014d639b669e20b7ee56a8fd908022b478c4a Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Tue, 4 Apr 2017 12:05:49 +1000
Subject: [PATCH 1/2] Add InvalidDERException to jss_exceptions.h

---
 org/mozilla/jss/util/jss_exceptions.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/org/mozilla/jss/util/jss_exceptions.h b/org/mozilla/jss/util/jss_exceptions.h
index eb4e4cfa0367aba5081b6cea2237b0069bdde3a9..d4fbe97400faacff73d973a9940fd6c83555f2c7 100644
--- a/org/mozilla/jss/util/jss_exceptions.h
+++ b/org/mozilla/jss/util/jss_exceptions.h
@@ -47,6 +47,8 @@ PR_BEGIN_EXTERN_C
 
 #define INTERRUPTED_IO_EXCEPTION "java/io/InterruptedIOException"
 
+#define INVALID_DER_EXCEPTION "org/mozilla/jss/crypto/InvalidDERException"
+
 #define INVALID_NICKNAME_EXCEPTION "org/mozilla/jss/util/InvalidNicknameException"
 
 #define INVALID_KEY_FORMAT_EXCEPTION "org/mozilla/jss/crypto/InvalidKeyFormatException"
-- 
2.9.3

-------------- next part --------------
From 59ebce62d1f9b19f211f35f6e41c5b956588273f Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Thu, 23 Mar 2017 13:28:21 +1100
Subject: [PATCH 2/2] Add methods for importing and exporting
 EncryptedPrivateKeyInfo

Add a second variant of CryptoStore.getEncryptedPrivateKeyInfo that
allows more control over the encryption algorithm and how the
password gets converted to bytes.  (The existing method is changed
from a native method to pure Java, calling the generalised native
method).

Also add method CryptoStore.importEncryptedPrivateKeyInfo that
imports an EncryptedPrivateKeyInfo.

Related to: https://pagure.io/dogtagpki/issue/2610
---
 lib/jss.def                             |   1 +
 org/mozilla/jss/crypto/CryptoStore.java |  46 ++++-
 org/mozilla/jss/pkcs11/PK11Store.c      | 332 ++++++++++++++++++++++++--------
 org/mozilla/jss/pkcs11/PK11Store.java   |  33 +++-
 4 files changed, 324 insertions(+), 88 deletions(-)

diff --git a/lib/jss.def b/lib/jss.def
index 2f371ac9e32afa31e3bf0f5118dbcac62ab8cc4e..313d26c67442f7088d19f13164a4f721154342a0 100644
--- a/lib/jss.def
+++ b/lib/jss.def
@@ -193,6 +193,7 @@ Java_org_mozilla_jss_util_Password_readPasswordFromConsole;
 ;+    global:
 Java_org_mozilla_jss_pkcs11_PK11KeyWrapper_nativeUnwrapSymPlaintext;
 Java_org_mozilla_jss_pkcs11_PK11Store_getEncryptedPrivateKeyInfo;
+Java_org_mozilla_jss_pkcs11_PK11Store_importEncryptedPrivateKeyInfo;
 ;+    local:
 ;+       *;
 ;+};
diff --git a/org/mozilla/jss/crypto/CryptoStore.java b/org/mozilla/jss/crypto/CryptoStore.java
index a4fe7cf87a8e7530484ffbfe54ab483a3e8632b7..213df9aa37b29d47bbf4135106b412d3e59b6ba6 100644
--- a/org/mozilla/jss/crypto/CryptoStore.java
+++ b/org/mozilla/jss/crypto/CryptoStore.java
@@ -4,6 +4,7 @@
 
 package org.mozilla.jss.crypto;
 
+import org.mozilla.jss.CryptoManager;
 import org.mozilla.jss.util.*;
 import java.security.*;
 import java.security.cert.CertificateEncodingException;
@@ -68,9 +69,50 @@ public interface CryptoStore {
     public void deletePrivateKey(org.mozilla.jss.crypto.PrivateKey key)
         throws NoSuchItemOnTokenException, TokenException;
 
-
+    /**
+     * Get an encrypted private key for the given cert.
+     *
+     * @param cert Certificate of key to be exported
+     * @param pbeAlg The PBEAlgorithm to use
+     * @param pw The password to encrypt with
+     * @param iteration Iteration count; default of 2000 if le 0
+     */
     public byte[] getEncryptedPrivateKeyInfo(X509Certificate cert,
-        PBEAlgorithm pbeAlg, Password pw, int iteration);
+        PBEAlgorithm pbeAlg, Password pw, int iteration)
+        throws CryptoManager.NotInitializedException,
+            ObjectNotFoundException, TokenException;
+
+    /**
+     * Get an encrypted private key, with optional password
+     * conversion.
+     *
+     * @param conv Password converter.  If null, pw.getByteCopy()
+     *             will be used to get password bytes.
+     * @param pw The password
+     * @param alg The encryption algorithm
+     * @param n Iteration count; default of 2000 if le 0
+     * @param k The private key
+     */
+    public byte[] getEncryptedPrivateKeyInfo(
+        KeyGenerator.CharToByteConverter conv,
+        Password pw,
+        Algorithm alg,
+        int n,
+        PrivateKey k);
+
+    /**
+     * @param conv Password converter.  If null, pw.getByteCopy()
+     *             will be used to get password bytes.
+     * @param pw The password
+     * @param nickname Nickname to use for private key
+     * @param pubKey Public key corresponding to private key
+     */
+    public void importEncryptedPrivateKeyInfo(
+        KeyGenerator.CharToByteConverter conv,
+        Password pw,
+        String nickname,
+        PublicKey pubKey,
+        byte[] epkiBytes);
 
     ////////////////////////////////////////////////////////////
     // Certs
diff --git a/org/mozilla/jss/pkcs11/PK11Store.c b/org/mozilla/jss/pkcs11/PK11Store.c
index 7afa29786ea0917bce8981e168d1cc4efb5743bb..9285a0f5d0e0269150fdebdea3eead338573c18b 100644
--- a/org/mozilla/jss/pkcs11/PK11Store.c
+++ b/org/mozilla/jss/pkcs11/PK11Store.c
@@ -31,6 +31,8 @@ typedef struct
     char *data;
 } secuPWData;
 
+SECItem *preparePassword(JNIEnv *env, jobject conv, jobject pwObj);
+
 /**********************************************************************
  * PK11Store.putSymKeysInVector
  */
@@ -533,103 +535,265 @@ Java_org_mozilla_jss_pkcs11_PK11Store_importPrivateKey
 
 
 JNIEXPORT jbyteArray JNICALL
-Java_org_mozilla_jss_pkcs11_PK11Store_getEncryptedPrivateKeyInfo
-(JNIEnv *env, jobject this, jobject certObj, jobject algObj,
-    jobject pwObj, jint iteration)
-
+Java_org_mozilla_jss_pkcs11_PK11Store_getEncryptedPrivateKeyInfo(
+    JNIEnv *env,
+    jobject this,
+    jobject conv,
+    jobject pwObj,
+    jobject algObj,
+    jint iterations,
+    jobject key)
 {
-    SECKEYEncryptedPrivateKeyInfo *epki = NULL;
-    jbyteArray encodedEpki = NULL;
+    if (iterations <= 0) {
+        iterations = 2000;  // set default iterations
+    }
+
+    // get slot
     PK11SlotInfo *slot = NULL;
-    SECOidTag algTag;
-    jclass passwordClass = NULL;
-    jmethodID getByteCopyMethod = NULL;
-    jbyteArray pwArray = NULL;
-    jbyte* pwchars = NULL;
-    SECItem pwItem;
-    CERTCertificate *cert = NULL;
+    if( JSS_PK11_getStoreSlotPtr(env, this, &slot) != PR_SUCCESS) {
+        ASSERT_OUTOFMEM(env);
+        goto finish;
+    }
+    PR_ASSERT(slot!=NULL);
+
+    // get algorithm
+    SECOidTag algTag = JSS_getOidTagFromAlg(env, algObj);
+    if (algTag == SEC_OID_UNKNOWN) {
+        JSS_throwMsg(env, NO_SUCH_ALG_EXCEPTION, "Unrecognized algorithm");
+        goto finish;
+    }
+
+    SECItem *pwItem = preparePassword(env, conv, pwObj);
+    if (pwItem == NULL) {
+        ASSERT_OUTOFMEM(env);
+        goto finish;
+    }
+
+    // get key
+    SECKEYPrivateKey *privk;
+    if (JSS_PK11_getPrivKeyPtr(env, key, &privk) != PR_SUCCESS) {
+        PR_ASSERT( (*env)->ExceptionOccurred(env) != NULL);
+        goto finish;
+    }
+
+    // export the epki
+    SECKEYEncryptedPrivateKeyInfo *epki = PK11_ExportEncryptedPrivKeyInfo(
+        slot, algTag, pwItem, privk, iterations, NULL /*wincx*/);
+
+    // DER-encode the epki
     SECItem epkiItem;
-
-    epkiItem.data = NULL;
-
-    /* get slot */
-    if( JSS_PK11_getStoreSlotPtr(env, this, &slot) != PR_SUCCESS) {
-        ASSERT_OUTOFMEM(env);
-        goto finish;
-    }
-    PR_ASSERT(slot!=NULL);
-    
-    /* get algorithm */
-    algTag = JSS_getOidTagFromAlg(env, algObj);
-    if( algTag == SEC_OID_UNKNOWN ) {
-        JSS_throwMsg(env, NO_SUCH_ALG_EXCEPTION, "Unrecognized PBE algorithm");
-        goto finish;
-    }
-
-    /*
-     * get password
-     */
-    passwordClass = (*env)->GetObjectClass(env, pwObj);
-    if(passwordClass == NULL) {
-        ASSERT_OUTOFMEM(env);
-        goto finish;
-    }
-    getByteCopyMethod = (*env)->GetMethodID(
-                                            env,
-                                            passwordClass,
-                                            PW_GET_BYTE_COPY_NAME,
-                                            PW_GET_BYTE_COPY_SIG);
-    if(getByteCopyMethod==NULL) {
-        ASSERT_OUTOFMEM(env);
-        goto finish;
-    }
-    pwArray = (*env)->CallObjectMethod( env, pwObj, getByteCopyMethod);
-    pwchars = (*env)->GetByteArrayElements(env, pwArray, NULL);
-    /* !!! Include the NULL byte or not? */
-    pwItem.data = (unsigned char*) pwchars;
-    pwItem.len = strlen((const char*)pwchars) + 1;
-
-    /*
-     * get cert
-     */
-    if( JSS_PK11_getCertPtr(env, certObj, &cert) != PR_SUCCESS ) {
-        /* exception was thrown */
-        goto finish;
-    }
-
-    /*
-     * export the epki
-     */
-    epki = PK11_ExportEncryptedPrivateKeyInfo(slot, algTag, &pwItem,
-            cert, iteration, NULL /*wincx*/);
-
-
-    /*
-     * DER-encode the epki
-     */
     epkiItem.data = NULL;
     epkiItem.len = 0;
-    if( SEC_ASN1EncodeItem(NULL, &epkiItem, epki,
-        SEC_ASN1_GET(SECKEY_EncryptedPrivateKeyInfoTemplate) )  == NULL ) {
-        JSS_throwMsg(env, TOKEN_EXCEPTION, "Failed to ASN1-encode "
-            "EncryptedPrivateKeyInfo");
+    if (SEC_ASN1EncodeItem(NULL, &epkiItem, epki,
+        SEC_ASN1_GET(SECKEY_EncryptedPrivateKeyInfoTemplate)) == NULL) {
+        JSS_throwMsg(
+            env, TOKEN_EXCEPTION,
+            "Failed to ASN1-encode EncryptedPrivateKeyInfo");
         goto finish;
     }
 
-    /*
-     * convert to Java byte array
-     */
-    encodedEpki = JSS_SECItemToByteArray(env, &epkiItem);
+    // convert to Java byte array
+    jbyteArray encodedEpki = JSS_SECItemToByteArray(env, &epkiItem);
 
 finish:
-    if( epki != NULL ) {
+    if (epki != NULL) {
         SECKEY_DestroyEncryptedPrivateKeyInfo(epki, PR_TRUE /*freeit*/);
     }
-    if( pwchars != NULL ) {
-        (*env)->ReleaseByteArrayElements(env, pwArray, pwchars, JNI_ABORT);
+    if (epkiItem.data != NULL) {
+        SECITEM_FreeItem(&epkiItem, PR_FALSE /*freeit*/);
     }
-    if(epkiItem.data != NULL) {
-        PR_Free(epkiItem.data);
+    if (pwItem != NULL) {
+        SECITEM_FreeItem(pwItem, PR_TRUE /*freeit*/);
     }
     return encodedEpki;
 }
+
+
+JNIEXPORT void JNICALL
+Java_org_mozilla_jss_pkcs11_PK11Store_importEncryptedPrivateKeyInfo(
+    JNIEnv *env,
+    jobject this,
+    jobject conv,
+    jobject pwObj,
+    jstring nickname,
+    jobject pubKeyObj,
+    jbyteArray epkiBytes)
+{
+    // get slot
+    PK11SlotInfo *slot = NULL;
+    if (JSS_PK11_getStoreSlotPtr(env, this, &slot) != PR_SUCCESS) {
+        ASSERT_OUTOFMEM(env);
+        goto finish;
+    }
+    PR_ASSERT(slot != NULL);
+
+    // decode EncryptedPrivateKeyInfo
+    SECItem *epkiItem = JSS_ByteArrayToSECItem(env, epkiBytes);
+    SECKEYEncryptedPrivateKeyInfo *epki =
+        PR_Calloc(1, sizeof(SECKEYEncryptedPrivateKeyInfo));
+    if (SEC_ASN1DecodeItem(
+                NULL,
+                epki,
+                SEC_ASN1_GET(SECKEY_EncryptedPrivateKeyInfoTemplate),
+                epkiItem
+            ) != SECSuccess) {
+        JSS_throwMsg(env, INVALID_DER_EXCEPTION,
+            "Failed to decode EncryptedPrivateKeyInfo");
+        goto finish;
+    }
+
+    SECItem *pwItem = preparePassword(env, conv, pwObj);
+    if (pwItem == NULL) {
+        ASSERT_OUTOFMEM(env);
+        goto finish;
+    }
+
+    // get public key value
+    jclass pubKeyClass = (*env)->GetObjectClass(env, pubKeyObj);
+    if (pubKeyClass == NULL) {
+        ASSERT_OUTOFMEM(env);
+        goto finish;
+    }
+    jmethodID getEncoded = (*env)->GetMethodID(
+        env, pubKeyClass, "getEncoded", "()[B");
+    if (getEncoded == NULL) {
+        ASSERT_OUTOFMEM(env);
+        goto finish;
+    }
+    jbyteArray spkiBytes = (*env)->CallObjectMethod(
+        env, pubKeyObj, getEncoded);
+    SECItem *spkiItem = JSS_ByteArrayToSECItem(env, spkiBytes);
+    CERTSubjectPublicKeyInfo *spki =
+        PR_Calloc(1, sizeof(CERTSubjectPublicKeyInfo));
+    if (SEC_ASN1DecodeItem(
+                NULL,
+                spki,
+                SEC_ASN1_GET(CERT_SubjectPublicKeyInfoTemplate),
+                spkiItem
+            ) != SECSuccess) {
+        JSS_throwMsg(env, INVALID_DER_EXCEPTION,
+            "Failed to decode SubjectPublicKeyInfo");
+        goto finish;
+    }
+
+    SECKEYPublicKey *pubKey = SECKEY_ExtractPublicKey(spki);
+    if (pubKey == NULL) {
+        JSS_throwMsgPrErr(env, INVALID_DER_EXCEPTION,
+            "Failed to extract public key from SubjectPublicKeyInfo");
+        goto finish;
+    }
+
+    SECItem *pubValue;
+    switch (pubKey->keyType) {
+        case dsaKey:
+            pubValue = &pubKey->u.dsa.publicValue;
+            break;
+        case dhKey:
+            pubValue = &pubKey->u.dh.publicValue;
+            break;
+        case rsaKey:
+            pubValue = &pubKey->u.rsa.modulus;
+            break;
+        case ecKey:
+            pubValue = &pubKey->u.ec.publicValue;
+            break;
+        default:
+            pubValue = NULL;
+    }
+
+    // prepare nickname
+    const char *nicknameChars = (*env)->GetStringUTFChars(env, nickname, NULL);
+    if (nicknameChars == NULL) {
+        ASSERT_OUTOFMEM(env);
+        goto finish;
+    }
+    SECItem nickItem;
+    nickItem.data = nicknameChars;
+    nickItem.len = (*env)->GetStringUTFLength(env, nickname);
+
+    // perform import
+    SECStatus result = PK11_ImportEncryptedPrivateKeyInfo(
+        slot, epki, pwItem, &nickItem, pubValue,
+        PR_TRUE /* isperm */, PR_TRUE /* isprivate */,
+        pubKey->keyType, 0 /* keyUsage */, NULL /* wincx */);
+    // keyUsage = 198 ?
+    if (result != SECSuccess) {
+        JSS_throwMsg(
+            env, TOKEN_EXCEPTION,
+            "Failed to import EncryptedPrivateKeyInfo to token");
+        goto finish;
+    }
+
+finish:
+    if (epkiItem != NULL) {
+        SECITEM_FreeItem(epkiItem, PR_TRUE /*freeit*/);
+    }
+    if (epki != NULL) {
+        SECKEY_DestroyEncryptedPrivateKeyInfo(epki, PR_TRUE /*freeit*/);
+    }
+    if (spkiItem != NULL) {
+        SECITEM_FreeItem(spkiItem, PR_TRUE /*freeit*/);
+    }
+    if (spki != NULL) {
+        SECKEY_DestroySubjectPublicKeyInfo(spki);
+    }
+    if (pwItem != NULL) {
+        SECITEM_FreeItem(pwItem, PR_TRUE /*freeit*/);
+    }
+    if (pubKey != NULL) {
+        SECKEY_DestroyPublicKey(pubKey);
+    }
+    if (nicknameChars != NULL) {
+        (*env)->ReleaseStringUTFChars(env, nickname, nicknameChars);
+    }
+}
+
+/* Process the given password through the given PasswordConverter,
+ * returning a new SECItem* on success.
+ *
+ * After use, the caller should free the SECItem:
+ *
+ *   SECITEM_FreeItem(pwItem, PR_TRUE).
+ */
+SECItem *preparePassword(JNIEnv *env, jobject conv, jobject pwObj) {
+    jclass passwordClass = (*env)->GetObjectClass(env, pwObj);
+    if (passwordClass == NULL) {
+        ASSERT_OUTOFMEM(env);
+        return NULL;
+    }
+
+    jbyteArray pwBytes;
+
+    if (conv == NULL) {
+        jmethodID getByteCopy = (*env)->GetMethodID(
+            env, passwordClass, PW_GET_BYTE_COPY_NAME, PW_GET_BYTE_COPY_SIG);
+        if (getByteCopy == NULL) {
+            ASSERT_OUTOFMEM(env);
+            return NULL;
+        }
+        pwBytes = (*env)->CallObjectMethod(env, pwObj, getByteCopy);
+    } else {
+        jmethodID getChars = (*env)->GetMethodID(
+            env, passwordClass, "getChars", "()[C");
+        if (getChars == NULL) {
+            ASSERT_OUTOFMEM(env);
+            return NULL;
+        }
+        jcharArray pwChars = (*env)->CallObjectMethod(env, pwObj, getChars);
+
+        jclass convClass = (*env)->GetObjectClass(env, conv);
+        if (conv == NULL) {
+            ASSERT_OUTOFMEM(env);
+            return NULL;
+        }
+        jmethodID convert = (*env)->GetMethodID(
+            env, convClass, "convert", "([C)[B");
+        if (convert == NULL) {
+            ASSERT_OUTOFMEM(env);
+            return NULL;
+        }
+        pwBytes = (*env)->CallObjectMethod(env, conv, convert, pwChars);
+    }
+
+    return JSS_ByteArrayToSECItem(env, pwBytes);
+}
diff --git a/org/mozilla/jss/pkcs11/PK11Store.java b/org/mozilla/jss/pkcs11/PK11Store.java
index 3508db1c55398e2fd302b6a971a1dbf8a07e8411..4d656034d4e022fdc1b2e17a0f251c06fb5d633f 100644
--- a/org/mozilla/jss/pkcs11/PK11Store.java
+++ b/org/mozilla/jss/pkcs11/PK11Store.java
@@ -4,8 +4,10 @@
 
 package org.mozilla.jss.pkcs11;
 
+import org.mozilla.jss.CryptoManager;
 import org.mozilla.jss.crypto.*;
 import org.mozilla.jss.util.*;
+import java.security.PublicKey;
 import java.security.cert.CertificateEncodingException;
 import java.util.Vector;
 
@@ -53,8 +55,35 @@ public final class PK11Store implements CryptoStore {
     public native void deletePrivateKey(PrivateKey key)
         throws NoSuchItemOnTokenException, TokenException;
 
-    public native byte[] getEncryptedPrivateKeyInfo(X509Certificate cert,
-        PBEAlgorithm pbeAlg, Password pw, int iteration);
+    public byte[] getEncryptedPrivateKeyInfo(
+            X509Certificate cert,
+            PBEAlgorithm pbeAlg,
+            Password pw,
+            int iteration)
+            throws CryptoManager.NotInitializedException,
+                ObjectNotFoundException, TokenException {
+        return getEncryptedPrivateKeyInfo(
+            null,
+            pw,
+            pbeAlg,
+            iteration,
+            CryptoManager.getInstance().findPrivKeyByCert(cert)
+        );
+    }
+
+    public native byte[] getEncryptedPrivateKeyInfo(
+        KeyGenerator.CharToByteConverter conv,
+        Password pw,
+        Algorithm alg,
+        int n,
+        PrivateKey k);
+
+    public native void importEncryptedPrivateKeyInfo(
+        KeyGenerator.CharToByteConverter conv,
+        Password pw,
+        String nickname,
+        PublicKey pubKey,
+        byte[] epkiBytes);
 
     ////////////////////////////////////////////////////////////
     // Certs
-- 
2.9.3

-------------- next part --------------
From f6891d0f265dee2f6631c3d8eb8b408f84010c94 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Thu, 23 Mar 2017 14:34:31 +1100
Subject: [PATCH] PKCS12Util: use AES to encrypt private keys

Update PKCS12Util to use AES-256-CBC to encrypt private keys.
Use JSS CryptoStore methods to ensure that all key wrapping and
unwrapping is done on the token.

Part of: https://pagure.io/dogtagpki/issue/2610
---
 .../netscape/cmstools/pkcs12/PKCS12ImportCLI.java  |   4 +-
 .../com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java |   1 -
 .../src/netscape/security/pkcs/PKCS12KeyInfo.java  |  29 +++--
 .../src/netscape/security/pkcs/PKCS12Util.java     | 122 ++++++++-------------
 4 files changed, 65 insertions(+), 91 deletions(-)

diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java
index da5478c60bc29a2b41b8f475029cae1fc569f1a5..de432848c553f8e20569c08aeb27d428373a09c2 100644
--- a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java
@@ -124,12 +124,12 @@ public class PKCS12ImportCLI extends CLI {
 
             if (nicknames.length == 0) {
                 // store all certificates
-                util.storeIntoNSS(pkcs12, overwrite);
+                util.storeIntoNSS(pkcs12, password, overwrite);
 
             } else {
                 // load specified certificates
                 for (String nickname : nicknames) {
-                    util.storeCertIntoNSS(pkcs12, nickname, overwrite);
+                    util.storeCertIntoNSS(pkcs12, password, nickname, overwrite);
                 }
             }
 
diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java
index fbebddabb918f12c0f94943d1ecf8fe102d01121..e74b63a59b467f04bbaa7d253688762a13f0d2c6 100644
--- a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java
@@ -38,6 +38,5 @@ public class PKCS12KeyCLI extends CLI {
 
         System.out.println("  Key ID: " + keyInfo.getID().toString(16));
         System.out.println("  Subject DN: " + keyInfo.getSubjectDN());
-        System.out.println("  Algorithm: " + keyInfo.getPrivateKeyInfo().getAlgorithm());
     }
 }
diff --git a/base/util/src/netscape/security/pkcs/PKCS12KeyInfo.java b/base/util/src/netscape/security/pkcs/PKCS12KeyInfo.java
index c7e84f01ffa19a0fc116f1f3fddf2bf3dfe9de9e..f180cf23bb431d4fb41d4bba2503f6aa9763a949 100644
--- a/base/util/src/netscape/security/pkcs/PKCS12KeyInfo.java
+++ b/base/util/src/netscape/security/pkcs/PKCS12KeyInfo.java
@@ -19,17 +19,34 @@ package netscape.security.pkcs;
 
 import java.math.BigInteger;
 
-import org.mozilla.jss.pkix.primitive.PrivateKeyInfo;
+import org.mozilla.jss.crypto.PrivateKey;
 
 public class PKCS12KeyInfo {
 
+    private PrivateKey privateKey;
+    private byte[] epkiBytes;
     BigInteger id;
-    PrivateKeyInfo privateKeyInfo;
     String subjectDN;
 
     public PKCS12KeyInfo() {
     }
 
+    public PKCS12KeyInfo(PrivateKey k) {
+        this.privateKey = k;
+    }
+
+    public PKCS12KeyInfo(byte[] epkiBytes) {
+        this.epkiBytes = epkiBytes;
+    }
+
+    public PrivateKey getPrivateKey() {
+        return this.privateKey;
+    }
+
+    public byte[] getEncryptedPrivateKeyInfoBytes() {
+        return epkiBytes;
+    }
+
     public BigInteger getID() {
         return id;
     }
@@ -38,14 +55,6 @@ public class PKCS12KeyInfo {
         this.id = id;
     }
 
-    public PrivateKeyInfo getPrivateKeyInfo() {
-        return privateKeyInfo;
-    }
-
-    public void setPrivateKeyInfo(PrivateKeyInfo privateKeyInfo) {
-        this.privateKeyInfo = privateKeyInfo;
-    }
-
     public String getSubjectDN() {
         return subjectDN;
     }
diff --git a/base/util/src/netscape/security/pkcs/PKCS12Util.java b/base/util/src/netscape/security/pkcs/PKCS12Util.java
index 0b164aafc55ef91f78a8252232a241c8c5d22d3e..9f9a35e163037791502f850657bc3ed2225b3e36 100644
--- a/base/util/src/netscape/security/pkcs/PKCS12Util.java
+++ b/base/util/src/netscape/security/pkcs/PKCS12Util.java
@@ -33,27 +33,19 @@ import java.util.Collection;
 import org.apache.commons.lang.StringUtils;
 import org.mozilla.jss.CryptoManager;
 import org.mozilla.jss.asn1.ANY;
-import org.mozilla.jss.asn1.ASN1Util;
 import org.mozilla.jss.asn1.ASN1Value;
 import org.mozilla.jss.asn1.BMPString;
 import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;
 import org.mozilla.jss.asn1.OCTET_STRING;
 import org.mozilla.jss.asn1.SEQUENCE;
 import org.mozilla.jss.asn1.SET;
-import org.mozilla.jss.crypto.Cipher;
 import org.mozilla.jss.crypto.CryptoStore;
 import org.mozilla.jss.crypto.CryptoToken;
 import org.mozilla.jss.crypto.EncryptionAlgorithm;
-import org.mozilla.jss.crypto.IVParameterSpec;
 import org.mozilla.jss.crypto.InternalCertificate;
-import org.mozilla.jss.crypto.KeyGenAlgorithm;
-import org.mozilla.jss.crypto.KeyWrapAlgorithm;
-import org.mozilla.jss.crypto.KeyWrapper;
 import org.mozilla.jss.crypto.NoSuchItemOnTokenException;
 import org.mozilla.jss.crypto.ObjectNotFoundException;
-import org.mozilla.jss.crypto.PBEAlgorithm;
 import org.mozilla.jss.crypto.PrivateKey;
-import org.mozilla.jss.crypto.SymmetricKey;
 import org.mozilla.jss.crypto.X509Certificate;
 import org.mozilla.jss.pkcs12.AuthenticatedSafes;
 import org.mozilla.jss.pkcs12.CertBag;
@@ -61,14 +53,10 @@ import org.mozilla.jss.pkcs12.PFX;
 import org.mozilla.jss.pkcs12.PasswordConverter;
 import org.mozilla.jss.pkcs12.SafeBag;
 import org.mozilla.jss.pkix.primitive.Attribute;
-import org.mozilla.jss.pkix.primitive.EncryptedPrivateKeyInfo;
-import org.mozilla.jss.pkix.primitive.PrivateKeyInfo;
 import org.mozilla.jss.util.Password;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.netscape.cmsutil.crypto.CryptoUtil;
-
 import netscape.ldap.LDAPDN;
 import netscape.ldap.util.DN;
 import netscape.security.x509.X509CertImpl;
@@ -114,41 +102,30 @@ public class PKCS12Util {
         icert.setObjectSigningTrust(PKCS12.decodeFlags(flags[2]));
     }
 
-    byte[] getEncodedKey(PrivateKey privateKey) throws Exception {
-        CryptoManager cm = CryptoManager.getInstance();
-        CryptoToken token = cm.getInternalKeyStorageToken();
-
-        byte[] iv = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 };
-        IVParameterSpec param = new IVParameterSpec(iv);
-
-        SymmetricKey sk = CryptoUtil.generateKey(token, KeyGenAlgorithm.DES3, 0, null, true);
-        byte[] enckey = CryptoUtil.wrapUsingSymmetricKey(
-                token,
-                sk,
-                privateKey,
-                param,
-                KeyWrapAlgorithm.DES3_CBC_PAD);
-
-        Cipher c = token.getCipherContext(EncryptionAlgorithm.DES3_CBC_PAD);
-        c.initDecrypt(sk, param);
-        return c.doFinal(enckey);
-    }
-
     public void addKeyBag(PKCS12KeyInfo keyInfo, Password password,
             SEQUENCE encSafeContents) throws Exception {
+        PrivateKey k = keyInfo.getPrivateKey();
+        if (k == null) {
+            logger.debug("NO PRIVATE KEY for " + keyInfo.subjectDN);
+            return;
+        }
 
         logger.debug("Creating key bag for " + keyInfo.subjectDN);
 
         PasswordConverter passConverter = new PasswordConverter();
-        byte salt[] = { 0x01, 0x01, 0x01, 0x01 };
-
-        EncryptedPrivateKeyInfo encPrivateKeyInfo = EncryptedPrivateKeyInfo.createPBE(
-                PBEAlgorithm.PBE_SHA1_DES3_CBC,
-                password, salt, 1, passConverter, keyInfo.privateKeyInfo);
+        byte[] epkiBytes = CryptoManager.getInstance()
+            .getInternalKeyStorageToken()
+            .getCryptoStore()
+            .getEncryptedPrivateKeyInfo(
+                /* NSS has a bug that causes any AES CBC encryption
+                 * to use AES-256, but AlgorithmID contains chosen
+                 * alg.  To avoid mismatch, use AES_256_CBC. */
+                passConverter, password, EncryptionAlgorithm.AES_256_CBC, 0, k);
 
         SET keyAttrs = createKeyBagAttrs(keyInfo);
 
-        SafeBag safeBag = new SafeBag(SafeBag.PKCS8_SHROUDED_KEY_BAG, encPrivateKeyInfo, keyAttrs);
+        SafeBag safeBag = new SafeBag(
+            SafeBag.PKCS8_SHROUDED_KEY_BAG, new ANY(epkiBytes), keyAttrs);
         encSafeContents.addElement(safeBag);
     }
 
@@ -318,14 +295,10 @@ public class PKCS12Util {
             PrivateKey privateKey = cm.findPrivKeyByCert(cert);
             logger.debug("Certificate \"" + nickname + "\" has private key");
 
-            PKCS12KeyInfo keyInfo = new PKCS12KeyInfo();
+            PKCS12KeyInfo keyInfo = new PKCS12KeyInfo(privateKey);
             keyInfo.id = id;
             keyInfo.subjectDN = cert.getSubjectDN().toString();
 
-            byte[] privateData = getEncodedKey(privateKey);
-            keyInfo.privateKeyInfo = (PrivateKeyInfo)
-                    ASN1Util.decode(PrivateKeyInfo.getTemplate(), privateData);
-
             pkcs12.addKeyInfo(keyInfo);
 
         } catch (ObjectNotFoundException e) {
@@ -375,11 +348,7 @@ public class PKCS12Util {
 
     public PKCS12KeyInfo getKeyInfo(SafeBag bag, Password password) throws Exception {
 
-        PKCS12KeyInfo keyInfo = new PKCS12KeyInfo();
-
-        // get private key info
-        EncryptedPrivateKeyInfo encPrivateKeyInfo = (EncryptedPrivateKeyInfo) bag.getInterpretedBagContent();
-        keyInfo.privateKeyInfo = encPrivateKeyInfo.decrypt(password, new PasswordConverter());
+        PKCS12KeyInfo keyInfo = new PKCS12KeyInfo(bag.getBagContent().getEncoded());
 
         // get key attributes
         SET bagAttrs = bag.getBagAttributes();
@@ -491,7 +460,7 @@ public class PKCS12Util {
 
     public void getKeyInfos(PKCS12 pkcs12, PFX pfx, Password password) throws Exception {
 
-        logger.debug("Load private keys:");
+        logger.debug("Load encrypted private keys:");
 
         AuthenticatedSafes safes = pfx.getAuthSafes();
 
@@ -590,20 +559,12 @@ public class PKCS12Util {
 
     public void importKey(
             PKCS12 pkcs12,
+            Password password,
+            String nickname,
             PKCS12KeyInfo keyInfo) throws Exception {
 
         logger.debug("Importing private key " + keyInfo.subjectDN);
 
-        byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 };
-        IVParameterSpec param = new IVParameterSpec(iv);
-
-        PrivateKeyInfo privateKeyInfo = keyInfo.privateKeyInfo;
-
-        // encode private key
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        privateKeyInfo.encode(bos);
-        byte[] privateKey = bos.toByteArray();
-
         PKCS12CertInfo certInfo = pkcs12.getCertInfoByID(keyInfo.getID());
         if (certInfo == null) {
             logger.debug("Private key has no certificate, ignore");
@@ -619,26 +580,29 @@ public class PKCS12Util {
         // get public key
         PublicKey publicKey = cert.getPublicKey();
 
-        // delete the cert again
+        byte[] epkiBytes = keyInfo.getEncryptedPrivateKeyInfoBytes();
+        if (epkiBytes == null) {
+            logger.debug(
+                "No EncryptedPrivateKeyInfo for key '"
+                + keyInfo.subjectDN + "'; skipping key");
+        }
+        store.importEncryptedPrivateKeyInfo(
+            new PasswordConverter(), password, nickname, publicKey, epkiBytes);
+
+        // delete the cert again (it will be imported again later
+        // with the correct nickname)
         try {
             store.deleteCert(cert);
         } catch (NoSuchItemOnTokenException e) {
             // this is OK
         }
-
-        // encrypt private key
-        SymmetricKey sk = CryptoUtil.generateKey(token, KeyGenAlgorithm.DES3, 0, null, true);
-        byte[] encpkey = CryptoUtil.encryptUsingSymmetricKey(
-                token, sk, privateKey, EncryptionAlgorithm.DES3_CBC_PAD, param);
-
-        // unwrap private key to load into database
-        KeyWrapper wrapper = token.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD);
-        wrapper.initUnwrap(sk, param);
-        wrapper.unwrapPrivate(encpkey, getPrivateKeyType(publicKey), publicKey);
     }
 
-    public void storeCertIntoNSS(PKCS12 pkcs12, PKCS12CertInfo certInfo, boolean overwrite) throws Exception {
-
+    public void storeCertIntoNSS(
+            PKCS12 pkcs12, Password password,
+            PKCS12CertInfo certInfo, boolean overwrite)
+        throws Exception
+    {
         CryptoManager cm = CryptoManager.getInstance();
         CryptoToken ct = cm.getInternalKeyStorageToken();
         CryptoStore store = ct.getCryptoStore();
@@ -656,7 +620,7 @@ public class PKCS12Util {
         X509Certificate cert;
         if (keyInfo != null) { // cert has key
             logger.debug("Importing user key for " + certInfo.nickname);
-            importKey(pkcs12, keyInfo);
+            importKey(pkcs12, password, certInfo.nickname, keyInfo);
 
             logger.debug("Importing user certificate " + certInfo.nickname);
             cert = cm.importUserCACertPackage(certInfo.cert.getEncoded(), certInfo.nickname);
@@ -671,19 +635,21 @@ public class PKCS12Util {
             setTrustFlags(cert, certInfo.trustFlags);
     }
 
-    public void storeCertIntoNSS(PKCS12 pkcs12, String nickname, boolean overwrite) throws Exception {
+    public void storeCertIntoNSS(PKCS12 pkcs12, Password password, String nickname, boolean overwrite) throws Exception {
         Collection<PKCS12CertInfo> certInfos = pkcs12.getCertInfosByNickname(nickname);
         for (PKCS12CertInfo certInfo : certInfos) {
-            storeCertIntoNSS(pkcs12, certInfo, overwrite);
+            storeCertIntoNSS(pkcs12, password, certInfo, overwrite);
         }
     }
 
-    public void storeIntoNSS(PKCS12 pkcs12, boolean overwrite) throws Exception {
-
+    public void storeIntoNSS(
+            PKCS12 pkcs12, Password password, boolean overwrite)
+        throws Exception
+    {
         logger.info("Storing data into NSS database");
 
         for (PKCS12CertInfo certInfo : pkcs12.getCertInfos()) {
-            storeCertIntoNSS(pkcs12, certInfo, overwrite);
+            storeCertIntoNSS(pkcs12, password, certInfo, overwrite);
         }
     }
 }
-- 
2.9.3



More information about the Pki-devel mailing list