/*
 * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#import "apple_security_KeychainStore.h"
#import "jni_util.h"
#import <Security/Security.h>
#import <Security/SecImportExport.h>
#import <CoreServices/CoreServices.h>  // (for require() macros)
#import <Cocoa/Cocoa.h>

static jstring getLabelFromItem(JNIEnv *env, SecKeychainItemRef inItem)
{
    OSStatus status;
    jstring returnValue = NULL;
    char *attribCString = NULL;

    SecKeychainAttribute itemAttrs[] = { { kSecLabelItemAttr, 0, NULL } };
    SecKeychainAttributeList attrList = { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs };

    status = SecKeychainItemCopyContent(inItem, NULL, &attrList, NULL, NULL);

    if(status) {
        cssmPerror("getLabelFromItem: SecKeychainItemCopyContent", status);
        goto errOut;
    }

    attribCString = malloc(itemAttrs[0].length + 1);
    if (attribCString == NULL) {
        JNU_ThrowOutOfMemoryError(env, "native heap");
        goto errOut;
    }

    strncpy(attribCString, itemAttrs[0].data, itemAttrs[0].length);
    attribCString[itemAttrs[0].length] = '\0';
    returnValue = (*env)->NewStringUTF(env, attribCString);

errOut:
    SecKeychainItemFreeContent(&attrList, NULL);
    if (attribCString) free(attribCString);
    return returnValue;
}

static jlong getModDateFromItem(JNIEnv *env, SecKeychainItemRef inItem)
{
    OSStatus status;
    SecKeychainAttribute itemAttrs[] = { { kSecModDateItemAttr, 0, NULL } };
    SecKeychainAttributeList attrList = { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs };
    jlong returnValue = 0;

    status = SecKeychainItemCopyContent(inItem, NULL, &attrList, NULL, NULL);

    if(status) {
        // This is almost always missing, so don't dump an error.
        // cssmPerror("getModDateFromItem: SecKeychainItemCopyContent", status);
        goto errOut;
    }

    memcpy(&returnValue, itemAttrs[0].data, itemAttrs[0].length);

errOut:
    SecKeychainItemFreeContent(&attrList, NULL);
    return returnValue;
}

static void setLabelForItem(NSString *inLabel, SecKeychainItemRef inItem)
{
    OSStatus status;
    const char *labelCString = [inLabel UTF8String];

    // Set up attribute vector (each attribute consists of {tag, length, pointer}):
    SecKeychainAttribute attrs[] = {
        { kSecLabelItemAttr, strlen(labelCString), (void *)labelCString }
    };

    const SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]), attrs };

    // Not changing data here, just attributes.
    status = SecKeychainItemModifyContent(inItem, &attributes, 0, NULL);

    if(status) {
        cssmPerror("setLabelForItem: SecKeychainItemModifyContent", status);
    }
}

/*
 * Given a SecIdentityRef, do our best to construct a complete, ordered, and
 * verified cert chain, returning the result in a CFArrayRef. The result is
 * can be passed back to Java as a chain for a private key.
 */
static OSStatus completeCertChain(
                                     SecIdentityRef         identity,
                                     SecCertificateRef    trustedAnchor,    // optional additional trusted anchor
                                     bool                 includeRoot,     // include the root in outArray
                                     CFArrayRef            *outArray)        // created and RETURNED
{
    SecTrustRef                    secTrust = NULL;
    SecPolicyRef                policy = NULL;
    SecPolicySearchRef            policySearch = NULL;
    SecTrustResultType            secTrustResult;
    CSSM_TP_APPLE_EVIDENCE_INFO *dummyEv;            // not used
    CFArrayRef                    certChain = NULL;   // constructed chain, CERTS ONLY
    CFMutableArrayRef             subjCerts;            // passed to SecTrust
    CFMutableArrayRef             certArray;            // returned array starting with
                                                    //   identity
    CFIndex                     numResCerts;
    CFIndex                     dex;
    OSStatus                     ortn;
      SecCertificateRef             certRef;

    /* First element in out array is the SecIdentity */
    certArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
    CFArrayAppendValue(certArray, identity);

    /* the single element in certs-to-be-evaluated comes from the identity */
       ortn = SecIdentityCopyCertificate(identity, &certRef);
    if(ortn) {
        /* should never happen */
        cssmPerror("SecIdentityCopyCertificate", ortn);
        return ortn;
    }

    /*
     * Now use SecTrust to get a complete cert chain, using all of the
     * user's keychains to look for intermediate certs.
     * NOTE this does NOT handle root certs which are not in the system
     * root cert DB.
     */
    subjCerts = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);
    CFArraySetValueAtIndex(subjCerts, 0, certRef);

    /* the array owns the subject cert ref now */
    CFRelease(certRef);

    /* Get a SecPolicyRef for generic X509 cert chain verification */
    ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3,
                                 &CSSMOID_APPLE_X509_BASIC,
                                 NULL,                // value
                                 &policySearch);
    if(ortn) {
        /* should never happen */
        cssmPerror("SecPolicySearchCreate", ortn);
        goto errOut;
    }
    ortn = SecPolicySearchCopyNext(policySearch, &policy);
    if(ortn) {
        /* should never happen */
        cssmPerror("SecPolicySearchCopyNext", ortn);
        goto errOut;
    }

    /* build a SecTrustRef for specified policy and certs */
    ortn = SecTrustCreateWithCertificates(subjCerts,
                                          policy, &secTrust);
    if(ortn) {
        cssmPerror("SecTrustCreateWithCertificates", ortn);
        goto errOut;
    }

    if(trustedAnchor) {
        /*
        * Tell SecTrust to trust this one in addition to the current
         * trusted system-wide anchors.
         */
        CFMutableArrayRef newAnchors;
        CFArrayRef currAnchors;

        ortn = SecTrustCopyAnchorCertificates(&currAnchors);
        if(ortn) {
            /* should never happen */
            cssmPerror("SecTrustCopyAnchorCertificates", ortn);
            goto errOut;
        }
        newAnchors = CFArrayCreateMutableCopy(NULL,
                                              CFArrayGetCount(currAnchors) + 1,
                                              currAnchors);
        CFRelease(currAnchors);
        CFArrayAppendValue(newAnchors, trustedAnchor);
        ortn = SecTrustSetAnchorCertificates(secTrust, newAnchors);
        CFRelease(newAnchors);
        if(ortn) {
            cssmPerror("SecTrustSetAnchorCertificates", ortn);
            goto errOut;
        }
    }

    /* evaluate: GO */
    ortn = SecTrustEvaluate(secTrust, &secTrustResult);
    if(ortn) {
        cssmPerror("SecTrustEvaluate", ortn);
        goto errOut;
    }
    switch(secTrustResult) {
        case kSecTrustResultUnspecified:
            /* cert chain valid, no special UserTrust assignments; drop thru */
        case kSecTrustResultProceed:
            /* cert chain valid AND user explicitly trusts this */
            break;
        default:
            /*
             * Cert chain construction failed.
             * Just go with the single subject cert we were given; maybe the
             * peer can complete the chain.
             */
            ortn = noErr;
            goto errOut;
    }

    /* get resulting constructed cert chain */
    ortn = SecTrustGetResult(secTrust, &secTrustResult, &certChain, &dummyEv);
    if(ortn) {
        cssmPerror("SecTrustEvaluate", ortn);
        goto errOut;
    }

    /*
     * Copy certs from constructed chain to our result array, skipping
     * the leaf (which is already there, as a SecIdentityRef) and possibly
     * a root.
     */
    numResCerts = CFArrayGetCount(certChain);
    if(numResCerts < 1) {
        /*
         * Can't happen: If chain doesn't verify to a root, we'd
         * have bailed after SecTrustEvaluate().
         */
        ortn = noErr;
        goto errOut;
    }
    if(!includeRoot) {
        /* skip the last (root) cert) */
        numResCerts--;
    }
    for(dex=1; dex<numResCerts; dex++) {
        certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certChain, dex);
        CFArrayAppendValue(certArray, certRef);
    }
errOut:
        /* clean up */
        if(secTrust) {
            CFRelease(secTrust);
        }
    if(subjCerts) {
        CFRelease(subjCerts);
    }
    if(policy) {
        CFRelease(policy);
    }
    if(policySearch) {
        CFRelease(policySearch);
    }
    *outArray = certArray;
    return ortn;
}

static void addIdentitiesToKeystore(JNIEnv *env, jobject keyStore, jmethodID jm_createKeyEntry)
{
    // Search the user keychain list for all identities. Identities are a certificate/private key association that
    // can be chosen for a purpose such as signing or an SSL connection.
    SecIdentitySearchRef identitySearch = NULL;
    // Pass 0 if you want all identities returned by this search
    OSStatus err = SecIdentitySearchCreate(NULL, 0, &identitySearch);
    SecIdentityRef theIdentity = NULL;
    OSErr searchResult = noErr;

    do {
        searchResult = SecIdentitySearchCopyNext(identitySearch, &theIdentity);

        if (searchResult == noErr) {
            // Get the cert from the identity, then generate a chain.
            SecCertificateRef certificate;
            SecIdentityCopyCertificate(theIdentity, &certificate);
            CFArrayRef certChain = NULL;

            // *** Should do something with this error...
            err = completeCertChain(theIdentity, NULL, TRUE, &certChain);

            CFIndex i, certCount = CFArrayGetCount(certChain);

            // Make a java array of certificate data from the chain.
            jclass byteArrayClass = (*env)->FindClass(env, "[B");
            if (byteArrayClass == NULL) {
                goto errOut;
            }
            jobjectArray javaCertArray = (*env)->NewObjectArray(env, certCount, byteArrayClass, NULL);
            // Cleanup first then check for a NULL return code
            (*env)->DeleteLocalRef(env, byteArrayClass);
            if (javaCertArray == NULL) {
                goto errOut;
            }

            // And, make an array of the certificate refs.
            jlongArray certRefArray = (*env)->NewLongArray(env, certCount);
            if (certRefArray == NULL) {
                goto errOut;
            }

            SecCertificateRef currCertRef = NULL;

            for (i = 0; i < certCount; i++) {
                CSSM_DATA currCertData;

                if (i == 0)
                    currCertRef = certificate;
                else
                    currCertRef = (SecCertificateRef)CFArrayGetValueAtIndex(certChain, i);

                bzero(&currCertData, sizeof(CSSM_DATA));
                err = SecCertificateGetData(currCertRef, &currCertData);
                jbyteArray encodedCertData = (*env)->NewByteArray(env, currCertData.Length);
                if (encodedCertData == NULL) {
                    goto errOut;
                }
                (*env)->SetByteArrayRegion(env, encodedCertData, 0, currCertData.Length, (jbyte *)currCertData.Data);
                (*env)->SetObjectArrayElement(env, javaCertArray, i, encodedCertData);
                jlong certRefElement = ptr_to_jlong(currCertRef);
                (*env)->SetLongArrayRegion(env, certRefArray, i, 1, &certRefElement);
            }

            // Get the private key.  When needed we'll export the data from it later.
            SecKeyRef privateKeyRef;
            err = SecIdentityCopyPrivateKey(theIdentity, &privateKeyRef);

            // Find the label.  It's a 'blob', but we interpret as characters.
            jstring alias = getLabelFromItem(env, (SecKeychainItemRef)certificate);
            if (alias == NULL) {
                goto errOut;
            }

            // Find the creation date.
            jlong creationDate = getModDateFromItem(env, (SecKeychainItemRef)certificate);

            // Call back to the Java object to create Java objects corresponding to this security object.
            jlong nativeKeyRef = ptr_to_jlong(privateKeyRef);
            (*env)->CallVoidMethod(env, keyStore, jm_createKeyEntry, alias, creationDate, nativeKeyRef, certRefArray, javaCertArray);
            if ((*env)->ExceptionCheck(env)) {
                goto errOut;
            }
        }
    } while (searchResult == noErr);

errOut:
    if (identitySearch != NULL) {
        CFRelease(identitySearch);
    }
}

#define ADD(list, str) { \
    jobject localeObj = (*env)->NewStringUTF(env, [str UTF8String]); \
    (*env)->CallBooleanMethod(env, list, jm_listAdd, localeObj); \
    (*env)->DeleteLocalRef(env, localeObj); \
}

#define ADDNULL(list) (*env)->CallBooleanMethod(env, list, jm_listAdd, NULL)


static void addTrustSettingsToInputTrust(JNIEnv *env, jmethodID jm_listAdd, CFArrayRef trustSettings, jobject inputTrust)
{
    CFIndex count = CFArrayGetCount(trustSettings);
    for (int i = 0; i < count; i++) {
        CFDictionaryRef oneTrust = (CFDictionaryRef) CFArrayGetValueAtIndex(trustSettings, i);
        CFIndex size = CFDictionaryGetCount(oneTrust);
        const void * keys [size];
        const void * values [size];
        CFDictionaryGetKeysAndValues(oneTrust, keys, values);
        for (int j = 0; j < size; j++) {
            NSString* s = [NSString stringWithFormat:@"%@", keys[j]];
            ADD(inputTrust, s);
            s = [NSString stringWithFormat:@"%@", values[j]];
            ADD(inputTrust, s);
        }
        SecPolicyRef certPolicy;
        certPolicy = (SecPolicyRef)CFDictionaryGetValue(oneTrust, kSecTrustSettingsPolicy);
        if (certPolicy != NULL) {
            CFDictionaryRef policyDict = SecPolicyCopyProperties(certPolicy);
            ADD(inputTrust, @"SecPolicyOid");
            NSString* s = [NSString stringWithFormat:@"%@", CFDictionaryGetValue(policyDict, @"SecPolicyOid")];
            ADD(inputTrust, s);
            CFRelease(policyDict);
        }
        ADDNULL(inputTrust);
    }
}

static bool loadTrustSettings(JNIEnv *env,
                              SecCertificateRef certRef,
                              SecTrustSettingsDomain domain,
                              jclass jc_arrayListClass,
                              jmethodID jm_arrayListCons,
                              jmethodID jm_listAdd,
                              jobject *inputTrust) {
    CFArrayRef trustSettings;
    // Load trustSettings into inputTrust
    if (SecTrustSettingsCopyTrustSettings(certRef, domain, &trustSettings) == errSecSuccess && trustSettings != NULL) {
        if (*inputTrust == NULL) {
            *inputTrust = (*env)->NewObject(env, jc_arrayListClass, jm_arrayListCons);
            if (*inputTrust == NULL) {
                CFRelease(trustSettings);
                return false;
            }
        }
        addTrustSettingsToInputTrust(env, jm_listAdd, trustSettings, *inputTrust);
        CFRelease(trustSettings);
    }
    return true;
}

static bool createTrustedCertEntry(JNIEnv *env,  jobject keyStore,
                              SecCertificateRef certRef,
                              jmethodID jm_createTrustedCertEntry,
                              jobject inputTrust) {

    SecKeychainItemRef theItem = (SecKeychainItemRef)certRef;
    CSSM_DATA currCertificate;
    OSStatus err = SecCertificateGetData(certRef, &currCertificate);
    jbyteArray certData = (*env)->NewByteArray(env, currCertificate.Length);
    if (certData == NULL) {
        return false;
    }
    (*env)->SetByteArrayRegion(env, certData, 0, currCertificate.Length, (jbyte *)currCertificate.Data);

    // Find the label.  It's a 'blob', but we interpret as characters.
    jstring alias = getLabelFromItem(env, theItem);
    if (alias == NULL) {
        return false;
    }

    // Find the creation date.
    jlong creationDate = getModDateFromItem(env, theItem);

    // Call back to the Java object to create Java objects corresponding to this security object.
    jlong nativeRef = ptr_to_jlong(certRef);
    (*env)->CallVoidMethod(env, keyStore, jm_createTrustedCertEntry, alias, inputTrust, nativeRef, creationDate, certData);
    if ((*env)->ExceptionCheck(env)) {
        return false;
    }
    return true;
}

static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore,
                                        jmethodID jm_createTrustedCertEntry,
                                        jclass jc_arrayListClass,
                                        jmethodID jm_arrayListCons,
                                        jmethodID jm_listAdd) {
    // Search the user keychain list for all X509 certificates.
    SecKeychainSearchRef keychainItemSearch = NULL;
    OSStatus err = SecKeychainSearchCreateFromAttributes(NULL, kSecCertificateItemClass, NULL, &keychainItemSearch);
    SecKeychainItemRef theItem = NULL;
    OSErr searchResult = noErr;

    do {
        searchResult = SecKeychainSearchCopyNext(keychainItemSearch, &theItem);

        if (searchResult == noErr) {
            // Make a byte array with the DER-encoded contents of the certificate.
            SecCertificateRef certRef = (SecCertificateRef)theItem;

            // See KeychainStore::createTrustedCertEntry for content of inputTrust
            // We load trust settings from domains kSecTrustSettingsDomainUser and kSecTrustSettingsDomainAdmin
            // kSecTrustSettingsDomainSystem is ignored because it seems to only contain data for root certificates
            jobject inputTrust = NULL;

            // Load user trustSettings into inputTrust
            if (!loadTrustSettings(env, certRef, kSecTrustSettingsDomainUser,
                                   jc_arrayListClass, jm_arrayListCons, jm_listAdd, &inputTrust)) {
                goto errOut;
            }
            // Load admin trustSettings into inputTrust
            if (!loadTrustSettings(env, certRef, kSecTrustSettingsDomainAdmin,
                                   jc_arrayListClass, jm_arrayListCons, jm_listAdd, &inputTrust)) {
                goto errOut;
            }

            // Only add certificates with trust settings
            if (inputTrust == NULL) {
                continue;
            }

            // Create java object for certificate with trust settings
            if (!createTrustedCertEntry(env, keyStore, certRef, jm_createTrustedCertEntry, inputTrust)) {
                goto errOut;
            }
        }
    } while (searchResult == noErr);

errOut:
    if (keychainItemSearch != NULL) {
        CFRelease(keychainItemSearch);
    }
}

static void addCertificatesToKeystoreRoot(JNIEnv *env, jobject keyStore,
                                        jmethodID jm_createTrustedCertEntry,
                                        jclass jc_arrayListClass,
                                        jmethodID jm_arrayListCons,
                                        jmethodID jm_listAdd) {
    SecKeychainRef keychain = NULL;
    CFMutableArrayRef keychainList = NULL;
    CFDictionaryRef search = NULL;
    CFArrayRef currAnchors = NULL;

    // Load predefined root certificates from SystemRootCertificates keychain
    // SecTrustCopyAnchorCertificates includes extra root certificates and can not be used here
    if( SecKeychainOpen("/System/Library/Keychains/SystemRootCertificates.keychain", &keychain) != errSecSuccess ) {
        return;
    }

    keychainList = CFArrayCreateMutable(kCFAllocatorDefault, 1, &kCFTypeArrayCallBacks);
    if (keychainList == NULL) {
        goto errOut;
    }
    CFArrayAppendValue(keychainList, keychain);

    CFTypeRef searchKeys[] = { kSecClass, kSecMatchLimit, kSecReturnRef, kSecMatchSearchList };
    CFTypeRef searchValues[] = { kSecClassCertificate, kSecMatchLimitAll, kCFBooleanTrue, keychainList };
    search = CFDictionaryCreate(kCFAllocatorDefault,
                              searchKeys, searchValues, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    if (search == NULL) {
        goto errOut;
    }

    if( SecItemCopyMatching( search, (CFTypeRef *)&currAnchors ) == errSecSuccess ){
        CFIndex nAnchors = CFArrayGetCount(currAnchors);

        for (CFIndex i = 0; i < nAnchors; i++) {
            SecCertificateRef certRef = (SecCertificateRef)CFArrayGetValueAtIndex(currAnchors, i);

            // See KeychainStore::createTrustedCertEntry for content of inputTrust for system root certs
            // This time we load trust settings from domains kSecTrustSettingsDomainUser,
            // kSecTrustSettingsDomainAdmin and kSecTrustSettingsDomainSystem
            jobject inputTrust = NULL;

            // Load user trustSettings into inputTrust
            if (!loadTrustSettings(env, certRef, kSecTrustSettingsDomainUser,
                                   jc_arrayListClass, jm_arrayListCons, jm_listAdd, &inputTrust)) {
                goto errOut;
            }
            // Load admin trustSettings into inputTrust
            if (!loadTrustSettings(env, certRef, kSecTrustSettingsDomainAdmin,
                                   jc_arrayListClass, jm_arrayListCons, jm_listAdd, &inputTrust)) {
                goto errOut;
            }
            // Load system trustSettings into inputTrust
            if (!loadTrustSettings(env, certRef, kSecTrustSettingsDomainSystem,
                                   jc_arrayListClass, jm_arrayListCons, jm_listAdd, &inputTrust)) {
                goto errOut;
            }

            if (inputTrust == NULL) {
                continue;
            }

            // Create java object for certificate with trust settings
            if (!createTrustedCertEntry(env, keyStore, certRef, jm_createTrustedCertEntry, inputTrust)) {
                goto errOut;
            }
            CFRetain(certRef);
        }
    }

errOut:
    if (currAnchors != NULL) {
        CFRelease(currAnchors);
    }
    if (search != NULL) {
        CFRelease(search);
    }
    if (keychainList != NULL) {
        CFRelease(keychainList);
    }
    if (keychain != NULL) {
        CFRelease(keychain);
    }
}

/*
 * Class:     apple_security_KeychainStore
 * Method:    _getEncodedKeyData
 * Signature: (J)[B
     */
JNIEXPORT jbyteArray JNICALL Java_apple_security_KeychainStore__1getEncodedKeyData
(JNIEnv *env, jobject this, jlong keyRefLong, jcharArray passwordObj)
{
    SecKeyRef keyRef = (SecKeyRef)jlong_to_ptr(keyRefLong);
    SecKeyImportExportParameters paramBlock;
    OSStatus err = noErr;
    CFDataRef exportedData = NULL;
    jbyteArray returnValue = NULL;
    CFStringRef passwordStrRef = NULL;

    jsize passwordLen = 0;
    jchar *passwordChars = NULL;

    if (passwordObj) {
        passwordLen = (*env)->GetArrayLength(env, passwordObj);

        if (passwordLen > 0) {
            passwordChars = (*env)->GetCharArrayElements(env, passwordObj, NULL);
            if (passwordChars == NULL) {
                goto errOut;
            }

            passwordStrRef = CFStringCreateWithCharactersNoCopy(NULL, passwordChars, passwordLen, kCFAllocatorNull);
            if (passwordStrRef == NULL) {
                goto errOut;
            }
        }
    }

    paramBlock.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
    // Note that setting the flags field **requires** you to pass in a password of some kind.  The keychain will not prompt you.
    paramBlock.flags = 0;
    paramBlock.passphrase = passwordStrRef;
    paramBlock.alertTitle = NULL;
    paramBlock.alertPrompt = NULL;
    paramBlock.accessRef = NULL;
    paramBlock.keyUsage = CSSM_KEYUSE_ANY;
    paramBlock.keyAttributes = CSSM_KEYATTR_RETURN_DEFAULT;

    err = SecKeychainItemExport(keyRef, kSecFormatPKCS12, 0, &paramBlock, &exportedData);

    if (err == noErr) {
        CFIndex size = CFDataGetLength(exportedData);
        returnValue = (*env)->NewByteArray(env, size);
        if (returnValue == NULL) {
            goto errOut;
        }
        (*env)->SetByteArrayRegion(env, returnValue, 0, size, (jbyte *)CFDataGetBytePtr(exportedData));
    }

errOut:
    if (exportedData) CFRelease(exportedData);
    if (passwordStrRef) CFRelease(passwordStrRef);
    if (passwordChars) {
        // clear the password and release
        memset(passwordChars, 0, passwordLen);
        (*env)->ReleaseCharArrayElements(env, passwordObj, passwordChars,
            JNI_ABORT);
    }
    return returnValue;
}


/*
 * Class:     apple_security_KeychainStore
 * Method:    _scanKeychain
 * Signature: (Ljava/lang/String)V
 */
JNIEXPORT void JNICALL Java_apple_security_KeychainStore__1scanKeychain
(JNIEnv *env, jobject this, jstring name)
{
    jboolean isCopy;
    const char *name_utf = (*env)->GetStringUTFChars(env, name, &isCopy);
    if (name_utf != NULL) {
        jclass jc_KeychainStore = (*env)->FindClass(env, "apple/security/KeychainStore");
        if (jc_KeychainStore == NULL) {
            return;
        }

        jmethodID jm_createKeyEntry = (*env)->GetMethodID(env, jc_KeychainStore, "createKeyEntry", "(Ljava/lang/String;JJ[J[[B)V");
        if (jm_createKeyEntry == NULL) {
            return;
        }

        jmethodID jm_createTrustedCertEntry = (*env)->GetMethodID(
                env, jc_KeychainStore, "createTrustedCertEntry", "(Ljava/lang/String;Ljava/util/List;JJ[B)V");
        if (jm_createTrustedCertEntry == NULL) {
            return;
        }

        jclass jc_arrayListClass = (*env)->FindClass(env, "java/util/ArrayList");
        if (jc_arrayListClass == NULL) {
            return;
        }

        jmethodID jm_arrayListCons = (*env)->GetMethodID(env, jc_arrayListClass, "<init>", "()V");
        if (jm_arrayListCons == NULL) {
            return;
        }

        jmethodID jm_listAdd = (*env)->GetMethodID(env, jc_arrayListClass, "add", "(Ljava/lang/Object;)Z");
        if (jm_listAdd == NULL) {
            return;
        }

        if (strcmp(name_utf, "ROOT") == 0) {
            // Scan Trusted Anchors keychain for trusted certificates.
            addCertificatesToKeystoreRoot(env, this,
                                          jm_createTrustedCertEntry,
                                          jc_arrayListClass,
                                          jm_arrayListCons,
                                          jm_listAdd);
        } else {
            // Look for 'identities' -- private key and certificate chain pairs -- and add those.
            // Search for these first, because a certificate that's found here as part of an identity will show up
            // again later as a certificate.
            addIdentitiesToKeystore(env, this, jm_createKeyEntry);

            JNU_CHECK_EXCEPTION(env);

            // Scan current keychain for trusted certificates.
            addCertificatesToKeystore(env, this,
                                      jm_createTrustedCertEntry,
                                      jc_arrayListClass,
                                      jm_arrayListCons,
                                      jm_listAdd);
        }
    }
}

NSString* JavaStringToNSString(JNIEnv *env, jstring jstr) {
     if (jstr == NULL) {
         return NULL;
     }
     jsize len = (*env)->GetStringLength(env, jstr);
     const jchar *chars = (*env)->GetStringChars(env, jstr, NULL);
     if (chars == NULL) {
         return NULL;
     }
     NSString *result = [NSString stringWithCharacters:(UniChar *)chars length:len];
     (*env)->ReleaseStringChars(env, jstr, chars);
     return result;
}

/*
 * Class:     apple_security_KeychainStore
 * Method:    _addItemToKeychain
 * Signature: (Ljava/lang/String;Z[B[C)J
 */
JNIEXPORT jlong JNICALL Java_apple_security_KeychainStore__1addItemToKeychain
(JNIEnv *env, jobject this, jstring alias, jboolean isCertificate, jbyteArray rawDataObj, jcharArray passwordObj)
{
    OSStatus err;
    jlong returnValue = 0;

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; \
    @try {
        jsize dataSize = (*env)->GetArrayLength(env, rawDataObj);
        jbyte *rawData = (*env)->GetByteArrayElements(env, rawDataObj, NULL);
        if (rawData == NULL) {
            goto errOut;
        }

        CFDataRef cfDataToImport = CFDataCreate(kCFAllocatorDefault, (UInt8 *)rawData, dataSize);
        CFArrayRef createdItems = NULL;

        SecKeychainRef defaultKeychain = NULL;
        SecKeychainCopyDefault(&defaultKeychain);

        SecExternalFormat dataFormat = (isCertificate == JNI_TRUE ? kSecFormatX509Cert : kSecFormatWrappedPKCS8);

        // Convert the password obj into a CFStringRef that the keychain importer can use for encryption.
        SecKeyImportExportParameters paramBlock;
        CFStringRef passwordStrRef = NULL;

        jsize passwordLen = 0;
        jchar *passwordChars = NULL;

        if (passwordObj) {
            passwordLen = (*env)->GetArrayLength(env, passwordObj);

            if (passwordLen > 0) {
                passwordChars = (*env)->GetCharArrayElements(env, passwordObj, NULL);
                if (passwordChars == NULL) {
                    goto errOut;
                }

                passwordStrRef = CFStringCreateWithCharactersNoCopy(NULL, passwordChars, passwordLen, kCFAllocatorNull);
                if (passwordStrRef == NULL) {
                    goto errOut;
                }
            }
        }

        paramBlock.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
        // Note that setting the flags field **requires** you to pass in a password of some kind.  The keychain will not prompt you.
        paramBlock.flags = 0;
        paramBlock.passphrase = passwordStrRef;
        paramBlock.alertTitle = NULL;
        paramBlock.alertPrompt = NULL;
        paramBlock.accessRef = NULL;
        paramBlock.keyUsage = CSSM_KEYUSE_ANY;
        paramBlock.keyAttributes = CSSM_KEYATTR_RETURN_DEFAULT;

        err = SecKeychainItemImport(cfDataToImport, NULL, &dataFormat, NULL,
                                    0, &paramBlock, defaultKeychain, &createdItems);
        if (cfDataToImport != NULL) {
            CFRelease(cfDataToImport);
        }

        if (err == noErr) {
            SecKeychainItemRef anItem = (SecKeychainItemRef)CFArrayGetValueAtIndex(createdItems, 0);

            // Don't bother labeling keys. They become part of an identity, and are not an accessible part of the keychain.
            if (CFGetTypeID(anItem) == SecCertificateGetTypeID()) {
                setLabelForItem(JavaStringToNSString(env, alias), anItem);
            }

            // Retain the item, since it will be released once when the array holding it gets released.
            CFRetain(anItem);
            returnValue = ptr_to_jlong(anItem);
        } else {
            cssmPerror("_addItemToKeychain: SecKeychainItemImport", err);
        }

        if (createdItems != NULL) {
            CFRelease(createdItems);
        }

    errOut:
        if (rawData) {
            (*env)->ReleaseByteArrayElements(env, rawDataObj, rawData, JNI_ABORT);
        }

        if (passwordStrRef) CFRelease(passwordStrRef);
        if (passwordChars) {
            // clear the password and release
            memset(passwordChars, 0, passwordLen);
            (*env)->ReleaseCharArrayElements(env, passwordObj, passwordChars,
                JNI_ABORT);
        }
    } @catch (NSException *e) {
        NSLog(@"%@", [e callStackSymbols]);
    } @finally {
        [pool drain];
    }
    return returnValue;
}

/*
 * Class:     apple_security_KeychainStore
 * Method:    _removeItemFromKeychain
 * Signature: (J)I
*/
JNIEXPORT jint JNICALL Java_apple_security_KeychainStore__1removeItemFromKeychain
(JNIEnv *env, jobject this, jlong keychainItem)
{
    SecKeychainItemRef itemToRemove = jlong_to_ptr(keychainItem);
    return SecKeychainItemDelete(itemToRemove);
}

/*
 * Class:     apple_security_KeychainStore
 * Method:    _releaseKeychainItemRef
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_apple_security_KeychainStore__1releaseKeychainItemRef
(JNIEnv *env, jobject this, jlong keychainItem)
{
    SecKeychainItemRef itemToFree = jlong_to_ptr(keychainItem);
    CFRelease(itemToFree);
}
