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 = TrustManagers.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 boolean isIp(final String server) {
197 return server != null
198 && (PATTERN_IPV4.matcher(server).matches()
199 || PATTERN_IPV6.matcher(server).matches()
200 || PATTERN_IPV6_6HEX4DEC.matcher(server).matches()
201 || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches()
202 || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches());
203 }
204
205 private static String getBase64Hash(X509Certificate certificate, String digest)
206 throws CertificateEncodingException {
207 MessageDigest md;
208 try {
209 md = MessageDigest.getInstance(digest);
210 } catch (NoSuchAlgorithmException e) {
211 return null;
212 }
213 md.update(certificate.getEncoded());
214 return Base64.encodeToString(md.digest(), Base64.NO_WRAP);
215 }
216
217 private static String hexString(byte[] data) {
218 StringBuffer si = new StringBuffer();
219 for (int i = 0; i < data.length; i++) {
220 si.append(String.format("%02x", data[i]));
221 if (i < data.length - 1) si.append(":");
222 }
223 return si.toString();
224 }
225
226 private static String certHash(final X509Certificate cert, String digest) {
227 try {
228 MessageDigest md = MessageDigest.getInstance(digest);
229 md.update(cert.getEncoded());
230 return hexString(md.digest());
231 } catch (CertificateEncodingException | NoSuchAlgorithmException e) {
232 return e.getMessage();
233 }
234 }
235
236 public static void interactResult(int decisionId, int choice) {
237 MTMDecision d;
238 synchronized (openDecisions) {
239 d = openDecisions.get(decisionId);
240 openDecisions.remove(decisionId);
241 }
242 if (d == null) {
243 LOGGER.log(Level.SEVERE, "interactResult: aborting due to stale decision reference!");
244 return;
245 }
246 synchronized (d) {
247 d.state = choice;
248 d.notify();
249 }
250 }
251
252 void init(final Context context) {
253 master = context;
254 masterHandler = new Handler(context.getMainLooper());
255 notificationManager =
256 (NotificationManager) master.getSystemService(Context.NOTIFICATION_SERVICE);
257
258 Application app;
259 if (context instanceof Application) {
260 app = (Application) context;
261 } else if (context instanceof Service) {
262 app = ((Service) context).getApplication();
263 } else if (context instanceof AppCompatActivity) {
264 app = ((AppCompatActivity) context).getApplication();
265 } else
266 throw new ClassCastException(
267 "MemorizingTrustManager context must be either Activity or Service!");
268
269 File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE);
270 keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE);
271
272 appKeyStore = loadAppKeyStore();
273 }
274
275 /**
276 * Get a list of all certificate aliases stored in MTM.
277 *
278 * @return an {@link Enumeration} of all certificates
279 */
280 public Enumeration<String> getCertificates() {
281 try {
282 return appKeyStore.aliases();
283 } catch (KeyStoreException e) {
284 // this should never happen, however...
285 throw new RuntimeException(e);
286 }
287 }
288
289 /**
290 * Removes the given certificate from MTMs key store.
291 *
292 * <p><b>WARNING</b>: this does not immediately invalidate the certificate. It is well possible
293 * that (a) data is transmitted over still existing connections or (b) new connections are
294 * created using TLS renegotiation, without a new cert check.
295 *
296 * @param alias the certificate's alias as returned by {@link #getCertificates()}.
297 * @throws KeyStoreException if the certificate could not be deleted.
298 */
299 public void deleteCertificate(String alias) throws KeyStoreException {
300 appKeyStore.deleteEntry(alias);
301 keyStoreUpdated();
302 }
303
304 private X509TrustManager getTrustManager(final KeyStore keyStore) {
305 Preconditions.checkNotNull(keyStore);
306 try {
307 TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
308 tmf.init(keyStore);
309 for (TrustManager t : tmf.getTrustManagers()) {
310 if (t instanceof X509TrustManager) {
311 return (X509TrustManager) t;
312 }
313 }
314 } catch (final Exception e) {
315 // Here, we are covering up errors. It might be more useful
316 // however to throw them out of the constructor so the
317 // embedding app knows something went wrong.
318 LOGGER.log(Level.SEVERE, "getTrustManager(" + keyStore + ")", e);
319 }
320 return null;
321 }
322
323 KeyStore loadAppKeyStore() {
324 KeyStore ks;
325 try {
326 ks = KeyStore.getInstance(KeyStore.getDefaultType());
327 } catch (KeyStoreException e) {
328 LOGGER.log(Level.SEVERE, "getAppKeyStore()", e);
329 return null;
330 }
331 FileInputStream fileInputStream = null;
332 try {
333 ks.load(null, null);
334 fileInputStream = new FileInputStream(keyStoreFile);
335 ks.load(fileInputStream, "MTM".toCharArray());
336 } catch (java.io.FileNotFoundException e) {
337 LOGGER.log(Level.INFO, "getAppKeyStore(" + keyStoreFile + ") - file does not exist");
338 } catch (Exception e) {
339 LOGGER.log(Level.SEVERE, "getAppKeyStore(" + keyStoreFile + ")", e);
340 } finally {
341 FileBackend.close(fileInputStream);
342 }
343 return ks;
344 }
345
346 void storeCert(String alias, Certificate cert) {
347 try {
348 appKeyStore.setCertificateEntry(alias, cert);
349 } catch (KeyStoreException e) {
350 LOGGER.log(Level.SEVERE, "storeCert(" + cert + ")", e);
351 return;
352 }
353 keyStoreUpdated();
354 }
355
356 void storeCert(X509Certificate cert) {
357 storeCert(cert.getSubjectDN().toString(), cert);
358 }
359
360 void keyStoreUpdated() {
361 // reload appTrustManager
362 appTrustManager = getTrustManager(appKeyStore);
363
364 // store KeyStore to file
365 java.io.FileOutputStream fos = null;
366 try {
367 fos = new java.io.FileOutputStream(keyStoreFile);
368 appKeyStore.store(fos, "MTM".toCharArray());
369 } catch (Exception e) {
370 LOGGER.log(Level.SEVERE, "storeCert(" + keyStoreFile + ")", e);
371 } finally {
372 if (fos != null) {
373 try {
374 fos.close();
375 } catch (IOException e) {
376 LOGGER.log(Level.SEVERE, "storeCert(" + keyStoreFile + ")", e);
377 }
378 }
379 }
380 }
381
382 // if the certificate is stored in the app key store, it is considered "known"
383 private boolean isCertKnown(X509Certificate cert) {
384 try {
385 return appKeyStore.getCertificateAlias(cert) != null;
386 } catch (KeyStoreException e) {
387 return false;
388 }
389 }
390
391 private void checkCertTrusted(
392 X509Certificate[] chain,
393 String authType,
394 String domain,
395 boolean isServer,
396 boolean interactive,
397 String verifiedHostname,
398 int port,
399 Consumer<Boolean> daneCb)
400 throws CertificateException {
401 LOGGER.log(
402 Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")");
403 try {
404 LOGGER.log(Level.FINE, "checkCertTrusted: trying appTrustManager");
405 if (isServer) {
406 if (verifiedHostname != null && !eu.siacs.conversations.utils.IP.matches(verifiedHostname)) {
407 try {
408 if (daneVerifier.verifyCertificateChain(chain, verifiedHostname, port)) {
409 if (daneCb != null) daneCb.accept(true);
410 return;
411 }
412 } catch (final CertificateException e) {
413 Log.d(Config.LOGTAG, "checkCertTrusted DANE failure: " + e);
414 throw e;
415 } catch (final Throwable e) {
416 Log.d(Config.LOGTAG, "checkCertTrusted DANE related failure: " + e);
417 }
418 }
419 appTrustManager.checkServerTrusted(chain, authType);
420 } else
421 appTrustManager.checkClientTrusted(chain, authType);
422 } catch (final CertificateException ae) {
423 LOGGER.log(Level.FINER, "checkCertTrusted: appTrustManager failed", ae);
424 if (isCertKnown(chain[0])) {
425 LOGGER.log(
426 Level.INFO, "checkCertTrusted: accepting cert already stored in keystore");
427 return;
428 }
429 try {
430 if (defaultTrustManager == null) throw ae;
431 LOGGER.log(Level.FINE, "checkCertTrusted: trying defaultTrustManager");
432 if (isServer) defaultTrustManager.checkServerTrusted(chain, authType);
433 else defaultTrustManager.checkClientTrusted(chain, authType);
434 } catch (final CertificateException e) {
435 if (interactive) {
436 interactCert(chain, authType, e);
437 } else {
438 throw e;
439 }
440 }
441 }
442 }
443
444 private X509Certificate[] getAcceptedIssuers() {
445 return defaultTrustManager == null
446 ? new X509Certificate[0]
447 : 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 void certDetails(
461 final StringBuffer si, final X509Certificate c, final boolean showValidFor) {
462
463 si.append("\n");
464 if (showValidFor) {
465 try {
466 si.append("Valid for: ");
467 si.append(Joiner.on(", ").join(XmppDomainVerifier.parseValidDomains(c).all()));
468 } catch (final CertificateParsingException e) {
469 si.append("Unable to parse Certificate");
470 }
471 si.append("\n");
472 } else {
473 si.append(c.getSubjectDN());
474 }
475 si.append("\n");
476 si.append(DATE_FORMAT.format(c.getNotBefore()));
477 si.append(" - ");
478 si.append(DATE_FORMAT.format(c.getNotAfter()));
479 si.append("\nSHA-256: ");
480 si.append(certHash(c, "SHA-256"));
481 si.append("\nSHA-1: ");
482 si.append(certHash(c, "SHA-1"));
483 si.append("\nSigned by: ");
484 si.append(c.getIssuerDN().toString());
485 si.append("\n");
486 }
487
488 private String certChainMessage(final X509Certificate[] chain, CertificateException cause) {
489 Throwable e = cause;
490 LOGGER.log(Level.FINE, "certChainMessage for " + e);
491 final StringBuffer si = new StringBuffer();
492 if (e.getCause() != null) {
493 e = e.getCause();
494 // HACK: there is no sane way to check if the error is a "trust anchor
495 // not found", so we use string comparison.
496 if (NO_TRUST_ANCHOR.equals(e.getMessage())) {
497 si.append(master.getString(R.string.mtm_trust_anchor));
498 } else si.append(e.getLocalizedMessage());
499 si.append("\n");
500 }
501 si.append("\n");
502 si.append(master.getString(R.string.mtm_connect_anyway));
503 si.append("\n\n");
504 si.append(master.getString(R.string.mtm_cert_details));
505 si.append('\n');
506 for (int i = 0; i < chain.length; ++i) {
507 certDetails(si, chain[i], i == 0);
508 }
509 return si.toString();
510 }
511
512 /**
513 * Returns the top-most entry of the activity stack.
514 *
515 * @return the Context of the currently bound UI or the master context if none is bound
516 */
517 Context getUI() {
518 return (foregroundAct != null) ? foregroundAct : master;
519 }
520
521 int interact(final String message, final int titleId) {
522 /* prepare the MTMDecision blocker object */
523 MTMDecision choice = new MTMDecision();
524 final int myId = createDecisionId(choice);
525
526 masterHandler.post(
527 new Runnable() {
528 public void run() {
529 Intent ni = new Intent(master, MemorizingActivity.class);
530 ni.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
531 ni.setData(Uri.parse(MemorizingTrustManager.class.getName() + "/" + myId));
532 ni.putExtra(DECISION_INTENT_ID, myId);
533 ni.putExtra(DECISION_INTENT_CERT, message);
534 ni.putExtra(DECISION_TITLE_ID, titleId);
535
536 // we try to directly start the activity and fall back to
537 // making a notification
538 try {
539 getUI().startActivity(ni);
540 } catch (Exception e) {
541 LOGGER.log(Level.FINE, "startActivity(MemorizingActivity)", e);
542 }
543 }
544 });
545
546 LOGGER.log(Level.FINE, "openDecisions: " + openDecisions + ", waiting on " + myId);
547 try {
548 synchronized (choice) {
549 choice.wait();
550 }
551 } catch (InterruptedException e) {
552 LOGGER.log(Level.FINER, "InterruptedException", e);
553 }
554 LOGGER.log(Level.FINE, "finished wait on " + myId + ": " + choice.state);
555 return choice.state;
556 }
557
558 void interactCert(final X509Certificate[] chain, String authType, CertificateException cause)
559 throws CertificateException {
560 switch (interact(certChainMessage(chain, cause), R.string.mtm_accept_cert)) {
561 case MTMDecision.DECISION_ALWAYS:
562 storeCert(chain[0]); // only store the server cert, not the whole chain
563 case MTMDecision.DECISION_ONCE:
564 break;
565 default:
566 throw (cause);
567 }
568 }
569
570 public X509TrustManager getNonInteractive(String domain, String verifiedHostname, int port, Consumer<Boolean> daneCb) {
571 return new NonInteractiveMemorizingTrustManager(domain, verifiedHostname, port, daneCb);
572 }
573
574 public X509TrustManager getInteractive(String domain, String verifiedHostname, int port, Consumer<Boolean> daneCb) {
575 return new InteractiveMemorizingTrustManager(domain, verifiedHostname, port, daneCb);
576 }
577
578 public X509TrustManager getNonInteractive() {
579 return new NonInteractiveMemorizingTrustManager(null, null, 0, null);
580 }
581
582 public X509TrustManager getInteractive() {
583 return new InteractiveMemorizingTrustManager(null, null, 0, null);
584 }
585
586 private class NonInteractiveMemorizingTrustManager implements X509TrustManager {
587
588 private final String domain;
589 private final String verifiedHostname;
590 private final int port;
591 private final Consumer<Boolean> daneCb;
592
593 public NonInteractiveMemorizingTrustManager(String domain, String verifiedHostname, int port, Consumer<Boolean> daneCb) {
594 this.domain = domain;
595 this.verifiedHostname = verifiedHostname;
596 this.port = port;
597 this.daneCb = daneCb;
598 }
599
600 @Override
601 public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
602 MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, false, verifiedHostname, port, daneCb);
603 }
604
605 @Override
606 public void checkServerTrusted(X509Certificate[] chain, String authType)
607 throws CertificateException {
608 MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, true, false, verifiedHostname, port, daneCb);
609 }
610
611 @Override
612 public X509Certificate[] getAcceptedIssuers() {
613 return MemorizingTrustManager.this.getAcceptedIssuers();
614 }
615 }
616
617 private class InteractiveMemorizingTrustManager implements X509TrustManager {
618 private final String domain;
619 private final String verifiedHostname;
620 private final int port;
621 private final Consumer<Boolean> daneCb;
622
623 public InteractiveMemorizingTrustManager(String domain, String verifiedHostname, int port, Consumer<Boolean> daneCb) {
624 this.domain = domain;
625 this.verifiedHostname = verifiedHostname;
626 this.port = port;
627 this.daneCb = daneCb;
628 }
629
630 @Override
631 public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
632 MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, true, verifiedHostname, port, daneCb);
633 }
634
635 @Override
636 public void checkServerTrusted(X509Certificate[] chain, String authType)
637 throws CertificateException {
638 MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, true, true, verifiedHostname, port, daneCb);
639 }
640
641 @Override
642 public X509Certificate[] getAcceptedIssuers() {
643 return MemorizingTrustManager.this.getAcceptedIssuers();
644 }
645 }
646}