From 59ebce62d1f9b19f211f35f6e41c5b956588273f Mon Sep 17 00:00:00 2001 From: Fraser Tweedale 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