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