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
From de2d7f049eb4462c7442795a77a8a915ae70d216 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@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
From 85f014d639b669e20b7ee56a8fd908022b478c4a Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@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
From 59ebce62d1f9b19f211f35f6e41c5b956588273f Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@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
From f6891d0f265dee2f6631c3d8eb8b408f84010c94 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@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
_______________________________________________ Pki-devel mailing list Pki-devel@redhat.com https://www.redhat.com/mailman/listinfo/pki-devel