[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