2 * Copyright (c) 2005 David Kocher. All rights reserved.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * Bug fixes, suggestions and comments should be sent to:
16 * dkocher@cyberduck.ch
20 #import <Security/Security.h>
21 #import <SecurityInterface/SFCertificatePanel.h>
22 #import <SecurityInterface/SFCertificateTrustPanel.h>
23 #import <SecurityInterface/SFChooseIdentityPanel.h>
24 #import <JavaNativeFoundation/JNFString.h>
26 #import "EMKeychainProxy.h"
27 #import "EMKeychainItem.h"
29 SecCertificateRef CreateCertificateFromData(JNIEnv *env, jbyteArray jCertificate);
30 NSArray* CreateCertificatesFromData(JNIEnv * env, jobjectArray jCertificateChain);
32 OSStatus CreateSSLClientPolicy(SecPolicyRef* policy);
33 OSStatus CreatePolicy(const CSSM_OID* policy_OID,
36 SecPolicyRef* policy);
38 SecProtocolType convertToSecProtocolType(JNIEnv *env, jstring jProtocol)
40 if([JNFJavaToNSString(env, jProtocol) isEqualTo: @"ftp"]) {
41 return kSecProtocolTypeFTP;
43 if([JNFJavaToNSString(env, jProtocol) isEqualTo: @"ftps"]) {
44 return kSecProtocolTypeFTPS;
46 if([JNFJavaToNSString(env, jProtocol) isEqualTo: @"sftp"]) {
47 return kSecProtocolTypeSSH;
49 if([JNFJavaToNSString(env, jProtocol) isEqualTo: @"http"]) {
50 return kSecProtocolTypeHTTP;
52 if([JNFJavaToNSString(env, jProtocol) isEqualTo: @"https"]) {
53 return kSecProtocolTypeHTTPS;
55 return kSecProtocolTypeAny;
58 JNIEXPORT jstring JNICALL Java_ch_cyberduck_core_Keychain_getInternetPasswordFromKeychain(JNIEnv *env, jobject this, jstring jProtocol,
59 jint port, jstring jService,jstring jUsername) {
61 EMInternetKeychainItem *keychainItem = [[EMKeychainProxy sharedProxy] internetKeychainItemForServer:JNFJavaToNSString(env, jService)
62 withUsername:JNFJavaToNSString(env, jUsername)
65 protocol:convertToSecProtocolType(env, jProtocol)];
66 return (*env)->NewStringUTF(env, [[keychainItem password] UTF8String]);
69 JNIEXPORT jstring JNICALL Java_ch_cyberduck_core_Keychain_getPasswordFromKeychain(JNIEnv *env, jobject this, jstring jService, jstring jUsername) {
70 EMGenericKeychainItem *keychainItem = [[EMKeychainProxy sharedProxy] genericKeychainItemForService:JNFJavaToNSString(env, jService)
71 withUsername:JNFJavaToNSString(env, jUsername)];
72 return (*env)->NewStringUTF(env, [[keychainItem password] UTF8String]);
75 JNIEXPORT void JNICALL Java_ch_cyberduck_core_Keychain_addInternetPasswordToKeychain(JNIEnv *env, jobject this, jstring jProtocol, jint port,
76 jstring jService, jstring jUsername, jstring jPassword) {
77 [[EMKeychainProxy sharedProxy] addInternetKeychainItemForServer:JNFJavaToNSString(env, jService)
78 withUsername:JNFJavaToNSString(env, jUsername)
79 password:JNFJavaToNSString(env, jPassword)
82 protocol:convertToSecProtocolType(env, jProtocol)];
85 JNIEXPORT void JNICALL Java_ch_cyberduck_core_Keychain_addPasswordToKeychain(JNIEnv *env, jobject this, jstring jService, jstring jUsername, jstring jPass) {
86 [[EMKeychainProxy sharedProxy] addGenericKeychainItemForService:JNFJavaToNSString(env, jService)
87 withUsername:JNFJavaToNSString(env, jUsername)
88 password:JNFJavaToNSString(env, jPass)];
91 jbyteArray GetCertData(JNIEnv *env, SecCertificateRef certificateRef) {
93 OSStatus err = SecCertificateGetData(certificateRef, &cssmData);
98 jb=(*env)->NewByteArray(env, cssmData.Length);
99 (*env)->SetByteArrayRegion(env, jb, 0, cssmData.Length, (jbyte *)cssmData.Data);
103 JNIEXPORT jbyteArray Java_ch_cyberduck_core_Keychain_chooseCertificateNative(JNIEnv *env, jobject this,
104 jobjectArray jCertificates,
108 NSMutableArray *identities = [NSMutableArray array];
110 for(i = 0; i < (*env)->GetArrayLength(env, jCertificates); i++) {
111 SecCertificateRef certificate = CreateCertificateFromData(env, (*env)->GetObjectArrayElement(env, jCertificates, i));
112 SecIdentityRef identity;
113 // If the associated private key is not found in one of the specified keychains, this function fails with an appropriate error code (usually errSecItemNotFound), and does not return anything in the identityRef parameter.
114 status = SecIdentityCreateWithCertificate(NULL, certificate, &identity);
115 if(status == noErr) {
116 [identities addObject:(id)identity];
118 CFRelease(certificate);
120 SFChooseIdentityPanel *panel = [[SFChooseIdentityPanel alloc] init];
121 [panel setShowsHelp:NO];
122 [panel setDomain:JNFJavaToNSString(env, jHostname)];
123 [panel setAlternateButtonTitle:NSLocalizedString(@"Disconnect", @"")];
124 [panel setInformativeText:JNFJavaToNSString(env, jPrompt)];
125 // Create an SSL policy ref configured for client cert evaluation. Policy will require the specified value
126 // to match the host name in the leaf certificate
127 SecPolicyRef policy = SecPolicyCreateSSL(false, (CFStringRef)JNFJavaToNSString(env, jHostname));
129 [panel setPolicies:(id)policy];
132 if([panel runModalForIdentities:identities message:NSLocalizedString(@"Choose", @"")] == NSOKButton) {
133 SecIdentityRef identity = [panel identity];
135 SecCertificateRef certificate;
136 if(SecIdentityCopyCertificate(identity, &certificate) == noErr) {
137 jbyteArray der = GetCertData(env, certificate);
138 CFRelease(certificate);
148 SecCertificateRef CreateCertificateFromData(JNIEnv *env, jbyteArray jCertificate) {
150 jbyte *der = (*env)->GetByteArrayElements(env, jCertificate, NULL);
151 // Creates a certificate object based on the specified data, type, and encoding.
152 NSData *certData = [NSData dataWithBytes:der length:(*env)->GetArrayLength(env, jCertificate)];
153 (*env)->ReleaseByteArrayElements(env, jCertificate, der, 0);
154 CSSM_DATA *cssmData = NULL;
156 cssmData = (CSSM_DATA*)malloc(sizeof(CSSM_DATA));
157 cssmData->Length = [certData length];
158 cssmData->Data = (uint8*)malloc(cssmData->Length);
159 [certData getBytes:(char*)(cssmData->Data)];
161 SecCertificateRef certificateRef = NULL;
162 err = SecCertificateCreateFromData(cssmData, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER, &certificateRef);
164 free(cssmData->Data);
168 NSLog(@"Error creating certificate from data");
171 return certificateRef;
174 NSArray* CreateCertificatesFromData(JNIEnv *env, jobjectArray jCertificates) {
175 NSMutableArray *result = [NSMutableArray arrayWithCapacity:(*env)->GetArrayLength(env, jCertificates)];
177 for(i = 0; i < (*env)->GetArrayLength(env, jCertificates); i++) {
178 jbyteArray jCertificate = (jbyteArray)(*env)->GetObjectArrayElement(env, jCertificates, i);
179 SecCertificateRef ref = CreateCertificateFromData(env, jCertificate);
181 NSLog(@"Error creating certificate from ASN.1 DER");
184 [result addObject:(id)ref];
189 JNIEXPORT jboolean JNICALL Java_ch_cyberduck_core_Keychain_isTrustedNative(JNIEnv *env, jobject this, jstring jHostname, jobjectArray jCertificates) {
191 NSArray *certificates = CreateCertificatesFromData(env, jCertificates);
192 // Creates a search object for finding policies.
193 SecPolicySearchRef searchRef = NULL;
194 err = SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_SSL, NULL, &searchRef);
196 NSLog(@"Error creating policy");
199 // Retrieves a policy object for the next policy matching specified search criteria.
200 SecPolicyRef policyRef = NULL;
201 err = SecPolicySearchCopyNext(searchRef, &policyRef);
203 NSLog(@"Error retrieving policy");
205 CFRelease(searchRef);
210 CFRelease(searchRef);
215 NSString *hostname = JNFJavaToNSString(env, jHostname);
216 // Returns NULL if the receiver cannot be losslessly converted to encoding.
217 const char *cHostname = [hostname cStringUsingEncoding:NSASCIIStringEncoding];
219 NSLog(@"Error adding hostname to SSL options");
222 CSSM_APPLE_TP_SSL_OPTIONS ssloptions = {
223 .Version = CSSM_APPLE_TP_SSL_OPTS_VERSION,
224 .ServerNameLen = strlen(cHostname),
225 .ServerName = cHostname,
228 CSSM_DATA customCssmData = {
229 .Length = sizeof(ssloptions),
230 .Data = (uint8*)&ssloptions
232 err = SecPolicySetValue(policyRef, &customCssmData);
234 NSLog(@"Error setting policy for evaluating trust");
236 CFRelease(policyRef);
241 // Creates a trust management object based on certificates and policies.
242 SecTrustRef trustRef = NULL;
243 err = SecTrustCreateWithCertificates((CFArrayRef)certificates, policyRef, &trustRef);
245 NSLog(@"Error creating trust");
247 CFRelease(policyRef);
251 SecTrustResultType trustResult;
252 err = SecTrustEvaluate(trustRef, &trustResult);
254 NSLog(@"Error evaluating trust");
256 CFRelease(policyRef);
263 // kSecTrustResultProceed -> trust ok, go right ahead
264 // kSecTrustResultConfirm -> trust ok, but user asked (earlier) that you check with him before proceeding
265 // kSecTrustResultDeny -> trust ok, but user previously said not to trust it anyway
266 // kSecTrustResultUnspecified -> trust ok, user has no particular opinion about this
267 // kSecTrustResultRecoverableTrustFailure -> trust broken, perhaps argue with the user
268 // kSecTrustResultFatalTrustFailure -> trust broken, user can't fix it
269 // kSecTrustResultOtherError -> something failed weirdly, abort operation
270 // kSecTrustResultInvalid -> logic error; fix your program (SecTrust was used incorrectly)
271 switch(trustResult) {
272 case kSecTrustResultProceed:
273 // Accepted by user keychain setting explicitly
275 CFRelease(policyRef);
281 case kSecTrustResultUnspecified:
282 // See http://developer.apple.com/qa/qa2007/qa1360.html
283 // Unspecified means that the user never expressed any persistent opinion about
284 // this certificate (or any of its signers). Either this is the first time this certificate
285 // has been encountered (in these circumstances), or the user has previously dealt with it
286 // on a one-off basis without recording a persistent decision. In practice, this is what
287 // most (cryptographically successful) evaluations return.
288 // If the certificate is invalid kSecTrustResultUnspecified can never be returned.
290 CFRelease(policyRef);
299 CFArrayRef certChain;
300 CSSM_TP_APPLE_EVIDENCE_INFO *statusChain;
301 err = SecTrustGetResult(trustRef, &trustResult, &certChain, &statusChain);
302 // CSSM_CERT_STATUS_EXPIRED = 0x00000001,
303 // CSSM_CERT_STATUS_NOT_VALID_YET = 0x00000002,
304 // CSSM_CERT_STATUS_IS_IN_INPUT_CERTS = 0x00000004,
305 // CSSM_CERT_STATUS_IS_IN_ANCHORS = 0x00000008,
306 // CSSM_CERT_STATUS_IS_ROOT = 0x00000010,
307 // CSSM_CERT_STATUS_IS_FROM_NET = 0x00000020,
308 SFCertificateTrustPanel *panel = [[SFCertificateTrustPanel alloc] init];
310 if ([panel respondsToSelector:@selector(setInformativeText:)]) {
311 if((statusChain->StatusBits & CSSM_CERT_STATUS_EXPIRED) == CSSM_CERT_STATUS_EXPIRED) {
312 [panel setInformativeText:[NSString stringWithFormat:NSLocalizedStringFromTable(@"The certificate for this server has expired. You might be connecting to a server that is pretending to be \u201c%@\u201d which could put your confidential information at risk. Would you like to connect to the server anyway?", @"Keychain", @""), hostname]];
314 else if((statusChain->StatusBits & CSSM_CERT_STATUS_NOT_VALID_YET) == CSSM_CERT_STATUS_NOT_VALID_YET) {
315 [panel setInformativeText:[NSString stringWithFormat:NSLocalizedStringFromTable(@"The certificate for this server is not yet valid. You might be connecting to a server that is pretending to be \u201c%@\u201d which could put your confidential information at risk. Would you like to connect to the server anyway?", @"Keychain", @""), hostname]];
317 else if((statusChain->StatusBits & CSSM_CERT_STATUS_IS_ROOT) == CSSM_CERT_STATUS_IS_ROOT
318 && (statusChain->StatusBits & CSSM_CERT_STATUS_IS_IN_ANCHORS) != CSSM_CERT_STATUS_IS_IN_ANCHORS) {
319 [panel setInformativeText:[NSString stringWithFormat:NSLocalizedStringFromTable(@"The certificate for this server was signed by an unknown certifying authority. You might be connecting to a server that is pretending to be \u201c%@\u201d which could put your confidential information at risk. Would you like to connect to the server anyway?", @"Keychain", @""), hostname]];
321 else if((statusChain->StatusBits & CSSM_CERT_STATUS_IS_IN_ANCHORS) != CSSM_CERT_STATUS_IS_IN_ANCHORS) {
322 [panel setInformativeText:[NSString stringWithFormat:NSLocalizedStringFromTable(@"The certificate for this server is invalid. You might be connecting to a server that is pretending to be \u201c%@\u201d which could put your confidential information at risk. Would you like to connect to the server anyway?", @"Keychain", @""), hostname]];
326 if([panel respondsToSelector:@selector(setAlternateButtonTitle:)]) {
327 [panel setAlternateButtonTitle:NSLocalizedString(@"Disconnect", @"")];
329 if([panel respondsToSelector:@selector(setPolicies:)]) {
330 [panel setPolicies:(id)policyRef];
332 if([panel respondsToSelector:@selector(setShowsHelp:)]) {
333 [panel setShowsHelp:YES];
335 // Displays a modal panel that shows the results of a certificate trust evaluation and
336 // that allows the user to edit trust settings.
337 NSInteger result = [panel runModalForTrust:trustRef message:nil];
340 CFRelease(policyRef);
345 if(NSOKButton == result) {
351 JNIEXPORT jboolean JNICALL Java_ch_cyberduck_core_Keychain_displayCertificatesNative(JNIEnv *env, jobject this, jobjectArray jCertificates) {
352 NSArray *certificates = CreateCertificatesFromData(env, jCertificates);
353 SFCertificatePanel *panel = [[SFCertificatePanel alloc] init];
354 if([panel respondsToSelector:@selector(setShowsHelp:)]) {
355 [panel setShowsHelp:NO];
357 NSInteger result = [panel runModalForCertificates:certificates showGroup:YES];
358 if(NSOKButton == result) {