1 /* 2 * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 #import "apple_security_KeychainStore.h" 27 #import "jni_util.h" 28 29 #import <Security/Security.h> 30 #import <Security/SecImportExport.h> 31 #import <CoreServices/CoreServices.h> // (for require() macros) 32 #import <JavaNativeFoundation/JavaNativeFoundation.h> 33 34 35 static JNF_CLASS_CACHE(jc_KeychainStore, "apple/security/KeychainStore"); 36 static JNF_MEMBER_CACHE(jm_createTrustedCertEntry, jc_KeychainStore, "createTrustedCertEntry", "(Ljava/lang/String;JJ[B)V"); 37 static JNF_MEMBER_CACHE(jm_createKeyEntry, jc_KeychainStore, "createKeyEntry", "(Ljava/lang/String;JJ[J[[B)V"); 38 39 static jstring getLabelFromItem(JNIEnv *env, SecKeychainItemRef inItem) 40 { 41 OSStatus status; 42 jstring returnValue = NULL; 43 char *attribCString = NULL; 44 45 SecKeychainAttribute itemAttrs[] = { { kSecLabelItemAttr, 0, NULL } }; 46 SecKeychainAttributeList attrList = { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs }; 47 48 status = SecKeychainItemCopyContent(inItem, NULL, &attrList, NULL, NULL); 49 50 if(status) { 51 cssmPerror("getLabelFromItem: SecKeychainItemCopyContent", status); 52 goto errOut; 53 } 54 55 attribCString = malloc(itemAttrs[0].length + 1); 56 if (attribCString == NULL) { 57 JNU_ThrowOutOfMemoryError(env, "native heap"); 58 goto errOut; 59 } 60 61 strncpy(attribCString, itemAttrs[0].data, itemAttrs[0].length); 62 attribCString[itemAttrs[0].length] = '\0'; 63 returnValue = (*env)->NewStringUTF(env, attribCString); 64 65 errOut: 66 SecKeychainItemFreeContent(&attrList, NULL); 67 if (attribCString) free(attribCString); 68 return returnValue; 69 } 70 71 static jlong getModDateFromItem(JNIEnv *env, SecKeychainItemRef inItem) 72 { 73 OSStatus status; 74 SecKeychainAttribute itemAttrs[] = { { kSecModDateItemAttr, 0, NULL } }; 75 SecKeychainAttributeList attrList = { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs }; 76 jlong returnValue = 0; 77 78 status = SecKeychainItemCopyContent(inItem, NULL, &attrList, NULL, NULL); 79 80 if(status) { 81 // This is almost always missing, so don't dump an error. 82 // cssmPerror("getModDateFromItem: SecKeychainItemCopyContent", status); 83 goto errOut; 84 } 85 86 memcpy(&returnValue, itemAttrs[0].data, itemAttrs[0].length); 87 88 errOut: 89 SecKeychainItemFreeContent(&attrList, NULL); 90 return returnValue; 91 } 92 93 static void setLabelForItem(NSString *inLabel, SecKeychainItemRef inItem) 94 { 95 OSStatus status; 96 const char *labelCString = [inLabel UTF8String]; 97 98 // Set up attribute vector (each attribute consists of {tag, length, pointer}): 99 SecKeychainAttribute attrs[] = { 100 { kSecLabelItemAttr, strlen(labelCString), (void *)labelCString } 101 }; 102 103 const SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]), attrs }; 104 105 // Not changing data here, just attributes. 106 status = SecKeychainItemModifyContent(inItem, &attributes, 0, NULL); 107 108 if(status) { 109 cssmPerror("setLabelForItem: SecKeychainItemModifyContent", status); 110 } 111 } 112 113 /* 114 * Given a SecIdentityRef, do our best to construct a complete, ordered, and 115 * verified cert chain, returning the result in a CFArrayRef. The result is 116 * can be passed back to Java as a chain for a private key. 117 */ 118 static OSStatus completeCertChain( 119 SecIdentityRef identity, 120 SecCertificateRef trustedAnchor, // optional additional trusted anchor 121 bool includeRoot, // include the root in outArray 122 CFArrayRef *outArray) // created and RETURNED 123 { 124 SecTrustRef secTrust = NULL; 125 SecPolicyRef policy = NULL; 126 SecPolicySearchRef policySearch = NULL; 127 SecTrustResultType secTrustResult; 128 CSSM_TP_APPLE_EVIDENCE_INFO *dummyEv; // not used 129 CFArrayRef certChain = NULL; // constructed chain, CERTS ONLY 130 CFMutableArrayRef subjCerts; // passed to SecTrust 131 CFMutableArrayRef certArray; // returned array starting with 132 // identity 133 CFIndex numResCerts; 134 CFIndex dex; 135 OSStatus ortn; 136 SecCertificateRef certRef; 137 138 /* First element in out array is the SecIdentity */ 139 certArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 140 CFArrayAppendValue(certArray, identity); 141 142 /* the single element in certs-to-be-evaluated comes from the identity */ 143 ortn = SecIdentityCopyCertificate(identity, &certRef); 144 if(ortn) { 145 /* should never happen */ 146 cssmPerror("SecIdentityCopyCertificate", ortn); 147 return ortn; 148 } 149 150 /* 151 * Now use SecTrust to get a complete cert chain, using all of the 152 * user's keychains to look for intermediate certs. 153 * NOTE this does NOT handle root certs which are not in the system 154 * root cert DB. 155 */ 156 subjCerts = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks); 157 CFArraySetValueAtIndex(subjCerts, 0, certRef); 158 159 /* the array owns the subject cert ref now */ 160 CFRelease(certRef); 161 162 /* Get a SecPolicyRef for generic X509 cert chain verification */ 163 ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3, 164 &CSSMOID_APPLE_X509_BASIC, 165 NULL, // value 166 &policySearch); 167 if(ortn) { 168 /* should never happen */ 169 cssmPerror("SecPolicySearchCreate", ortn); 170 goto errOut; 171 } 172 ortn = SecPolicySearchCopyNext(policySearch, &policy); 173 if(ortn) { 174 /* should never happen */ 175 cssmPerror("SecPolicySearchCopyNext", ortn); 176 goto errOut; 177 } 178 179 /* build a SecTrustRef for specified policy and certs */ 180 ortn = SecTrustCreateWithCertificates(subjCerts, 181 policy, &secTrust); 182 if(ortn) { 183 cssmPerror("SecTrustCreateWithCertificates", ortn); 184 goto errOut; 185 } 186 187 if(trustedAnchor) { 188 /* 189 * Tell SecTrust to trust this one in addition to the current 190 * trusted system-wide anchors. 191 */ 192 CFMutableArrayRef newAnchors; 193 CFArrayRef currAnchors; 194 195 ortn = SecTrustCopyAnchorCertificates(&currAnchors); 196 if(ortn) { 197 /* should never happen */ 198 cssmPerror("SecTrustCopyAnchorCertificates", ortn); 199 goto errOut; 200 } 201 newAnchors = CFArrayCreateMutableCopy(NULL, 202 CFArrayGetCount(currAnchors) + 1, 203 currAnchors); 204 CFRelease(currAnchors); 205 CFArrayAppendValue(newAnchors, trustedAnchor); 206 ortn = SecTrustSetAnchorCertificates(secTrust, newAnchors); 207 CFRelease(newAnchors); 208 if(ortn) { 209 cssmPerror("SecTrustSetAnchorCertificates", ortn); 210 goto errOut; 211 } 212 } 213 214 /* evaluate: GO */ 215 ortn = SecTrustEvaluate(secTrust, &secTrustResult); 216 if(ortn) { 217 cssmPerror("SecTrustEvaluate", ortn); 218 goto errOut; 219 } 220 switch(secTrustResult) { 221 case kSecTrustResultUnspecified: 222 /* cert chain valid, no special UserTrust assignments; drop thru */ 223 case kSecTrustResultProceed: 224 /* cert chain valid AND user explicitly trusts this */ 225 break; 226 default: 227 /* 228 * Cert chain construction failed. 229 * Just go with the single subject cert we were given; maybe the 230 * peer can complete the chain. 231 */ 232 ortn = noErr; 233 goto errOut; 234 } 235 236 /* get resulting constructed cert chain */ 237 ortn = SecTrustGetResult(secTrust, &secTrustResult, &certChain, &dummyEv); 238 if(ortn) { 239 cssmPerror("SecTrustEvaluate", ortn); 240 goto errOut; 241 } 242 243 /* 244 * Copy certs from constructed chain to our result array, skipping 245 * the leaf (which is already there, as a SecIdentityRef) and possibly 246 * a root. 247 */ 248 numResCerts = CFArrayGetCount(certChain); 249 if(numResCerts < 1) { 250 /* 251 * Can't happen: If chain doesn't verify to a root, we'd 252 * have bailed after SecTrustEvaluate(). 253 */ 254 ortn = noErr; 255 goto errOut; 256 } 257 if(!includeRoot) { 258 /* skip the last (root) cert) */ 259 numResCerts--; 260 } 261 for(dex=1; dex<numResCerts; dex++) { 262 certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certChain, dex); 263 CFArrayAppendValue(certArray, certRef); 264 } 265 errOut: 266 /* clean up */ 267 if(secTrust) { 268 CFRelease(secTrust); 269 } 270 if(subjCerts) { 271 CFRelease(subjCerts); 272 } 273 if(policy) { 274 CFRelease(policy); 275 } 276 if(policySearch) { 277 CFRelease(policySearch); 278 } 279 *outArray = certArray; 280 return ortn; 281 } 282 283 static void addIdentitiesToKeystore(JNIEnv *env, jobject keyStore) 284 { 285 // Search the user keychain list for all identities. Identities are a certificate/private key association that 286 // can be chosen for a purpose such as signing or an SSL connection. 287 SecIdentitySearchRef identitySearch = NULL; 288 // Pass 0 if you want all identities returned by this search 289 OSStatus err = SecIdentitySearchCreate(NULL, 0, &identitySearch); 290 SecIdentityRef theIdentity = NULL; 291 OSErr searchResult = noErr; 292 293 do { 294 searchResult = SecIdentitySearchCopyNext(identitySearch, &theIdentity); 295 296 if (searchResult == noErr) { 297 // Get the cert from the identity, then generate a chain. 298 SecCertificateRef certificate; 299 SecIdentityCopyCertificate(theIdentity, &certificate); 300 CFArrayRef certChain = NULL; 301 302 // *** Should do something with this error... 303 err = completeCertChain(theIdentity, NULL, TRUE, &certChain); 304 305 CFIndex i, certCount = CFArrayGetCount(certChain); 306 307 // Make a java array of certificate data from the chain. 308 jclass byteArrayClass = (*env)->FindClass(env, "[B"); 309 if (byteArrayClass == NULL) { 310 goto errOut; 311 } 312 jobjectArray javaCertArray = (*env)->NewObjectArray(env, certCount, byteArrayClass, NULL); 313 // Cleanup first then check for a NULL return code 314 (*env)->DeleteLocalRef(env, byteArrayClass); 315 if (javaCertArray == NULL) { 316 goto errOut; 317 } 318 319 // And, make an array of the certificate refs. 320 jlongArray certRefArray = (*env)->NewLongArray(env, certCount); 321 if (certRefArray == NULL) { 322 goto errOut; 323 } 324 325 SecCertificateRef currCertRef = NULL; 326 327 for (i = 0; i < certCount; i++) { 328 CSSM_DATA currCertData; 329 330 if (i == 0) 331 currCertRef = certificate; 332 else 333 currCertRef = (SecCertificateRef)CFArrayGetValueAtIndex(certChain, i); 334 335 bzero(&currCertData, sizeof(CSSM_DATA)); 336 err = SecCertificateGetData(currCertRef, &currCertData); 337 jbyteArray encodedCertData = (*env)->NewByteArray(env, currCertData.Length); 338 if (encodedCertData == NULL) { 339 goto errOut; 340 } 341 (*env)->SetByteArrayRegion(env, encodedCertData, 0, currCertData.Length, (jbyte *)currCertData.Data); 342 (*env)->SetObjectArrayElement(env, javaCertArray, i, encodedCertData); 343 jlong certRefElement = ptr_to_jlong(currCertRef); 344 (*env)->SetLongArrayRegion(env, certRefArray, i, 1, &certRefElement); 345 } 346 347 // Get the private key. When needed we'll export the data from it later. 348 SecKeyRef privateKeyRef; 349 err = SecIdentityCopyPrivateKey(theIdentity, &privateKeyRef); 350 351 // Find the label. It's a 'blob', but we interpret as characters. 352 jstring alias = getLabelFromItem(env, (SecKeychainItemRef)certificate); 353 if (alias == NULL) { 354 goto errOut; 355 } 356 357 // Find the creation date. 358 jlong creationDate = getModDateFromItem(env, (SecKeychainItemRef)certificate); 359 360 // Call back to the Java object to create Java objects corresponding to this security object. 361 jlong nativeKeyRef = ptr_to_jlong(privateKeyRef); 362 JNFCallVoidMethod(env, keyStore, jm_createKeyEntry, alias, creationDate, nativeKeyRef, certRefArray, javaCertArray); 363 } 364 } while (searchResult == noErr); 365 366 errOut: 367 if (identitySearch != NULL) { 368 CFRelease(identitySearch); 369 } 370 } 371 372 static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore) 373 { 374 // Search the user keychain list for all X509 certificates. 375 SecKeychainSearchRef keychainItemSearch = NULL; 376 OSStatus err = SecKeychainSearchCreateFromAttributes(NULL, kSecCertificateItemClass, NULL, &keychainItemSearch); 377 SecKeychainItemRef theItem = NULL; 378 OSErr searchResult = noErr; 379 380 do { 381 searchResult = SecKeychainSearchCopyNext(keychainItemSearch, &theItem); 382 383 if (searchResult == noErr) { 384 // Make a byte array with the DER-encoded contents of the certificate. 385 SecCertificateRef certRef = (SecCertificateRef)theItem; 386 CSSM_DATA currCertificate; 387 err = SecCertificateGetData(certRef, &currCertificate); 388 jbyteArray certData = (*env)->NewByteArray(env, currCertificate.Length); 389 if (certData == NULL) { 390 goto errOut; 391 } 392 (*env)->SetByteArrayRegion(env, certData, 0, currCertificate.Length, (jbyte *)currCertificate.Data); 393 394 // Find the label. It's a 'blob', but we interpret as characters. 395 jstring alias = getLabelFromItem(env, theItem); 396 if (alias == NULL) { 397 goto errOut; 398 } 399 400 // Find the creation date. 401 jlong creationDate = getModDateFromItem(env, theItem); 402 403 // Call back to the Java object to create Java objects corresponding to this security object. 404 jlong nativeRef = ptr_to_jlong(certRef); 405 JNFCallVoidMethod(env, keyStore, jm_createTrustedCertEntry, alias, nativeRef, creationDate, certData); 406 } 407 } while (searchResult == noErr); 408 409 errOut: 410 if (keychainItemSearch != NULL) { 411 CFRelease(keychainItemSearch); 412 } 413 } 414 415 /* 416 * Class: apple_security_KeychainStore 417 * Method: _getEncodedKeyData 418 * Signature: (J)[B 419 */ 420 JNIEXPORT jbyteArray JNICALL Java_apple_security_KeychainStore__1getEncodedKeyData 421 (JNIEnv *env, jobject this, jlong keyRefLong, jcharArray passwordObj) 422 { 423 SecKeyRef keyRef = (SecKeyRef)jlong_to_ptr(keyRefLong); 424 SecKeyImportExportParameters paramBlock; 425 OSStatus err = noErr; 426 CFDataRef exportedData = NULL; 427 jbyteArray returnValue = NULL; 428 CFStringRef passwordStrRef = NULL; 429 430 jsize passwordLen = 0; 431 jchar *passwordChars = NULL; 432 433 if (passwordObj) { 434 passwordLen = (*env)->GetArrayLength(env, passwordObj); 435 436 if (passwordLen > 0) { 437 passwordChars = (*env)->GetCharArrayElements(env, passwordObj, NULL); 438 if (passwordChars == NULL) { 439 goto errOut; 440 } 441 442 passwordStrRef = CFStringCreateWithCharactersNoCopy(NULL, passwordChars, passwordLen, kCFAllocatorNull); 443 if (passwordStrRef == NULL) { 444 goto errOut; 445 } 446 } 447 } 448 449 paramBlock.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; 450 // Note that setting the flags field **requires** you to pass in a password of some kind. The keychain will not prompt you. 451 paramBlock.flags = 0; 452 paramBlock.passphrase = passwordStrRef; 453 paramBlock.alertTitle = NULL; 454 paramBlock.alertPrompt = NULL; 455 paramBlock.accessRef = NULL; 456 paramBlock.keyUsage = CSSM_KEYUSE_ANY; 457 paramBlock.keyAttributes = CSSM_KEYATTR_RETURN_DEFAULT; 458 459 err = SecKeychainItemExport(keyRef, kSecFormatPKCS12, 0, ¶mBlock, &exportedData); 460 461 if (err == noErr) { 462 CFIndex size = CFDataGetLength(exportedData); 463 returnValue = (*env)->NewByteArray(env, size); 464 if (returnValue == NULL) { 465 goto errOut; 466 } 467 (*env)->SetByteArrayRegion(env, returnValue, 0, size, (jbyte *)CFDataGetBytePtr(exportedData)); 468 } 469 470 errOut: 471 if (exportedData) CFRelease(exportedData); 472 if (passwordStrRef) CFRelease(passwordStrRef); 473 if (passwordChars) { 474 // clear the password and release 475 memset(passwordChars, 0, passwordLen); 476 (*env)->ReleaseCharArrayElements(env, passwordObj, passwordChars, 477 JNI_ABORT); 478 } 479 return returnValue; 480 } 481 482 483 /* 484 * Class: apple_security_KeychainStore 485 * Method: _scanKeychain 486 * Signature: ()V 487 */ 488 JNIEXPORT void JNICALL Java_apple_security_KeychainStore__1scanKeychain 489 (JNIEnv *env, jobject this) 490 { 491 // Look for 'identities' -- private key and certificate chain pairs -- and add those. 492 // Search for these first, because a certificate that's found here as part of an identity will show up 493 // again later as a certificate. 494 addIdentitiesToKeystore(env, this); 495 496 JNU_CHECK_EXCEPTION(env); 497 498 // Scan current keychain for trusted certificates. 499 addCertificatesToKeystore(env, this); 500 501 } 502 503 /* 504 * Class: apple_security_KeychainStore 505 * Method: _addItemToKeychain 506 * Signature: (Ljava/lang/String;[B)I 507 */ 508 JNIEXPORT jlong JNICALL Java_apple_security_KeychainStore__1addItemToKeychain 509 (JNIEnv *env, jobject this, jstring alias, jboolean isCertificate, jbyteArray rawDataObj, jcharArray passwordObj) 510 { 511 OSStatus err; 512 jlong returnValue = 0; 513 514 JNF_COCOA_ENTER(env); 515 516 jsize dataSize = (*env)->GetArrayLength(env, rawDataObj); 517 jbyte *rawData = (*env)->GetByteArrayElements(env, rawDataObj, NULL); 518 if (rawData == NULL) { 519 goto errOut; 520 } 521 522 CFDataRef cfDataToImport = CFDataCreate(kCFAllocatorDefault, (UInt8 *)rawData, dataSize); 523 CFArrayRef createdItems = NULL; 524 525 SecKeychainRef defaultKeychain = NULL; 526 SecKeychainCopyDefault(&defaultKeychain); 527 528 SecExternalFormat dataFormat = (isCertificate == JNI_TRUE ? kSecFormatX509Cert : kSecFormatWrappedPKCS8); 529 530 // Convert the password obj into a CFStringRef that the keychain importer can use for encryption. 531 SecKeyImportExportParameters paramBlock; 532 CFStringRef passwordStrRef = NULL; 533 534 jsize passwordLen = 0; 535 jchar *passwordChars = NULL; 536 537 if (passwordObj) { 538 passwordLen = (*env)->GetArrayLength(env, passwordObj); 539 540 if (passwordLen > 0) { 541 passwordChars = (*env)->GetCharArrayElements(env, passwordObj, NULL); 542 if (passwordChars == NULL) { 543 goto errOut; 544 } 545 546 passwordStrRef = CFStringCreateWithCharactersNoCopy(NULL, passwordChars, passwordLen, kCFAllocatorNull); 547 if (passwordStrRef == NULL) { 548 goto errOut; 549 } 550 } 551 } 552 553 paramBlock.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; 554 // Note that setting the flags field **requires** you to pass in a password of some kind. The keychain will not prompt you. 555 paramBlock.flags = 0; 556 paramBlock.passphrase = passwordStrRef; 557 paramBlock.alertTitle = NULL; 558 paramBlock.alertPrompt = NULL; 559 paramBlock.accessRef = NULL; 560 paramBlock.keyUsage = CSSM_KEYUSE_ANY; 561 paramBlock.keyAttributes = CSSM_KEYATTR_RETURN_DEFAULT; 562 563 err = SecKeychainItemImport(cfDataToImport, NULL, &dataFormat, NULL, 564 0, ¶mBlock, defaultKeychain, &createdItems); 565 if (cfDataToImport != NULL) { 566 CFRelease(cfDataToImport); 567 } 568 569 if (err == noErr) { 570 SecKeychainItemRef anItem = (SecKeychainItemRef)CFArrayGetValueAtIndex(createdItems, 0); 571 572 // Don't bother labeling keys. They become part of an identity, and are not an accessible part of the keychain. 573 if (CFGetTypeID(anItem) == SecCertificateGetTypeID()) { 574 setLabelForItem(JNFJavaToNSString(env, alias), anItem); 575 } 576 577 // Retain the item, since it will be released once when the array holding it gets released. 578 CFRetain(anItem); 579 returnValue = ptr_to_jlong(anItem); 580 } else { 581 cssmPerror("_addItemToKeychain: SecKeychainItemImport", err); 582 } 583 584 if (createdItems != NULL) { 585 CFRelease(createdItems); 586 } 587 588 errOut: 589 if (rawData) { 590 (*env)->ReleaseByteArrayElements(env, rawDataObj, rawData, JNI_ABORT); 591 } 592 593 if (passwordStrRef) CFRelease(passwordStrRef); 594 if (passwordChars) { 595 // clear the password and release 596 memset(passwordChars, 0, passwordLen); 597 (*env)->ReleaseCharArrayElements(env, passwordObj, passwordChars, 598 JNI_ABORT); 599 } 600 601 JNF_COCOA_EXIT(env); 602 603 return returnValue; 604 } 605 606 /* 607 * Class: apple_security_KeychainStore 608 * Method: _removeItemFromKeychain 609 * Signature: (J)I 610 */ 611 JNIEXPORT jint JNICALL Java_apple_security_KeychainStore__1removeItemFromKeychain 612 (JNIEnv *env, jobject this, jlong keychainItem) 613 { 614 SecKeychainItemRef itemToRemove = jlong_to_ptr(keychainItem); 615 return SecKeychainItemDelete(itemToRemove); 616 } 617 618 /* 619 * Class: apple_security_KeychainStore 620 * Method: _releaseKeychainItemRef 621 * Signature: (J)V 622 */ 623 JNIEXPORT void JNICALL Java_apple_security_KeychainStore__1releaseKeychainItemRef 624 (JNIEnv *env, jobject this, jlong keychainItem) 625 { 626 SecKeychainItemRef itemToFree = jlong_to_ptr(keychainItem); 627 CFRelease(itemToFree); 628 }