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 eu.siacs.conversations.services;
28
29import android.app.Application;
30import android.app.NotificationManager;
31import android.app.Service;
32import android.content.Context;
33import android.content.Intent;
34import android.content.SharedPreferences;
35import android.net.Uri;
36import android.os.Handler;
37import android.preference.PreferenceManager;
38import android.util.Base64;
39import android.util.Log;
40import android.util.SparseArray;
41
42import androidx.appcompat.app.AppCompatActivity;
43
44import com.google.common.base.Charsets;
45import com.google.common.base.Joiner;
46import com.google.common.io.ByteStreams;
47import com.google.common.io.CharStreams;
48
49import org.json.JSONArray;
50import org.json.JSONException;
51import org.json.JSONObject;
52
53import java.io.File;
54import java.io.FileInputStream;
55import java.io.FileOutputStream;
56import java.io.IOException;
57import java.io.InputStream;
58import java.io.InputStreamReader;
59import java.security.KeyStore;
60import java.security.KeyStoreException;
61import java.security.MessageDigest;
62import java.security.NoSuchAlgorithmException;
63import java.security.cert.Certificate;
64import java.security.cert.CertificateEncodingException;
65import java.security.cert.CertificateException;
66import java.security.cert.CertificateParsingException;
67import java.security.cert.X509Certificate;
68import java.text.SimpleDateFormat;
69import java.util.ArrayList;
70import java.util.Enumeration;
71import java.util.List;
72import java.util.Locale;
73import java.util.logging.Level;
74import java.util.logging.Logger;
75import java.util.regex.Pattern;
76
77import javax.net.ssl.TrustManager;
78import javax.net.ssl.TrustManagerFactory;
79import javax.net.ssl.X509TrustManager;
80
81import eu.siacs.conversations.Config;
82import eu.siacs.conversations.R;
83import eu.siacs.conversations.crypto.XmppDomainVerifier;
84import eu.siacs.conversations.entities.MTMDecision;
85import eu.siacs.conversations.http.HttpConnectionManager;
86import eu.siacs.conversations.persistance.FileBackend;
87import eu.siacs.conversations.ui.MemorizingActivity;
88
89/**
90 * A X509 trust manager implementation which asks the user about invalid
91 * certificates and memorizes their decision.
92 * <p>
93 * The certificate validity is checked using the system default X509
94 * TrustManager, creating a query Dialog if the check fails.
95 * <p>
96 * <b>WARNING:</b> This only works if a dedicated thread is used for
97 * opening sockets!
98 */
99public class MemorizingTrustManager {
100
101 private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
102
103 final static String DECISION_INTENT = "de.duenndns.ssl.DECISION";
104 public final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId";
105 public final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert";
106 public final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId";
107 final static String NO_TRUST_ANCHOR = "Trust anchor for certification path not found.";
108 private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
109 private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
110 private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
111 private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
112 private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
113 private final static Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName());
114 static String KEYSTORE_DIR = "KeyStore";
115 static String KEYSTORE_FILE = "KeyStore.bks";
116 private static int decisionId = 0;
117 private static final SparseArray<MTMDecision> openDecisions = new SparseArray<MTMDecision>();
118 Context master;
119 AppCompatActivity foregroundAct;
120 NotificationManager notificationManager;
121 Handler masterHandler;
122 private File keyStoreFile;
123 private KeyStore appKeyStore;
124 private final X509TrustManager defaultTrustManager;
125 private X509TrustManager appTrustManager;
126 private String poshCacheDir;
127
128 /**
129 * Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager.
130 * <p>
131 * You need to supply the application context. This has to be one of:
132 * - Application
133 * - Activity
134 * - Service
135 * <p>
136 * The context is used for file management, to display the dialog /
137 * notification and for obtaining translated strings.
138 *
139 * @param m Context for the application.
140 * @param defaultTrustManager Delegate trust management to this TM. If null, the user must accept every certificate.
141 */
142 public MemorizingTrustManager(Context m, X509TrustManager defaultTrustManager) {
143 init(m);
144 this.appTrustManager = getTrustManager(appKeyStore);
145 this.defaultTrustManager = defaultTrustManager;
146 }
147
148 /**
149 * Creates an instance of the MemorizingTrustManager class using the system X509TrustManager.
150 * <p>
151 * You need to supply the application context. This has to be one of:
152 * - Application
153 * - Activity
154 * - Service
155 * <p>
156 * The context is used for file management, to display the dialog /
157 * notification and for obtaining translated strings.
158 *
159 * @param m Context for the application.
160 */
161 public MemorizingTrustManager(Context m) {
162 init(m);
163 this.appTrustManager = getTrustManager(appKeyStore);
164 this.defaultTrustManager = getTrustManager(null);
165 }
166
167 private static boolean isIp(final String server) {
168 return server != null && (
169 PATTERN_IPV4.matcher(server).matches()
170 || PATTERN_IPV6.matcher(server).matches()
171 || PATTERN_IPV6_6HEX4DEC.matcher(server).matches()
172 || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches()
173 || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches());
174 }
175
176 private static String getBase64Hash(X509Certificate certificate, String digest) throws CertificateEncodingException {
177 MessageDigest md;
178 try {
179 md = MessageDigest.getInstance(digest);
180 } catch (NoSuchAlgorithmException e) {
181 return null;
182 }
183 md.update(certificate.getEncoded());
184 return Base64.encodeToString(md.digest(), Base64.NO_WRAP);
185 }
186
187 private static String hexString(byte[] data) {
188 StringBuffer si = new StringBuffer();
189 for (int i = 0; i < data.length; i++) {
190 si.append(String.format("%02x", data[i]));
191 if (i < data.length - 1)
192 si.append(":");
193 }
194 return si.toString();
195 }
196
197 private static String certHash(final X509Certificate cert, String digest) {
198 try {
199 MessageDigest md = MessageDigest.getInstance(digest);
200 md.update(cert.getEncoded());
201 return hexString(md.digest());
202 } catch (CertificateEncodingException | NoSuchAlgorithmException e) {
203 return e.getMessage();
204 }
205 }
206
207 public static void interactResult(int decisionId, int choice) {
208 MTMDecision d;
209 synchronized (openDecisions) {
210 d = openDecisions.get(decisionId);
211 openDecisions.remove(decisionId);
212 }
213 if (d == null) {
214 LOGGER.log(Level.SEVERE, "interactResult: aborting due to stale decision reference!");
215 return;
216 }
217 synchronized (d) {
218 d.state = choice;
219 d.notify();
220 }
221 }
222
223 void init(final Context m) {
224 master = m;
225 masterHandler = new Handler(m.getMainLooper());
226 notificationManager = (NotificationManager) master.getSystemService(Context.NOTIFICATION_SERVICE);
227
228 Application app;
229 if (m instanceof Application) {
230 app = (Application) m;
231 } else if (m instanceof Service) {
232 app = ((Service) m).getApplication();
233 } else if (m instanceof AppCompatActivity) {
234 app = ((AppCompatActivity) m).getApplication();
235 } else
236 throw new ClassCastException("MemorizingTrustManager context must be either Activity or Service!");
237
238 File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE);
239 keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE);
240
241 poshCacheDir = app.getCacheDir().getAbsolutePath() + "/posh_cache/";
242
243 appKeyStore = loadAppKeyStore();
244 }
245
246 /**
247 * Get a list of all certificate aliases stored in MTM.
248 *
249 * @return an {@link Enumeration} of all certificates
250 */
251 public Enumeration<String> getCertificates() {
252 try {
253 return appKeyStore.aliases();
254 } catch (KeyStoreException e) {
255 // this should never happen, however...
256 throw new RuntimeException(e);
257 }
258 }
259
260 /**
261 * Removes the given certificate from MTMs key store.
262 *
263 * <p>
264 * <b>WARNING</b>: this does not immediately invalidate the certificate. It is
265 * well possible that (a) data is transmitted over still existing connections or
266 * (b) new connections are created using TLS renegotiation, without a new cert
267 * check.
268 * </p>
269 *
270 * @param alias the certificate's alias as returned by {@link #getCertificates()}.
271 * @throws KeyStoreException if the certificate could not be deleted.
272 */
273 public void deleteCertificate(String alias) throws KeyStoreException {
274 appKeyStore.deleteEntry(alias);
275 keyStoreUpdated();
276 }
277
278 X509TrustManager getTrustManager(KeyStore ks) {
279 try {
280 TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
281 tmf.init(ks);
282 for (TrustManager t : tmf.getTrustManagers()) {
283 if (t instanceof X509TrustManager) {
284 return (X509TrustManager) t;
285 }
286 }
287 } catch (Exception e) {
288 // Here, we are covering up errors. It might be more useful
289 // however to throw them out of the constructor so the
290 // embedding app knows something went wrong.
291 LOGGER.log(Level.SEVERE, "getTrustManager(" + ks + ")", e);
292 }
293 return null;
294 }
295
296 KeyStore loadAppKeyStore() {
297 KeyStore ks;
298 try {
299 ks = KeyStore.getInstance(KeyStore.getDefaultType());
300 } catch (KeyStoreException e) {
301 LOGGER.log(Level.SEVERE, "getAppKeyStore()", e);
302 return null;
303 }
304 FileInputStream fileInputStream = null;
305 try {
306 ks.load(null, null);
307 fileInputStream = new FileInputStream(keyStoreFile);
308 ks.load(fileInputStream, "MTM".toCharArray());
309 } catch (java.io.FileNotFoundException e) {
310 LOGGER.log(Level.INFO, "getAppKeyStore(" + keyStoreFile + ") - file does not exist");
311 } catch (Exception e) {
312 LOGGER.log(Level.SEVERE, "getAppKeyStore(" + keyStoreFile + ")", e);
313 } finally {
314 FileBackend.close(fileInputStream);
315 }
316 return ks;
317 }
318
319 void storeCert(String alias, Certificate cert) {
320 try {
321 appKeyStore.setCertificateEntry(alias, cert);
322 } catch (KeyStoreException e) {
323 LOGGER.log(Level.SEVERE, "storeCert(" + cert + ")", e);
324 return;
325 }
326 keyStoreUpdated();
327 }
328
329 void storeCert(X509Certificate cert) {
330 storeCert(cert.getSubjectDN().toString(), cert);
331 }
332
333 void keyStoreUpdated() {
334 // reload appTrustManager
335 appTrustManager = getTrustManager(appKeyStore);
336
337 // store KeyStore to file
338 java.io.FileOutputStream fos = null;
339 try {
340 fos = new java.io.FileOutputStream(keyStoreFile);
341 appKeyStore.store(fos, "MTM".toCharArray());
342 } catch (Exception e) {
343 LOGGER.log(Level.SEVERE, "storeCert(" + keyStoreFile + ")", e);
344 } finally {
345 if (fos != null) {
346 try {
347 fos.close();
348 } catch (IOException e) {
349 LOGGER.log(Level.SEVERE, "storeCert(" + keyStoreFile + ")", e);
350 }
351 }
352 }
353 }
354
355 // if the certificate is stored in the app key store, it is considered "known"
356 private boolean isCertKnown(X509Certificate cert) {
357 try {
358 return appKeyStore.getCertificateAlias(cert) != null;
359 } catch (KeyStoreException e) {
360 return false;
361 }
362 }
363
364
365 private void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive)
366 throws CertificateException {
367 LOGGER.log(Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")");
368 try {
369 LOGGER.log(Level.FINE, "checkCertTrusted: trying appTrustManager");
370 if (isServer)
371 appTrustManager.checkServerTrusted(chain, authType);
372 else
373 appTrustManager.checkClientTrusted(chain, authType);
374 } catch (final CertificateException ae) {
375 LOGGER.log(Level.FINER, "checkCertTrusted: appTrustManager failed", ae);
376 if (isCertKnown(chain[0])) {
377 LOGGER.log(Level.INFO, "checkCertTrusted: accepting cert already stored in keystore");
378 return;
379 }
380 try {
381 if (defaultTrustManager == null)
382 throw ae;
383 LOGGER.log(Level.FINE, "checkCertTrusted: trying defaultTrustManager");
384 if (isServer)
385 defaultTrustManager.checkServerTrusted(chain, authType);
386 else
387 defaultTrustManager.checkClientTrusted(chain, authType);
388 } catch (final CertificateException e) {
389 final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(master);
390 final boolean trustSystemCAs = !preferences.getBoolean("dont_trust_system_cas", false);
391 if (domain != null && isServer && trustSystemCAs && !isIp(domain) && !domain.endsWith(".onion")) {
392 final String hash = getBase64Hash(chain[0], "SHA-256");
393 final List<String> fingerprints = getPoshFingerprints(domain);
394 if (hash != null && fingerprints.size() > 0) {
395 if (fingerprints.contains(hash)) {
396 Log.d(Config.LOGTAG, "trusted cert fingerprint of " + domain + " via posh");
397 return;
398 } else {
399 Log.d(Config.LOGTAG, "fingerprint " + hash + " not found in " + fingerprints);
400 }
401 if (getPoshCacheFile(domain).delete()) {
402 Log.d(Config.LOGTAG, "deleted posh file for " + domain + " after not being able to verify");
403 }
404 }
405 }
406 if (interactive) {
407 interactCert(chain, authType, e);
408 } else {
409 throw e;
410 }
411 }
412 }
413 }
414
415 private List<String> getPoshFingerprints(final String domain) {
416 final List<String> cached = getPoshFingerprintsFromCache(domain);
417 if (cached == null) {
418 return getPoshFingerprintsFromServer(domain);
419 } else {
420 return cached;
421 }
422 }
423
424 private List<String> getPoshFingerprintsFromServer(String domain) {
425 return getPoshFingerprintsFromServer(domain, "https://" + domain + "/.well-known/posh/xmpp-client.json", -1, true);
426 }
427
428 private List<String> getPoshFingerprintsFromServer(String domain, String url, int maxTtl, boolean followUrl) {
429 Log.d(Config.LOGTAG, "downloading json for " + domain + " from " + url);
430 final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(master);
431 final boolean useTor = preferences.getBoolean("use_tor", master.getResources().getBoolean(R.bool.use_tor));
432 try {
433 final List<String> results = new ArrayList<>();
434 final InputStream inputStream = HttpConnectionManager.open(url, useTor);
435 final String body = CharStreams.toString(new InputStreamReader(ByteStreams.limit(inputStream,10_000), Charsets.UTF_8));
436 final JSONObject jsonObject = new JSONObject(body);
437 int expires = jsonObject.getInt("expires");
438 if (expires <= 0) {
439 return new ArrayList<>();
440 }
441 if (maxTtl >= 0) {
442 expires = Math.min(maxTtl, expires);
443 }
444 String redirect;
445 try {
446 redirect = jsonObject.getString("url");
447 } catch (JSONException e) {
448 redirect = null;
449 }
450 if (followUrl && redirect != null && redirect.toLowerCase().startsWith("https")) {
451 return getPoshFingerprintsFromServer(domain, redirect, expires, false);
452 }
453 final JSONArray fingerprints = jsonObject.getJSONArray("fingerprints");
454 for (int i = 0; i < fingerprints.length(); i++) {
455 final JSONObject fingerprint = fingerprints.getJSONObject(i);
456 final String sha256 = fingerprint.getString("sha-256");
457 results.add(sha256);
458 }
459 writeFingerprintsToCache(domain, results, 1000L * expires + System.currentTimeMillis());
460 return results;
461 } catch (final Exception e) {
462 Log.d(Config.LOGTAG, "error fetching posh",e);
463 return new ArrayList<>();
464 }
465 }
466
467 private File getPoshCacheFile(String domain) {
468 return new File(poshCacheDir + domain + ".json");
469 }
470
471 private void writeFingerprintsToCache(String domain, List<String> results, long expires) {
472 final File file = getPoshCacheFile(domain);
473 file.getParentFile().mkdirs();
474 try {
475 file.createNewFile();
476 JSONObject jsonObject = new JSONObject();
477 jsonObject.put("expires", expires);
478 jsonObject.put("fingerprints", new JSONArray(results));
479 FileOutputStream outputStream = new FileOutputStream(file);
480 outputStream.write(jsonObject.toString().getBytes());
481 outputStream.flush();
482 outputStream.close();
483 } catch (Exception e) {
484 e.printStackTrace();
485 }
486 }
487
488 private List<String> getPoshFingerprintsFromCache(String domain) {
489 final File file = getPoshCacheFile(domain);
490 try {
491 final InputStream inputStream = new FileInputStream(file);
492 final String json = CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8));
493 final JSONObject jsonObject = new JSONObject(json);
494 long expires = jsonObject.getLong("expires");
495 long expiresIn = expires - System.currentTimeMillis();
496 if (expiresIn < 0) {
497 file.delete();
498 return null;
499 } else {
500 Log.d(Config.LOGTAG, "posh fingerprints expire in " + (expiresIn / 1000) + "s");
501 }
502 final List<String> result = new ArrayList<>();
503 final JSONArray jsonArray = jsonObject.getJSONArray("fingerprints");
504 for (int i = 0; i < jsonArray.length(); ++i) {
505 result.add(jsonArray.getString(i));
506 }
507 return result;
508 } catch (final IOException e) {
509 return null;
510 } catch (JSONException e) {
511 file.delete();
512 return null;
513 }
514 }
515
516 private X509Certificate[] getAcceptedIssuers() {
517 return defaultTrustManager == null ? new X509Certificate[0] : defaultTrustManager.getAcceptedIssuers();
518 }
519
520 private int createDecisionId(MTMDecision d) {
521 int myId;
522 synchronized (openDecisions) {
523 myId = decisionId;
524 openDecisions.put(myId, d);
525 decisionId += 1;
526 }
527 return myId;
528 }
529
530 private void certDetails(final StringBuffer si, final X509Certificate c, final boolean showValidFor) {
531
532 si.append("\n");
533 if (showValidFor) {
534 try {
535 si.append("Valid for: ");
536 si.append(Joiner.on(", ").join(XmppDomainVerifier.parseValidDomains(c).all()));
537 } catch (final CertificateParsingException e) {
538 si.append("Unable to parse Certificate");
539 }
540 si.append("\n");
541 } else {
542 si.append(c.getSubjectDN());
543 }
544 si.append("\n");
545 si.append(DATE_FORMAT.format(c.getNotBefore()));
546 si.append(" - ");
547 si.append(DATE_FORMAT.format(c.getNotAfter()));
548 si.append("\nSHA-256: ");
549 si.append(certHash(c, "SHA-256"));
550 si.append("\nSHA-1: ");
551 si.append(certHash(c, "SHA-1"));
552 si.append("\nSigned by: ");
553 si.append(c.getIssuerDN().toString());
554 si.append("\n");
555 }
556
557 private String certChainMessage(final X509Certificate[] chain, CertificateException cause) {
558 Throwable e = cause;
559 LOGGER.log(Level.FINE, "certChainMessage for " + e);
560 final StringBuffer si = new StringBuffer();
561 if (e.getCause() != null) {
562 e = e.getCause();
563 // HACK: there is no sane way to check if the error is a "trust anchor
564 // not found", so we use string comparison.
565 if (NO_TRUST_ANCHOR.equals(e.getMessage())) {
566 si.append(master.getString(R.string.mtm_trust_anchor));
567 } else
568 si.append(e.getLocalizedMessage());
569 si.append("\n");
570 }
571 si.append("\n");
572 si.append(master.getString(R.string.mtm_connect_anyway));
573 si.append("\n\n");
574 si.append(master.getString(R.string.mtm_cert_details));
575 si.append('\n');
576 for(int i = 0; i < chain.length; ++i) {
577 certDetails(si, chain[i], i == 0);
578 }
579 return si.toString();
580 }
581
582 /**
583 * Returns the top-most entry of the activity stack.
584 *
585 * @return the Context of the currently bound UI or the master context if none is bound
586 */
587 Context getUI() {
588 return (foregroundAct != null) ? foregroundAct : master;
589 }
590
591 int interact(final String message, final int titleId) {
592 /* prepare the MTMDecision blocker object */
593 MTMDecision choice = new MTMDecision();
594 final int myId = createDecisionId(choice);
595
596 masterHandler.post(new Runnable() {
597 public void run() {
598 Intent ni = new Intent(master, MemorizingActivity.class);
599 ni.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
600 ni.setData(Uri.parse(MemorizingTrustManager.class.getName() + "/" + myId));
601 ni.putExtra(DECISION_INTENT_ID, myId);
602 ni.putExtra(DECISION_INTENT_CERT, message);
603 ni.putExtra(DECISION_TITLE_ID, titleId);
604
605 // we try to directly start the activity and fall back to
606 // making a notification
607 try {
608 getUI().startActivity(ni);
609 } catch (Exception e) {
610 LOGGER.log(Level.FINE, "startActivity(MemorizingActivity)", e);
611 }
612 }
613 });
614
615 LOGGER.log(Level.FINE, "openDecisions: " + openDecisions + ", waiting on " + myId);
616 try {
617 synchronized (choice) {
618 choice.wait();
619 }
620 } catch (InterruptedException e) {
621 LOGGER.log(Level.FINER, "InterruptedException", e);
622 }
623 LOGGER.log(Level.FINE, "finished wait on " + myId + ": " + choice.state);
624 return choice.state;
625 }
626
627 void interactCert(final X509Certificate[] chain, String authType, CertificateException cause)
628 throws CertificateException {
629 switch (interact(certChainMessage(chain, cause), R.string.mtm_accept_cert)) {
630 case MTMDecision.DECISION_ALWAYS:
631 storeCert(chain[0]); // only store the server cert, not the whole chain
632 case MTMDecision.DECISION_ONCE:
633 break;
634 default:
635 throw (cause);
636 }
637 }
638
639 public X509TrustManager getNonInteractive(String domain) {
640 return new NonInteractiveMemorizingTrustManager(domain);
641 }
642
643 public X509TrustManager getInteractive(String domain) {
644 return new InteractiveMemorizingTrustManager(domain);
645 }
646
647 public X509TrustManager getNonInteractive() {
648 return new NonInteractiveMemorizingTrustManager(null);
649 }
650
651 public X509TrustManager getInteractive() {
652 return new InteractiveMemorizingTrustManager(null);
653 }
654
655 private class NonInteractiveMemorizingTrustManager implements X509TrustManager {
656
657 private final String domain;
658
659 public NonInteractiveMemorizingTrustManager(String domain) {
660 this.domain = domain;
661 }
662
663 @Override
664 public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
665 MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, false);
666 }
667
668 @Override
669 public void checkServerTrusted(X509Certificate[] chain, String authType)
670 throws CertificateException {
671 MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, true, false);
672 }
673
674 @Override
675 public X509Certificate[] getAcceptedIssuers() {
676 return MemorizingTrustManager.this.getAcceptedIssuers();
677 }
678
679 }
680
681 private class InteractiveMemorizingTrustManager implements X509TrustManager {
682 private final String domain;
683
684 public InteractiveMemorizingTrustManager(String domain) {
685 this.domain = domain;
686 }
687
688 @Override
689 public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
690 MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, true);
691 }
692
693 @Override
694 public void checkServerTrusted(X509Certificate[] chain, String authType)
695 throws CertificateException {
696 MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, true, true);
697 }
698
699 @Override
700 public X509Certificate[] getAcceptedIssuers() {
701 return MemorizingTrustManager.this.getAcceptedIssuers();
702 }
703 }
704}