1/* MemorizingTrustManager - a TrustManager which asks the user about invalid
2 * certificates and memorizes their decision.
3 *
4 * Copyright (c) 2010 Georg Lukas <georg@op-co.de>
5 *
6 * MemorizingTrustManager.java contains the actual trust manager and interface
7 * code to create a MemorizingActivity and obtain the results.
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 * THE SOFTWARE.
26 */
27package de.duenndns.ssl;
28
29import android.app.Activity;
30import android.app.Application;
31import android.app.Notification;
32import android.app.NotificationManager;
33import android.app.Service;
34import android.app.PendingIntent;
35import android.content.Context;
36import android.content.Intent;
37import android.net.Uri;
38import android.util.SparseArray;
39import android.os.Handler;
40
41import java.io.File;
42import java.io.IOException;
43import java.security.cert.*;
44import java.security.KeyStore;
45import java.security.KeyStoreException;
46import java.security.MessageDigest;
47import java.util.logging.Level;
48import java.util.logging.Logger;
49import java.text.SimpleDateFormat;
50import java.util.Collection;
51import java.util.Enumeration;
52import java.util.List;
53import java.util.Locale;
54
55import javax.net.ssl.HostnameVerifier;
56import javax.net.ssl.SSLSession;
57import javax.net.ssl.TrustManager;
58import javax.net.ssl.TrustManagerFactory;
59import javax.net.ssl.X509TrustManager;
60
61/**
62 * A X509 trust manager implementation which asks the user about invalid
63 * certificates and memorizes their decision.
64 * <p>
65 * The certificate validity is checked using the system default X509
66 * TrustManager, creating a query Dialog if the check fails.
67 * <p>
68 * <b>WARNING:</b> This only works if a dedicated thread is used for
69 * opening sockets!
70 */
71public class MemorizingTrustManager implements X509TrustManager {
72 final static String DECISION_INTENT = "de.duenndns.ssl.DECISION";
73 final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId";
74 final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert";
75 final static String DECISION_INTENT_CHOICE = DECISION_INTENT + ".decisionChoice";
76
77 private final static Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName());
78 final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId";
79 private final static int NOTIFICATION_ID = 100509;
80
81 final static String NO_TRUST_ANCHOR = "Trust anchor for certification path not found.";
82
83 static String KEYSTORE_DIR = "KeyStore";
84 static String KEYSTORE_FILE = "KeyStore.bks";
85
86 Context master;
87 Activity foregroundAct;
88 NotificationManager notificationManager;
89 private static int decisionId = 0;
90 private static SparseArray<MTMDecision> openDecisions = new SparseArray<MTMDecision>();
91
92 Handler masterHandler;
93 private File keyStoreFile;
94 private KeyStore appKeyStore;
95 private X509TrustManager defaultTrustManager;
96 private X509TrustManager appTrustManager;
97
98 /** Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager.
99 *
100 * You need to supply the application context. This has to be one of:
101 * - Application
102 * - Activity
103 * - Service
104 *
105 * The context is used for file management, to display the dialog /
106 * notification and for obtaining translated strings.
107 *
108 * @param m Context for the application.
109 * @param defaultTrustManager Delegate trust management to this TM. If null, the user must accept every certificate.
110 */
111 public MemorizingTrustManager(Context m, X509TrustManager defaultTrustManager) {
112 init(m);
113 this.appTrustManager = getTrustManager(appKeyStore);
114 this.defaultTrustManager = defaultTrustManager;
115 }
116
117 /** Creates an instance of the MemorizingTrustManager class using the system X509TrustManager.
118 *
119 * You need to supply the application context. This has to be one of:
120 * - Application
121 * - Activity
122 * - Service
123 *
124 * The context is used for file management, to display the dialog /
125 * notification and for obtaining translated strings.
126 *
127 * @param m Context for the application.
128 */
129 public MemorizingTrustManager(Context m) {
130 init(m);
131 this.appTrustManager = getTrustManager(appKeyStore);
132 this.defaultTrustManager = getTrustManager(null);
133 }
134
135 void init(Context m) {
136 master = m;
137 masterHandler = new Handler(m.getMainLooper());
138 notificationManager = (NotificationManager)master.getSystemService(Context.NOTIFICATION_SERVICE);
139
140 Application app;
141 if (m instanceof Application) {
142 app = (Application)m;
143 } else if (m instanceof Service) {
144 app = ((Service)m).getApplication();
145 } else if (m instanceof Activity) {
146 app = ((Activity)m).getApplication();
147 } else throw new ClassCastException("MemorizingTrustManager context must be either Activity or Service!");
148
149 File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE);
150 keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE);
151
152 appKeyStore = loadAppKeyStore();
153 }
154
155
156 /**
157 * Returns a X509TrustManager list containing a new instance of
158 * TrustManagerFactory.
159 *
160 * This function is meant for convenience only. You can use it
161 * as follows to integrate TrustManagerFactory for HTTPS sockets:
162 *
163 * <pre>
164 * SSLContext sc = SSLContext.getInstance("TLS");
165 * sc.init(null, MemorizingTrustManager.getInstanceList(this),
166 * new java.security.SecureRandom());
167 * HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
168 * </pre>
169 * @param c Activity or Service to show the Dialog / Notification
170 */
171 public static X509TrustManager[] getInstanceList(Context c) {
172 return new X509TrustManager[] { new MemorizingTrustManager(c) };
173 }
174
175 /**
176 * Binds an Activity to the MTM for displaying the query dialog.
177 *
178 * This is useful if your connection is run from a service that is
179 * triggered by user interaction -- in such cases the activity is
180 * visible and the user tends to ignore the service notification.
181 *
182 * You should never have a hidden activity bound to MTM! Use this
183 * function in onResume() and @see unbindDisplayActivity in onPause().
184 *
185 * @param act Activity to be bound
186 */
187 public void bindDisplayActivity(Activity act) {
188 foregroundAct = act;
189 }
190
191 /**
192 * Removes an Activity from the MTM display stack.
193 *
194 * Always call this function when the Activity added with
195 * {@link #bindDisplayActivity(Activity)} is hidden.
196 *
197 * @param act Activity to be unbound
198 */
199 public void unbindDisplayActivity(Activity act) {
200 // do not remove if it was overridden by a different activity
201 if (foregroundAct == act)
202 foregroundAct = null;
203 }
204
205 /**
206 * Changes the path for the KeyStore file.
207 *
208 * The actual filename relative to the app's directory will be
209 * <code>app_<i>dirname</i>/<i>filename</i></code>.
210 *
211 * @param dirname directory to store the KeyStore.
212 * @param filename file name for the KeyStore.
213 */
214 public static void setKeyStoreFile(String dirname, String filename) {
215 KEYSTORE_DIR = dirname;
216 KEYSTORE_FILE = filename;
217 }
218
219 /**
220 * Get a list of all certificate aliases stored in MTM.
221 *
222 * @return an {@link Enumeration} of all certificates
223 */
224 public Enumeration<String> getCertificates() {
225 try {
226 return appKeyStore.aliases();
227 } catch (KeyStoreException e) {
228 // this should never happen, however...
229 throw new RuntimeException(e);
230 }
231 }
232
233 /**
234 * Get a certificate for a given alias.
235 *
236 * @param alias the certificate's alias as returned by {@link #getCertificates()}.
237 *
238 * @return the certificate associated with the alias or <tt>null</tt> if none found.
239 */
240 public Certificate getCertificate(String alias) {
241 try {
242 return appKeyStore.getCertificate(alias);
243 } catch (KeyStoreException e) {
244 // this should never happen, however...
245 throw new RuntimeException(e);
246 }
247 }
248
249 /**
250 * Removes the given certificate from MTMs key store.
251 *
252 * <p>
253 * <b>WARNING</b>: this does not immediately invalidate the certificate. It is
254 * well possible that (a) data is transmitted over still existing connections or
255 * (b) new connections are created using TLS renegotiation, without a new cert
256 * check.
257 * </p>
258 * @param alias the certificate's alias as returned by {@link #getCertificates()}.
259 *
260 * @throws KeyStoreException if the certificate could not be deleted.
261 */
262 public void deleteCertificate(String alias) throws KeyStoreException {
263 appKeyStore.deleteEntry(alias);
264 keyStoreUpdated();
265 }
266
267 /**
268 * Creates a new hostname verifier supporting user interaction.
269 *
270 * <p>This method creates a new {@link HostnameVerifier} that is bound to
271 * the given instance of {@link MemorizingTrustManager}, and leverages an
272 * existing {@link HostnameVerifier}. The returned verifier performs the
273 * following steps, returning as soon as one of them succeeds:
274 * </p>
275 * <ol>
276 * <li>Success, if the wrapped defaultVerifier accepts the certificate.</li>
277 * <li>Success, if the server certificate is stored in the keystore under the given hostname.</li>
278 * <li>Ask the user and return accordingly.</li>
279 * <li>Failure on exception.</li>
280 * </ol>
281 *
282 * @param defaultVerifier the {@link HostnameVerifier} that should perform the actual check
283 * @return a new hostname verifier using the MTM's key store
284 *
285 * @throws IllegalArgumentException if the defaultVerifier parameter is null
286 */
287 public HostnameVerifier wrapHostnameVerifier(final HostnameVerifier defaultVerifier) {
288 if (defaultVerifier == null)
289 throw new IllegalArgumentException("The default verifier may not be null");
290
291 return new MemorizingHostnameVerifier(defaultVerifier);
292 }
293
294 public HostnameVerifier wrapHostnameVerifierNonInteractive(final HostnameVerifier defaultVerifier) {
295 if (defaultVerifier == null)
296 throw new IllegalArgumentException("The default verifier may not be null");
297
298 return new NonInteractiveMemorizingHostnameVerifier(defaultVerifier);
299 }
300
301 X509TrustManager getTrustManager(KeyStore ks) {
302 try {
303 TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
304 tmf.init(ks);
305 for (TrustManager t : tmf.getTrustManagers()) {
306 if (t instanceof X509TrustManager) {
307 return (X509TrustManager)t;
308 }
309 }
310 } catch (Exception e) {
311 // Here, we are covering up errors. It might be more useful
312 // however to throw them out of the constructor so the
313 // embedding app knows something went wrong.
314 LOGGER.log(Level.SEVERE, "getTrustManager(" + ks + ")", e);
315 }
316 return null;
317 }
318
319 KeyStore loadAppKeyStore() {
320 KeyStore ks;
321 try {
322 ks = KeyStore.getInstance(KeyStore.getDefaultType());
323 } catch (KeyStoreException e) {
324 LOGGER.log(Level.SEVERE, "getAppKeyStore()", e);
325 return null;
326 }
327 try {
328 ks.load(null, null);
329 ks.load(new java.io.FileInputStream(keyStoreFile), "MTM".toCharArray());
330 } catch (java.io.FileNotFoundException e) {
331 LOGGER.log(Level.INFO, "getAppKeyStore(" + keyStoreFile + ") - file does not exist");
332 } catch (Exception e) {
333 LOGGER.log(Level.SEVERE, "getAppKeyStore(" + keyStoreFile + ")", e);
334 }
335 return ks;
336 }
337
338 void storeCert(String alias, Certificate cert) {
339 try {
340 appKeyStore.setCertificateEntry(alias, cert);
341 } catch (KeyStoreException e) {
342 LOGGER.log(Level.SEVERE, "storeCert(" + cert + ")", e);
343 return;
344 }
345 keyStoreUpdated();
346 }
347
348 void storeCert(X509Certificate cert) {
349 storeCert(cert.getSubjectDN().toString(), cert);
350 }
351
352 void keyStoreUpdated() {
353 // reload appTrustManager
354 appTrustManager = getTrustManager(appKeyStore);
355
356 // store KeyStore to file
357 java.io.FileOutputStream fos = null;
358 try {
359 fos = new java.io.FileOutputStream(keyStoreFile);
360 appKeyStore.store(fos, "MTM".toCharArray());
361 } catch (Exception e) {
362 LOGGER.log(Level.SEVERE, "storeCert(" + keyStoreFile + ")", e);
363 } finally {
364 if (fos != null) {
365 try {
366 fos.close();
367 } catch (IOException e) {
368 LOGGER.log(Level.SEVERE, "storeCert(" + keyStoreFile + ")", e);
369 }
370 }
371 }
372 }
373
374 // if the certificate is stored in the app key store, it is considered "known"
375 private boolean isCertKnown(X509Certificate cert) {
376 try {
377 return appKeyStore.getCertificateAlias(cert) != null;
378 } catch (KeyStoreException e) {
379 return false;
380 }
381 }
382
383 private boolean isExpiredException(Throwable e) {
384 do {
385 if (e instanceof CertificateExpiredException)
386 return true;
387 e = e.getCause();
388 } while (e != null);
389 return false;
390 }
391
392 public void checkCertTrusted(X509Certificate[] chain, String authType, boolean isServer, boolean interactive)
393 throws CertificateException
394 {
395 LOGGER.log(Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")");
396 try {
397 LOGGER.log(Level.FINE, "checkCertTrusted: trying appTrustManager");
398 if (isServer)
399 appTrustManager.checkServerTrusted(chain, authType);
400 else
401 appTrustManager.checkClientTrusted(chain, authType);
402 } catch (CertificateException ae) {
403 LOGGER.log(Level.FINER, "checkCertTrusted: appTrustManager failed", ae);
404 // if the cert is stored in our appTrustManager, we ignore expiredness
405 if (isExpiredException(ae)) {
406 LOGGER.log(Level.INFO, "checkCertTrusted: accepting expired certificate from keystore");
407 return;
408 }
409 if (isCertKnown(chain[0])) {
410 LOGGER.log(Level.INFO, "checkCertTrusted: accepting cert already stored in keystore");
411 return;
412 }
413 try {
414 if (defaultTrustManager == null)
415 throw ae;
416 LOGGER.log(Level.FINE, "checkCertTrusted: trying defaultTrustManager");
417 if (isServer)
418 defaultTrustManager.checkServerTrusted(chain, authType);
419 else
420 defaultTrustManager.checkClientTrusted(chain, authType);
421 } catch (CertificateException e) {
422 e.printStackTrace();
423 if (interactive) {
424 interactCert(chain, authType, e);
425 } else {
426 throw e;
427 }
428 }
429 }
430 }
431
432 public void checkClientTrusted(X509Certificate[] chain, String authType)
433 throws CertificateException
434 {
435 checkCertTrusted(chain, authType, false,true);
436 }
437
438 public void checkServerTrusted(X509Certificate[] chain, String authType)
439 throws CertificateException
440 {
441 checkCertTrusted(chain, authType, true,true);
442 }
443
444 public X509Certificate[] getAcceptedIssuers()
445 {
446 LOGGER.log(Level.FINE, "getAcceptedIssuers()");
447 return defaultTrustManager.getAcceptedIssuers();
448 }
449
450 private int createDecisionId(MTMDecision d) {
451 int myId;
452 synchronized(openDecisions) {
453 myId = decisionId;
454 openDecisions.put(myId, d);
455 decisionId += 1;
456 }
457 return myId;
458 }
459
460 private static String hexString(byte[] data) {
461 StringBuffer si = new StringBuffer();
462 for (int i = 0; i < data.length; i++) {
463 si.append(String.format("%02x", data[i]));
464 if (i < data.length - 1)
465 si.append(":");
466 }
467 return si.toString();
468 }
469
470 private static String certHash(final X509Certificate cert, String digest) {
471 try {
472 MessageDigest md = MessageDigest.getInstance(digest);
473 md.update(cert.getEncoded());
474 return hexString(md.digest());
475 } catch (java.security.cert.CertificateEncodingException e) {
476 return e.getMessage();
477 } catch (java.security.NoSuchAlgorithmException e) {
478 return e.getMessage();
479 }
480 }
481
482 private void certDetails(StringBuffer si, X509Certificate c) {
483 SimpleDateFormat validityDateFormater = new SimpleDateFormat("yyyy-MM-dd");
484 si.append("\n");
485 si.append(c.getSubjectDN().toString());
486 si.append("\n");
487 si.append(validityDateFormater.format(c.getNotBefore()));
488 si.append(" - ");
489 si.append(validityDateFormater.format(c.getNotAfter()));
490 si.append("\nSHA-256: ");
491 si.append(certHash(c, "SHA-256"));
492 si.append("\nSHA-1: ");
493 si.append(certHash(c, "SHA-1"));
494 si.append("\nSigned by: ");
495 si.append(c.getIssuerDN().toString());
496 si.append("\n");
497 }
498
499 private String certChainMessage(final X509Certificate[] chain, CertificateException cause) {
500 Throwable e = cause;
501 LOGGER.log(Level.FINE, "certChainMessage for " + e);
502 StringBuffer si = new StringBuffer();
503 if (e.getCause() != null) {
504 e = e.getCause();
505 // HACK: there is no sane way to check if the error is a "trust anchor
506 // not found", so we use string comparison.
507 if (NO_TRUST_ANCHOR.equals(e.getMessage())) {
508 si.append(master.getString(R.string.mtm_trust_anchor));
509 } else
510 si.append(e.getLocalizedMessage());
511 si.append("\n");
512 }
513 si.append("\n");
514 si.append(master.getString(R.string.mtm_connect_anyway));
515 si.append("\n\n");
516 si.append(master.getString(R.string.mtm_cert_details));
517 for (X509Certificate c : chain) {
518 certDetails(si, c);
519 }
520 return si.toString();
521 }
522
523 private String hostNameMessage(X509Certificate cert, String hostname) {
524 StringBuffer si = new StringBuffer();
525
526 si.append(master.getString(R.string.mtm_hostname_mismatch, hostname));
527 si.append("\n\n");
528 try {
529 Collection<List<?>> sans = cert.getSubjectAlternativeNames();
530 if (sans == null) {
531 si.append(cert.getSubjectDN());
532 si.append("\n");
533 } else for (List<?> altName : sans) {
534 Object name = altName.get(1);
535 if (name instanceof String) {
536 si.append("[");
537 si.append((Integer)altName.get(0));
538 si.append("] ");
539 si.append(name);
540 si.append("\n");
541 }
542 }
543 } catch (CertificateParsingException e) {
544 e.printStackTrace();
545 si.append("<Parsing error: ");
546 si.append(e.getLocalizedMessage());
547 si.append(">\n");
548 }
549 si.append("\n");
550 si.append(master.getString(R.string.mtm_connect_anyway));
551 si.append("\n\n");
552 si.append(master.getString(R.string.mtm_cert_details));
553 certDetails(si, cert);
554 return si.toString();
555 }
556
557 // We can use Notification.Builder once MTM's minSDK is >= 11
558 @SuppressWarnings("deprecation")
559 void startActivityNotification(Intent intent, int decisionId, String certName) {
560 Notification n = new Notification(android.R.drawable.ic_lock_lock,
561 master.getString(R.string.mtm_notification),
562 System.currentTimeMillis());
563 PendingIntent call = PendingIntent.getActivity(master, 0, intent, 0);
564 n.setLatestEventInfo(master.getApplicationContext(),
565 master.getString(R.string.mtm_notification),
566 certName, call);
567 n.flags |= Notification.FLAG_AUTO_CANCEL;
568
569 notificationManager.notify(NOTIFICATION_ID + decisionId, n);
570 }
571
572 /**
573 * Returns the top-most entry of the activity stack.
574 *
575 * @return the Context of the currently bound UI or the master context if none is bound
576 */
577 Context getUI() {
578 return (foregroundAct != null) ? foregroundAct : master;
579 }
580
581 int interact(final String message, final int titleId) {
582 /* prepare the MTMDecision blocker object */
583 MTMDecision choice = new MTMDecision();
584 final int myId = createDecisionId(choice);
585
586 masterHandler.post(new Runnable() {
587 public void run() {
588 Intent ni = new Intent(master, MemorizingActivity.class);
589 ni.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
590 ni.setData(Uri.parse(MemorizingTrustManager.class.getName() + "/" + myId));
591 ni.putExtra(DECISION_INTENT_ID, myId);
592 ni.putExtra(DECISION_INTENT_CERT, message);
593 ni.putExtra(DECISION_TITLE_ID, titleId);
594
595 // we try to directly start the activity and fall back to
596 // making a notification
597 try {
598 getUI().startActivity(ni);
599 } catch (Exception e) {
600 LOGGER.log(Level.FINE, "startActivity(MemorizingActivity)", e);
601 startActivityNotification(ni, myId, message);
602 }
603 }
604 });
605
606 LOGGER.log(Level.FINE, "openDecisions: " + openDecisions + ", waiting on " + myId);
607 try {
608 synchronized(choice) { choice.wait(); }
609 } catch (InterruptedException e) {
610 LOGGER.log(Level.FINER, "InterruptedException", e);
611 }
612 LOGGER.log(Level.FINE, "finished wait on " + myId + ": " + choice.state);
613 return choice.state;
614 }
615
616 void interactCert(final X509Certificate[] chain, String authType, CertificateException cause)
617 throws CertificateException
618 {
619 switch (interact(certChainMessage(chain, cause), R.string.mtm_accept_cert)) {
620 case MTMDecision.DECISION_ALWAYS:
621 storeCert(chain[0]); // only store the server cert, not the whole chain
622 case MTMDecision.DECISION_ONCE:
623 break;
624 default:
625 throw (cause);
626 }
627 }
628
629 boolean interactHostname(X509Certificate cert, String hostname)
630 {
631 switch (interact(hostNameMessage(cert, hostname), R.string.mtm_accept_servername)) {
632 case MTMDecision.DECISION_ALWAYS:
633 storeCert(hostname, cert);
634 case MTMDecision.DECISION_ONCE:
635 return true;
636 default:
637 return false;
638 }
639 }
640
641 protected static void interactResult(int decisionId, int choice) {
642 MTMDecision d;
643 synchronized(openDecisions) {
644 d = openDecisions.get(decisionId);
645 openDecisions.remove(decisionId);
646 }
647 if (d == null) {
648 LOGGER.log(Level.SEVERE, "interactResult: aborting due to stale decision reference!");
649 return;
650 }
651 synchronized(d) {
652 d.state = choice;
653 d.notify();
654 }
655 }
656
657 class MemorizingHostnameVerifier implements HostnameVerifier {
658 private HostnameVerifier defaultVerifier;
659
660 public MemorizingHostnameVerifier(HostnameVerifier wrapped) {
661 defaultVerifier = wrapped;
662 }
663
664 protected boolean verify(String hostname, SSLSession session, boolean interactive) {
665 LOGGER.log(Level.FINE, "hostname verifier for " + hostname + ", trying default verifier first");
666 // if the default verifier accepts the hostname, we are done
667 if (defaultVerifier.verify(hostname, session)) {
668 LOGGER.log(Level.FINE, "default verifier accepted " + hostname);
669 return true;
670 }
671 // otherwise, we check if the hostname is an alias for this cert in our keystore
672 try {
673 X509Certificate cert = (X509Certificate)session.getPeerCertificates()[0];
674 //Log.d(TAG, "cert: " + cert);
675 if (cert.equals(appKeyStore.getCertificate(hostname.toLowerCase(Locale.US)))) {
676 LOGGER.log(Level.FINE, "certificate for " + hostname + " is in our keystore. accepting.");
677 return true;
678 } else {
679 LOGGER.log(Level.FINE, "server " + hostname + " provided wrong certificate, asking user.");
680 if (interactive) {
681 return interactHostname(cert, hostname);
682 } else {
683 return false;
684 }
685 }
686 } catch (Exception e) {
687 e.printStackTrace();
688 return false;
689 }
690 }
691
692 @Override
693 public boolean verify(String hostname, SSLSession session) {
694 return verify(hostname, session, true);
695 }
696 }
697
698 class NonInteractiveMemorizingHostnameVerifier extends MemorizingHostnameVerifier {
699
700 public NonInteractiveMemorizingHostnameVerifier(HostnameVerifier wrapped) {
701 super(wrapped);
702 }
703 @Override
704 public boolean verify(String hostname, SSLSession session) {
705 return verify(hostname, session, false);
706 }
707
708
709 }
710
711 public X509TrustManager getNonInteractive() {
712 return new NonInteractiveMemorizingTrustManager();
713 }
714
715 private class NonInteractiveMemorizingTrustManager implements X509TrustManager {
716
717 @Override
718 public void checkClientTrusted(X509Certificate[] chain, String authType)
719 throws CertificateException {
720 MemorizingTrustManager.this.checkCertTrusted(chain, authType, false, false);
721 }
722
723 @Override
724 public void checkServerTrusted(X509Certificate[] chain, String authType)
725 throws CertificateException {
726 MemorizingTrustManager.this.checkCertTrusted(chain, authType, true, false);
727 }
728
729 @Override
730 public X509Certificate[] getAcceptedIssuers() {
731 return MemorizingTrustManager.this.getAcceptedIssuers();
732 }
733
734 }
735}