MemorizingTrustManager.java

  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}