Path: blob/master/src/java.base/macosx/native/libosxsecurity/KeystoreImpl.m
41119 views
/*1* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425#import "apple_security_KeychainStore.h"26#import "jni_util.h"27#import <Security/Security.h>28#import <Security/SecImportExport.h>29#import <CoreServices/CoreServices.h> // (for require() macros)30#import <Cocoa/Cocoa.h>3132static jstring getLabelFromItem(JNIEnv *env, SecKeychainItemRef inItem)33{34OSStatus status;35jstring returnValue = NULL;36char *attribCString = NULL;3738SecKeychainAttribute itemAttrs[] = { { kSecLabelItemAttr, 0, NULL } };39SecKeychainAttributeList attrList = { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs };4041status = SecKeychainItemCopyContent(inItem, NULL, &attrList, NULL, NULL);4243if(status) {44cssmPerror("getLabelFromItem: SecKeychainItemCopyContent", status);45goto errOut;46}4748attribCString = malloc(itemAttrs[0].length + 1);49if (attribCString == NULL) {50JNU_ThrowOutOfMemoryError(env, "native heap");51goto errOut;52}5354strncpy(attribCString, itemAttrs[0].data, itemAttrs[0].length);55attribCString[itemAttrs[0].length] = '\0';56returnValue = (*env)->NewStringUTF(env, attribCString);5758errOut:59SecKeychainItemFreeContent(&attrList, NULL);60if (attribCString) free(attribCString);61return returnValue;62}6364static jlong getModDateFromItem(JNIEnv *env, SecKeychainItemRef inItem)65{66OSStatus status;67SecKeychainAttribute itemAttrs[] = { { kSecModDateItemAttr, 0, NULL } };68SecKeychainAttributeList attrList = { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs };69jlong returnValue = 0;7071status = SecKeychainItemCopyContent(inItem, NULL, &attrList, NULL, NULL);7273if(status) {74// This is almost always missing, so don't dump an error.75// cssmPerror("getModDateFromItem: SecKeychainItemCopyContent", status);76goto errOut;77}7879memcpy(&returnValue, itemAttrs[0].data, itemAttrs[0].length);8081errOut:82SecKeychainItemFreeContent(&attrList, NULL);83return returnValue;84}8586static void setLabelForItem(NSString *inLabel, SecKeychainItemRef inItem)87{88OSStatus status;89const char *labelCString = [inLabel UTF8String];9091// Set up attribute vector (each attribute consists of {tag, length, pointer}):92SecKeychainAttribute attrs[] = {93{ kSecLabelItemAttr, strlen(labelCString), (void *)labelCString }94};9596const SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]), attrs };9798// Not changing data here, just attributes.99status = SecKeychainItemModifyContent(inItem, &attributes, 0, NULL);100101if(status) {102cssmPerror("setLabelForItem: SecKeychainItemModifyContent", status);103}104}105106/*107* Given a SecIdentityRef, do our best to construct a complete, ordered, and108* verified cert chain, returning the result in a CFArrayRef. The result is109* can be passed back to Java as a chain for a private key.110*/111static OSStatus completeCertChain(112SecIdentityRef identity,113SecCertificateRef trustedAnchor, // optional additional trusted anchor114bool includeRoot, // include the root in outArray115CFArrayRef *outArray) // created and RETURNED116{117SecTrustRef secTrust = NULL;118SecPolicyRef policy = NULL;119SecPolicySearchRef policySearch = NULL;120SecTrustResultType secTrustResult;121CSSM_TP_APPLE_EVIDENCE_INFO *dummyEv; // not used122CFArrayRef certChain = NULL; // constructed chain, CERTS ONLY123CFMutableArrayRef subjCerts; // passed to SecTrust124CFMutableArrayRef certArray; // returned array starting with125// identity126CFIndex numResCerts;127CFIndex dex;128OSStatus ortn;129SecCertificateRef certRef;130131/* First element in out array is the SecIdentity */132certArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);133CFArrayAppendValue(certArray, identity);134135/* the single element in certs-to-be-evaluated comes from the identity */136ortn = SecIdentityCopyCertificate(identity, &certRef);137if(ortn) {138/* should never happen */139cssmPerror("SecIdentityCopyCertificate", ortn);140return ortn;141}142143/*144* Now use SecTrust to get a complete cert chain, using all of the145* user's keychains to look for intermediate certs.146* NOTE this does NOT handle root certs which are not in the system147* root cert DB.148*/149subjCerts = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);150CFArraySetValueAtIndex(subjCerts, 0, certRef);151152/* the array owns the subject cert ref now */153CFRelease(certRef);154155/* Get a SecPolicyRef for generic X509 cert chain verification */156ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3,157&CSSMOID_APPLE_X509_BASIC,158NULL, // value159&policySearch);160if(ortn) {161/* should never happen */162cssmPerror("SecPolicySearchCreate", ortn);163goto errOut;164}165ortn = SecPolicySearchCopyNext(policySearch, &policy);166if(ortn) {167/* should never happen */168cssmPerror("SecPolicySearchCopyNext", ortn);169goto errOut;170}171172/* build a SecTrustRef for specified policy and certs */173ortn = SecTrustCreateWithCertificates(subjCerts,174policy, &secTrust);175if(ortn) {176cssmPerror("SecTrustCreateWithCertificates", ortn);177goto errOut;178}179180if(trustedAnchor) {181/*182* Tell SecTrust to trust this one in addition to the current183* trusted system-wide anchors.184*/185CFMutableArrayRef newAnchors;186CFArrayRef currAnchors;187188ortn = SecTrustCopyAnchorCertificates(&currAnchors);189if(ortn) {190/* should never happen */191cssmPerror("SecTrustCopyAnchorCertificates", ortn);192goto errOut;193}194newAnchors = CFArrayCreateMutableCopy(NULL,195CFArrayGetCount(currAnchors) + 1,196currAnchors);197CFRelease(currAnchors);198CFArrayAppendValue(newAnchors, trustedAnchor);199ortn = SecTrustSetAnchorCertificates(secTrust, newAnchors);200CFRelease(newAnchors);201if(ortn) {202cssmPerror("SecTrustSetAnchorCertificates", ortn);203goto errOut;204}205}206207/* evaluate: GO */208ortn = SecTrustEvaluate(secTrust, &secTrustResult);209if(ortn) {210cssmPerror("SecTrustEvaluate", ortn);211goto errOut;212}213switch(secTrustResult) {214case kSecTrustResultUnspecified:215/* cert chain valid, no special UserTrust assignments; drop thru */216case kSecTrustResultProceed:217/* cert chain valid AND user explicitly trusts this */218break;219default:220/*221* Cert chain construction failed.222* Just go with the single subject cert we were given; maybe the223* peer can complete the chain.224*/225ortn = noErr;226goto errOut;227}228229/* get resulting constructed cert chain */230ortn = SecTrustGetResult(secTrust, &secTrustResult, &certChain, &dummyEv);231if(ortn) {232cssmPerror("SecTrustEvaluate", ortn);233goto errOut;234}235236/*237* Copy certs from constructed chain to our result array, skipping238* the leaf (which is already there, as a SecIdentityRef) and possibly239* a root.240*/241numResCerts = CFArrayGetCount(certChain);242if(numResCerts < 1) {243/*244* Can't happen: If chain doesn't verify to a root, we'd245* have bailed after SecTrustEvaluate().246*/247ortn = noErr;248goto errOut;249}250if(!includeRoot) {251/* skip the last (root) cert) */252numResCerts--;253}254for(dex=1; dex<numResCerts; dex++) {255certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certChain, dex);256CFArrayAppendValue(certArray, certRef);257}258errOut:259/* clean up */260if(secTrust) {261CFRelease(secTrust);262}263if(subjCerts) {264CFRelease(subjCerts);265}266if(policy) {267CFRelease(policy);268}269if(policySearch) {270CFRelease(policySearch);271}272*outArray = certArray;273return ortn;274}275276static void addIdentitiesToKeystore(JNIEnv *env, jobject keyStore)277{278// Search the user keychain list for all identities. Identities are a certificate/private key association that279// can be chosen for a purpose such as signing or an SSL connection.280SecIdentitySearchRef identitySearch = NULL;281// Pass 0 if you want all identities returned by this search282OSStatus err = SecIdentitySearchCreate(NULL, 0, &identitySearch);283SecIdentityRef theIdentity = NULL;284OSErr searchResult = noErr;285286jclass jc_KeychainStore = (*env)->FindClass(env, "apple/security/KeychainStore");287CHECK_NULL(jc_KeychainStore);288jmethodID jm_createKeyEntry = (*env)->GetMethodID(env, jc_KeychainStore, "createKeyEntry", "(Ljava/lang/String;JJ[J[[B)V");289CHECK_NULL(jm_createKeyEntry);290do {291searchResult = SecIdentitySearchCopyNext(identitySearch, &theIdentity);292293if (searchResult == noErr) {294// Get the cert from the identity, then generate a chain.295SecCertificateRef certificate;296SecIdentityCopyCertificate(theIdentity, &certificate);297CFArrayRef certChain = NULL;298299// *** Should do something with this error...300err = completeCertChain(theIdentity, NULL, TRUE, &certChain);301302CFIndex i, certCount = CFArrayGetCount(certChain);303304// Make a java array of certificate data from the chain.305jclass byteArrayClass = (*env)->FindClass(env, "[B");306if (byteArrayClass == NULL) {307goto errOut;308}309jobjectArray javaCertArray = (*env)->NewObjectArray(env, certCount, byteArrayClass, NULL);310// Cleanup first then check for a NULL return code311(*env)->DeleteLocalRef(env, byteArrayClass);312if (javaCertArray == NULL) {313goto errOut;314}315316// And, make an array of the certificate refs.317jlongArray certRefArray = (*env)->NewLongArray(env, certCount);318if (certRefArray == NULL) {319goto errOut;320}321322SecCertificateRef currCertRef = NULL;323324for (i = 0; i < certCount; i++) {325CSSM_DATA currCertData;326327if (i == 0)328currCertRef = certificate;329else330currCertRef = (SecCertificateRef)CFArrayGetValueAtIndex(certChain, i);331332bzero(&currCertData, sizeof(CSSM_DATA));333err = SecCertificateGetData(currCertRef, &currCertData);334jbyteArray encodedCertData = (*env)->NewByteArray(env, currCertData.Length);335if (encodedCertData == NULL) {336goto errOut;337}338(*env)->SetByteArrayRegion(env, encodedCertData, 0, currCertData.Length, (jbyte *)currCertData.Data);339(*env)->SetObjectArrayElement(env, javaCertArray, i, encodedCertData);340jlong certRefElement = ptr_to_jlong(currCertRef);341(*env)->SetLongArrayRegion(env, certRefArray, i, 1, &certRefElement);342}343344// Get the private key. When needed we'll export the data from it later.345SecKeyRef privateKeyRef;346err = SecIdentityCopyPrivateKey(theIdentity, &privateKeyRef);347348// Find the label. It's a 'blob', but we interpret as characters.349jstring alias = getLabelFromItem(env, (SecKeychainItemRef)certificate);350if (alias == NULL) {351goto errOut;352}353354// Find the creation date.355jlong creationDate = getModDateFromItem(env, (SecKeychainItemRef)certificate);356357// Call back to the Java object to create Java objects corresponding to this security object.358jlong nativeKeyRef = ptr_to_jlong(privateKeyRef);359(*env)->CallVoidMethod(env, keyStore, jm_createKeyEntry, alias, creationDate, nativeKeyRef, certRefArray, javaCertArray);360JNU_CHECK_EXCEPTION(env);361}362} while (searchResult == noErr);363364errOut:365if (identitySearch != NULL) {366CFRelease(identitySearch);367}368}369370static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)371{372// Search the user keychain list for all X509 certificates.373SecKeychainSearchRef keychainItemSearch = NULL;374OSStatus err = SecKeychainSearchCreateFromAttributes(NULL, kSecCertificateItemClass, NULL, &keychainItemSearch);375SecKeychainItemRef theItem = NULL;376OSErr searchResult = noErr;377378jclass jc_KeychainStore = (*env)->FindClass(env, "apple/security/KeychainStore");379CHECK_NULL(jc_KeychainStore);380jmethodID jm_createTrustedCertEntry = (*env)->GetMethodID(381env, jc_KeychainStore, "createTrustedCertEntry", "(Ljava/lang/String;JJ[B)V");382CHECK_NULL(jm_createTrustedCertEntry);383do {384searchResult = SecKeychainSearchCopyNext(keychainItemSearch, &theItem);385386if (searchResult == noErr) {387// Make a byte array with the DER-encoded contents of the certificate.388SecCertificateRef certRef = (SecCertificateRef)theItem;389CSSM_DATA currCertificate;390err = SecCertificateGetData(certRef, &currCertificate);391jbyteArray certData = (*env)->NewByteArray(env, currCertificate.Length);392if (certData == NULL) {393goto errOut;394}395(*env)->SetByteArrayRegion(env, certData, 0, currCertificate.Length, (jbyte *)currCertificate.Data);396397// Find the label. It's a 'blob', but we interpret as characters.398jstring alias = getLabelFromItem(env, theItem);399if (alias == NULL) {400goto errOut;401}402403// Find the creation date.404jlong creationDate = getModDateFromItem(env, theItem);405406// Call back to the Java object to create Java objects corresponding to this security object.407jlong nativeRef = ptr_to_jlong(certRef);408(*env)->CallVoidMethod(env, keyStore, jm_createTrustedCertEntry, alias, nativeRef, creationDate, certData);409JNU_CHECK_EXCEPTION(env);410}411} while (searchResult == noErr);412413errOut:414if (keychainItemSearch != NULL) {415CFRelease(keychainItemSearch);416}417}418419/*420* Class: apple_security_KeychainStore421* Method: _getEncodedKeyData422* Signature: (J)[B423*/424JNIEXPORT jbyteArray JNICALL Java_apple_security_KeychainStore__1getEncodedKeyData425(JNIEnv *env, jobject this, jlong keyRefLong, jcharArray passwordObj)426{427SecKeyRef keyRef = (SecKeyRef)jlong_to_ptr(keyRefLong);428SecKeyImportExportParameters paramBlock;429OSStatus err = noErr;430CFDataRef exportedData = NULL;431jbyteArray returnValue = NULL;432CFStringRef passwordStrRef = NULL;433434jsize passwordLen = 0;435jchar *passwordChars = NULL;436437if (passwordObj) {438passwordLen = (*env)->GetArrayLength(env, passwordObj);439440if (passwordLen > 0) {441passwordChars = (*env)->GetCharArrayElements(env, passwordObj, NULL);442if (passwordChars == NULL) {443goto errOut;444}445446passwordStrRef = CFStringCreateWithCharactersNoCopy(NULL, passwordChars, passwordLen, kCFAllocatorNull);447if (passwordStrRef == NULL) {448goto errOut;449}450}451}452453paramBlock.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;454// Note that setting the flags field **requires** you to pass in a password of some kind. The keychain will not prompt you.455paramBlock.flags = 0;456paramBlock.passphrase = passwordStrRef;457paramBlock.alertTitle = NULL;458paramBlock.alertPrompt = NULL;459paramBlock.accessRef = NULL;460paramBlock.keyUsage = CSSM_KEYUSE_ANY;461paramBlock.keyAttributes = CSSM_KEYATTR_RETURN_DEFAULT;462463err = SecKeychainItemExport(keyRef, kSecFormatPKCS12, 0, ¶mBlock, &exportedData);464465if (err == noErr) {466CFIndex size = CFDataGetLength(exportedData);467returnValue = (*env)->NewByteArray(env, size);468if (returnValue == NULL) {469goto errOut;470}471(*env)->SetByteArrayRegion(env, returnValue, 0, size, (jbyte *)CFDataGetBytePtr(exportedData));472}473474errOut:475if (exportedData) CFRelease(exportedData);476if (passwordStrRef) CFRelease(passwordStrRef);477if (passwordChars) {478// clear the password and release479memset(passwordChars, 0, passwordLen);480(*env)->ReleaseCharArrayElements(env, passwordObj, passwordChars,481JNI_ABORT);482}483return returnValue;484}485486487/*488* Class: apple_security_KeychainStore489* Method: _scanKeychain490* Signature: ()V491*/492JNIEXPORT void JNICALL Java_apple_security_KeychainStore__1scanKeychain493(JNIEnv *env, jobject this)494{495// Look for 'identities' -- private key and certificate chain pairs -- and add those.496// Search for these first, because a certificate that's found here as part of an identity will show up497// again later as a certificate.498addIdentitiesToKeystore(env, this);499500JNU_CHECK_EXCEPTION(env);501502// Scan current keychain for trusted certificates.503addCertificatesToKeystore(env, this);504505}506507NSString* JavaStringToNSString(JNIEnv *env, jstring jstr) {508if (jstr == NULL) {509return NULL;510}511jsize len = (*env)->GetStringLength(env, jstr);512const jchar *chars = (*env)->GetStringChars(env, jstr, NULL);513if (chars == NULL) {514return NULL;515}516NSString *result = [NSString stringWithCharacters:(UniChar *)chars length:len];517(*env)->ReleaseStringChars(env, jstr, chars);518return result;519}520521/*522* Class: apple_security_KeychainStore523* Method: _addItemToKeychain524* Signature: (Ljava/lang/String;[B)I525*/526JNIEXPORT jlong JNICALL Java_apple_security_KeychainStore__1addItemToKeychain527(JNIEnv *env, jobject this, jstring alias, jboolean isCertificate, jbyteArray rawDataObj, jcharArray passwordObj)528{529OSStatus err;530jlong returnValue = 0;531532NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; \533@try {534jsize dataSize = (*env)->GetArrayLength(env, rawDataObj);535jbyte *rawData = (*env)->GetByteArrayElements(env, rawDataObj, NULL);536if (rawData == NULL) {537goto errOut;538}539540CFDataRef cfDataToImport = CFDataCreate(kCFAllocatorDefault, (UInt8 *)rawData, dataSize);541CFArrayRef createdItems = NULL;542543SecKeychainRef defaultKeychain = NULL;544SecKeychainCopyDefault(&defaultKeychain);545546SecExternalFormat dataFormat = (isCertificate == JNI_TRUE ? kSecFormatX509Cert : kSecFormatWrappedPKCS8);547548// Convert the password obj into a CFStringRef that the keychain importer can use for encryption.549SecKeyImportExportParameters paramBlock;550CFStringRef passwordStrRef = NULL;551552jsize passwordLen = 0;553jchar *passwordChars = NULL;554555if (passwordObj) {556passwordLen = (*env)->GetArrayLength(env, passwordObj);557558if (passwordLen > 0) {559passwordChars = (*env)->GetCharArrayElements(env, passwordObj, NULL);560if (passwordChars == NULL) {561goto errOut;562}563564passwordStrRef = CFStringCreateWithCharactersNoCopy(NULL, passwordChars, passwordLen, kCFAllocatorNull);565if (passwordStrRef == NULL) {566goto errOut;567}568}569}570571paramBlock.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;572// Note that setting the flags field **requires** you to pass in a password of some kind. The keychain will not prompt you.573paramBlock.flags = 0;574paramBlock.passphrase = passwordStrRef;575paramBlock.alertTitle = NULL;576paramBlock.alertPrompt = NULL;577paramBlock.accessRef = NULL;578paramBlock.keyUsage = CSSM_KEYUSE_ANY;579paramBlock.keyAttributes = CSSM_KEYATTR_RETURN_DEFAULT;580581err = SecKeychainItemImport(cfDataToImport, NULL, &dataFormat, NULL,5820, ¶mBlock, defaultKeychain, &createdItems);583if (cfDataToImport != NULL) {584CFRelease(cfDataToImport);585}586587if (err == noErr) {588SecKeychainItemRef anItem = (SecKeychainItemRef)CFArrayGetValueAtIndex(createdItems, 0);589590// Don't bother labeling keys. They become part of an identity, and are not an accessible part of the keychain.591if (CFGetTypeID(anItem) == SecCertificateGetTypeID()) {592setLabelForItem(JavaStringToNSString(env, alias), anItem);593}594595// Retain the item, since it will be released once when the array holding it gets released.596CFRetain(anItem);597returnValue = ptr_to_jlong(anItem);598} else {599cssmPerror("_addItemToKeychain: SecKeychainItemImport", err);600}601602if (createdItems != NULL) {603CFRelease(createdItems);604}605606errOut:607if (rawData) {608(*env)->ReleaseByteArrayElements(env, rawDataObj, rawData, JNI_ABORT);609}610611if (passwordStrRef) CFRelease(passwordStrRef);612if (passwordChars) {613// clear the password and release614memset(passwordChars, 0, passwordLen);615(*env)->ReleaseCharArrayElements(env, passwordObj, passwordChars,616JNI_ABORT);617}618} @catch (NSException *e) {619NSLog(@"%@", [e callStackSymbols]);620} @finally {621[pool drain];622}623return returnValue;624}625626/*627* Class: apple_security_KeychainStore628* Method: _removeItemFromKeychain629* Signature: (J)I630*/631JNIEXPORT jint JNICALL Java_apple_security_KeychainStore__1removeItemFromKeychain632(JNIEnv *env, jobject this, jlong keychainItem)633{634SecKeychainItemRef itemToRemove = jlong_to_ptr(keychainItem);635return SecKeychainItemDelete(itemToRemove);636}637638/*639* Class: apple_security_KeychainStore640* Method: _releaseKeychainItemRef641* Signature: (J)V642*/643JNIEXPORT void JNICALL Java_apple_security_KeychainStore__1releaseKeychainItemRef644(JNIEnv *env, jobject this, jlong keychainItem)645{646SecKeychainItemRef itemToFree = jlong_to_ptr(keychainItem);647CFRelease(itemToFree);648}649650651