Merge pull request #64 in ITERATE/cyberduck from feature/windows/9074 to master
[cyberduck.git] / source / ch / cyberduck / core / Keychain.m
blob9146cc958d2ece5a6721ecd5cc5835d6b9303630
1 /*
2  *  Copyright (c) 2005 David Kocher. All rights reserved.
3  *  http://cyberduck.ch/
4  *
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.
9  *
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.
14  *
15  *  Bug fixes, suggestions and comments should be sent to:
16  *  dkocher@cyberduck.ch
17  */
19 #import "Keychain.h"
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,
34                       void* option_data,
35                       size_t option_length,
36                       SecPolicyRef* policy);
38 SecProtocolType convertToSecProtocolType(JNIEnv *env, jstring jProtocol)
40     if([JNFJavaToNSString(env, jProtocol) isEqualTo: @"ftp"]) {
41         return kSecProtocolTypeFTP;
42     }
43     if([JNFJavaToNSString(env, jProtocol) isEqualTo: @"ftps"]) {
44         return kSecProtocolTypeFTPS;
45     }
46     if([JNFJavaToNSString(env, jProtocol) isEqualTo: @"sftp"]) {
47         return kSecProtocolTypeSSH;
48     }
49     if([JNFJavaToNSString(env, jProtocol) isEqualTo: @"http"]) {
50         return kSecProtocolTypeHTTP;
51     }
52     if([JNFJavaToNSString(env, jProtocol) isEqualTo: @"https"]) {
53         return kSecProtocolTypeHTTPS;
54     }
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) {
60         
61         EMInternetKeychainItem *keychainItem = [[EMKeychainProxy sharedProxy] internetKeychainItemForServer:JNFJavaToNSString(env, jService)
62                                                                                                                                                                                    withUsername:JNFJavaToNSString(env, jUsername)
63                                                                                                                                                                                                    path:nil 
64                                                                                                                                                                                                    port:port 
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) 
80                                                                                                                            path:nil 
81                                                                                                                            port:port
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) {
92         CSSM_DATA cssmData;
93         OSStatus err = SecCertificateGetData(certificateRef, &cssmData);
94         if(err != noErr) {
95                 return NULL;
96         }
97         jbyteArray jb;
98         jb=(*env)->NewByteArray(env, cssmData.Length);
99         (*env)->SetByteArrayRegion(env, jb, 0, cssmData.Length, (jbyte *)cssmData.Data);
100         return jb;
103 JNIEXPORT jbyteArray Java_ch_cyberduck_core_Keychain_chooseCertificateNative(JNIEnv *env, jobject this,
104                                                                              jobjectArray jCertificates,
105                                                                              jstring jHostname,
106                                                                              jstring jPrompt) {
107     OSStatus status;
108     NSMutableArray *identities = [NSMutableArray array];
109     int i;
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];
117         }
118         CFRelease(certificate);
119     }
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));
128         if (policy) {
129                 [panel setPolicies:(id)policy];
130                 CFRelease(policy);
131         }
132         if([panel runModalForIdentities:identities message:NSLocalizedString(@"Choose", @"")] == NSOKButton) {
133                 SecIdentityRef identity = [panel identity];
134         [panel release];
135                 SecCertificateRef certificate;
136                 if(SecIdentityCopyCertificate(identity, &certificate) == noErr) {
137             jbyteArray der = GetCertData(env, certificate);
138             CFRelease(certificate);
139             return der;
140                 }
141         }
142         else {
143                 [panel release];
144         }
145         return NULL;
148 SecCertificateRef CreateCertificateFromData(JNIEnv *env, jbyteArray jCertificate) {
149         OSStatus err;
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;
155         if(certData) {
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)];
160         }
161         SecCertificateRef certificateRef = NULL;
162         err = SecCertificateCreateFromData(cssmData, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER, &certificateRef);
163         if(cssmData) {
164                 free(cssmData->Data);
165                 free(cssmData);
166         }
167         if(err != noErr) {
168                 NSLog(@"Error creating certificate from data");
169                 return NULL;;
170         }
171         return certificateRef;
174 NSArray* CreateCertificatesFromData(JNIEnv *env, jobjectArray jCertificates) {
175     NSMutableArray *result = [NSMutableArray arrayWithCapacity:(*env)->GetArrayLength(env, jCertificates)];
176         int i;
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);
180                 if(NULL == ref) {
181             NSLog(@"Error creating certificate from ASN.1 DER");
182                         continue;
183                 }
184         [result addObject:(id)ref];
185     }
186     return result;
189 JNIEXPORT jboolean JNICALL Java_ch_cyberduck_core_Keychain_isTrustedNative(JNIEnv *env, jobject this, jstring jHostname, jobjectArray jCertificates) {
190         OSStatus err;
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);
195         if(err != noErr) {
196         NSLog(@"Error creating policy");
197                 return FALSE;
198         }
199         // Retrieves a policy object for the next policy matching specified search criteria.
200         SecPolicyRef policyRef = NULL;
201         err = SecPolicySearchCopyNext(searchRef, &policyRef);
202         if(err != noErr) {
203         NSLog(@"Error retrieving policy");
204                 if(searchRef) {
205                         CFRelease(searchRef);
206                 }
207                 return FALSE;
208         }
209         if(searchRef) {
210                 CFRelease(searchRef);
211         }
212         if(!policyRef) {
213             return FALSE;
214         }
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];
218         if(!cHostname) {
219         NSLog(@"Error adding hostname to SSL options");
220         }
221         else {
222         CSSM_APPLE_TP_SSL_OPTIONS ssloptions = {
223             .Version = CSSM_APPLE_TP_SSL_OPTS_VERSION,
224             .ServerNameLen = strlen(cHostname),
225             .ServerName = cHostname,
226             .Flags = 0
227         };
228         CSSM_DATA customCssmData = {
229             .Length = sizeof(ssloptions),
230             .Data = (uint8*)&ssloptions
231         };
232         err = SecPolicySetValue(policyRef, &customCssmData);
233         if(err != noErr) {
234             NSLog(@"Error setting policy for evaluating trust");
235             if(policyRef) {
236                 CFRelease(policyRef);
237             }
238             return FALSE;
239         }
240         }
241         // Creates a trust management object based on certificates and policies.
242         SecTrustRef trustRef = NULL;
243         err = SecTrustCreateWithCertificates((CFArrayRef)certificates, policyRef, &trustRef);
244         if(err != noErr) {
245         NSLog(@"Error creating trust");
246                 if(policyRef) {
247                         CFRelease(policyRef);
248                 }
249                 return FALSE;
250         }
251         SecTrustResultType trustResult;
252         err = SecTrustEvaluate(trustRef, &trustResult);
253         if(err != noErr) {
254         NSLog(@"Error evaluating trust");
255                 if(policyRef) {
256                         CFRelease(policyRef);
257                 }
258                 if(trustRef) {
259                         CFRelease(trustRef);
260                 }
261                 return FALSE;
262         }
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
274                         if(policyRef) {
275                                 CFRelease(policyRef);
276                         }
277                         if(trustRef) {
278                                 CFRelease(trustRef);
279                         }
280                         return TRUE;
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.
289                         if(policyRef) {
290                                 CFRelease(policyRef);
291                         }
292                         if(trustRef) {
293                                 CFRelease(trustRef);
294                         }
295                         return TRUE;
296                 default:
297                         break;
298         }
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];
309         if(err == noErr) {
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]];
313                         }
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]];
316                         }
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]];
320                         }
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]];
323                         }
324                 }
325         }
326         if([panel respondsToSelector:@selector(setAlternateButtonTitle:)]) {
327                 [panel setAlternateButtonTitle:NSLocalizedString(@"Disconnect", @"")];
328         }
329         if([panel respondsToSelector:@selector(setPolicies:)]) {
330                 [panel setPolicies:(id)policyRef];
331         }
332         if([panel respondsToSelector:@selector(setShowsHelp:)]) {
333                 [panel setShowsHelp:YES];
334         }
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];
338         [panel release];
339         if(policyRef) {
340                 CFRelease(policyRef);
341         }
342         if(trustRef) {
343                 CFRelease(trustRef);
344         }
345         if(NSOKButton == result) {
346                 return TRUE;
347         }
348     return FALSE;
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];
356         }
357         NSInteger result = [panel runModalForCertificates:certificates showGroup:YES];
358         if(NSOKButton == result) {
359                 return TRUE;
360         }
361         return FALSE;