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