예제 #1
0
  /** Creates the Activity and registers a MemorizingTrustManager. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
    setContentView(R.layout.mtmexample);

    // set up gui elements
    findViewById(R.id.connect).setOnClickListener(this);
    content = (TextView) findViewById(R.id.content);
    urlinput = (EditText) findViewById(R.id.url);
    verifyhost = (CheckBox) findViewById(R.id.verifyhost);

    // register handler for background thread
    hdlr = new Handler();

    // Here, the MemorizingTrustManager is activated for HTTPS
    try {
      // set location of the keystore
      MemorizingTrustManager.setKeyStoreFile("private", "sslkeys.bks");

      // register MemorizingTrustManager for HTTPS
      SSLContext sc = SSLContext.getInstance("TLS");
      sc.init(null, MemorizingTrustManager.getInstanceList(this), new java.security.SecureRandom());
      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
      defaultverifier = HttpsURLConnection.getDefaultHostnameVerifier();

      // disable redirects to reduce possible confusion
      HttpsURLConnection.setFollowRedirects(false);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
예제 #2
0
 @Override
 public HttpDownloadResult postUrlUnsecure(
     final URL url,
     final Integer timeOut,
     final Map<String, String> data,
     final HttpHeader httpHeader)
     throws HttpDownloaderException {
   final TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManagerAllowAll()};
   final SSLSocketFactory orgSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
   final HostnameVerifier orgHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
   try {
     final SSLContext sc = SSLContext.getInstance("SSL");
     sc.init(null, trustAllCerts, new java.security.SecureRandom());
     final HostnameVerifier hv = new HostnameVerifierAllowAll();
     HttpsURLConnection.setDefaultHostnameVerifier(hv);
     HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
     return postUrlSecure(url, timeOut, data, httpHeader);
   } catch (final NoSuchAlgorithmException e) {
     logger.error("NoSuchAlgorithmException", e);
     throw new HttpDownloaderException("NoSuchAlgorithmException", e);
   } catch (final KeyManagementException e) {
     logger.error("KeyManagementException", e);
     throw new HttpDownloaderException("KeyManagementException", e);
   } finally {
     HttpsURLConnection.setDefaultHostnameVerifier(orgHostnameVerifier);
     HttpsURLConnection.setDefaultSSLSocketFactory(orgSSLSocketFactory);
   }
 }
  @Override
  protected OkHttpClient newOkHttpClient(Proxy proxy) {
    OkHttpClient client = super.newOkHttpClient(proxy);
    client.setTransports(ENABLED_TRANSPORTS);

    HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
    // Assume that the internal verifier is better than the
    // default verifier.
    try {
      if (!Class.forName("javax.net.ssl.DefaultHostnameVerifier").isInstance(verifier)) {
        client.setHostnameVerifier(verifier);
      }
    } catch (ClassNotFoundException e) {
      // if we cannot find the class than let's NOT taper with it
    }

    return client;
  }
  /**
   * Verify the hostname of the certificate used by the other end of a connected socket. You MUST
   * call this if you did not supply a hostname to {@link #createSocket()}. It is harmless to call
   * this method redundantly if the hostname has already been verified.
   *
   * <p>Wildcard certificates are allowed to verify any matching hostname, so "foo.bar.example.com"
   * is verified if the peer has a certificate for "*.example.com".
   *
   * @param socket An SSL socket which has been connected to a server
   * @param hostname The expected hostname of the remote server
   * @throws IOException if something goes wrong handshaking with the server
   * @throws SSLPeerUnverifiedException if the server cannot prove its identity
   * @hide
   */
  public static void verifyHostname(Socket socket, String hostname) throws IOException {
    if (!(socket instanceof SSLSocket)) {
      throw new IllegalArgumentException("Attempt to verify non-SSL socket");
    }

    if (!isSslCheckRelaxed()) {
      // The code at the start of OpenSSLSocketImpl.startHandshake()
      // ensures that the call is idempotent, so we can safely call it.
      SSLSocket ssl = (SSLSocket) socket;
      ssl.startHandshake();

      SSLSession session = ssl.getSession();
      if (session == null) {
        throw new SSLException("Cannot verify SSL socket without session");
      }
      if (!HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session)) {
        throw new SSLPeerUnverifiedException("Cannot verify hostname: " + hostname);
      }
    }
  }
  public DefaultX509TrustManager(KeyStore keystore) {
    super();
    try {
      TrustManagerFactory factory =
          TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
      factory.init(keystore);
      TrustManager[] trustManagers = factory.getTrustManagers();
      if (trustManagers != null) {
        // Find the first X509TrustManager
        for (TrustManager trustManager : trustManagers) {
          if (trustManager instanceof X509TrustManager) {
            defaultManager = (X509TrustManager) trustManager;
            break;
          }
        }
      }
    } catch (NoSuchAlgorithmException e) {
    } catch (KeyStoreException e) {
    }

    defaultVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
  }
예제 #6
0
  private void trustEveryone() {
    // TODO : this should actually not be everyone -- but only the one we accept
    try {
      if (defaultVerifier == null) {
        defaultVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
      }
      if (defaultSSLSocketFactory == null) {
        defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
      }
      HttpsURLConnection.setDefaultHostnameVerifier(
          new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
              return true;
            }
          });
      SSLContext context = SSLContext.getInstance("TLS");
      context.init(
          null,
          new X509TrustManager[] {
            new X509TrustManager() {
              public void checkClientTrusted(X509Certificate[] chain, String authType)
                  throws CertificateException {}

              public void checkServerTrusted(X509Certificate[] chain, String authType)
                  throws CertificateException {}

              public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
              }
            }
          },
          new SecureRandom());
      HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
    } catch (Exception e) { // should never happen
      e.printStackTrace();
    }
  }
/**
 * SSLSocketFactory implementation with several extra features:
 *
 * <ul>
 *   <li>Timeout specification for SSL handshake operations
 *   <li>Hostname verification in most cases (see WARNINGs below)
 *   <li>Optional SSL session caching with {@link SSLSessionCache}
 *   <li>Optionally bypass all SSL certificate checks
 * </ul>
 *
 * The handshake timeout does not apply to actual TCP socket connection. If you want a connection
 * timeout as well, use {@link #createSocket()} and {@link Socket#connect(SocketAddress, int)},
 * after which you must verify the identity of the server you are connected to.
 *
 * <p class="caution"><b>Most {@link SSLSocketFactory} implementations do not verify the server's
 * identity, allowing man-in-the-middle attacks.</b> This implementation does check the server's
 * certificate hostname, but only for createSocket variants that specify a hostname. When using
 * methods that use {@link InetAddress} or which return an unconnected socket, you MUST verify the
 * server's identity yourself to ensure a secure connection.
 *
 * <p>One way to verify the server's identity is to use {@link
 * HttpsURLConnection#getDefaultHostnameVerifier()} to get a {@link HostnameVerifier} to verify the
 * certificate hostname.
 *
 * <p>On development devices, "setprop socket.relaxsslcheck yes" bypasses all SSL certificate and
 * hostname checks for testing purposes. This setting requires root access.
 */
public class SSLCertificateSocketFactory extends SSLSocketFactory {
  private static final String TAG = "SSLCertificateSocketFactory";

  private static final TrustManager[] INSECURE_TRUST_MANAGER =
      new TrustManager[] {
        new X509TrustManager() {
          public X509Certificate[] getAcceptedIssuers() {
            return null;
          }

          public void checkClientTrusted(X509Certificate[] certs, String authType) {}

          public void checkServerTrusted(X509Certificate[] certs, String authType) {}
        }
      };

  private static final HostnameVerifier HOSTNAME_VERIFIER =
      HttpsURLConnection.getDefaultHostnameVerifier();

  private SSLSocketFactory mInsecureFactory = null;
  private SSLSocketFactory mSecureFactory = null;
  private TrustManager[] mTrustManagers = null;
  private KeyManager[] mKeyManagers = null;
  private byte[] mNpnProtocols = null;
  private byte[] mAlpnProtocols = null;
  private PrivateKey mChannelIdPrivateKey = null;

  private final int mHandshakeTimeoutMillis;
  private final SSLClientSessionCache mSessionCache;
  private final boolean mSecure;

  /** @deprecated Use {@link #getDefault(int)} instead. */
  @Deprecated
  public SSLCertificateSocketFactory(int handshakeTimeoutMillis) {
    this(handshakeTimeoutMillis, null, true);
  }

  private SSLCertificateSocketFactory(
      int handshakeTimeoutMillis, SSLSessionCache cache, boolean secure) {
    mHandshakeTimeoutMillis = handshakeTimeoutMillis;
    mSessionCache = cache == null ? null : cache.mSessionCache;
    mSecure = secure;
  }

  /**
   * Returns a new socket factory instance with an optional handshake timeout.
   *
   * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 for none. The socket
   *     timeout is reset to 0 after the handshake.
   * @return a new SSLSocketFactory with the specified parameters
   */
  public static SocketFactory getDefault(int handshakeTimeoutMillis) {
    return new SSLCertificateSocketFactory(handshakeTimeoutMillis, null, true);
  }

  /**
   * Returns a new socket factory instance with an optional handshake timeout and SSL session cache.
   *
   * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 for none. The socket
   *     timeout is reset to 0 after the handshake.
   * @param cache The {@link SSLSessionCache} to use, or null for no cache.
   * @return a new SSLSocketFactory with the specified parameters
   */
  public static SSLSocketFactory getDefault(int handshakeTimeoutMillis, SSLSessionCache cache) {
    return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true);
  }

  /**
   * Returns a new instance of a socket factory with all SSL security checks disabled, using an
   * optional handshake timeout and SSL session cache.
   *
   * <p class="caution"><b>Warning:</b> Sockets created using this factory are vulnerable to
   * man-in-the-middle attacks!
   *
   * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 for none. The socket
   *     timeout is reset to 0 after the handshake.
   * @param cache The {@link SSLSessionCache} to use, or null for no cache.
   * @return an insecure SSLSocketFactory with the specified parameters
   */
  public static SSLSocketFactory getInsecure(int handshakeTimeoutMillis, SSLSessionCache cache) {
    return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, false);
  }

  /**
   * Returns a socket factory (also named SSLSocketFactory, but in a different namespace) for use
   * with the Apache HTTP stack.
   *
   * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 for none. The socket
   *     timeout is reset to 0 after the handshake.
   * @param cache The {@link SSLSessionCache} to use, or null for no cache.
   * @return a new SocketFactory with the specified parameters
   */
  public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(
      int handshakeTimeoutMillis, SSLSessionCache cache) {
    return new org.apache.http.conn.ssl.SSLSocketFactory(
        new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true));
  }

  /**
   * Verify the hostname of the certificate used by the other end of a connected socket. You MUST
   * call this if you did not supply a hostname to {@link #createSocket()}. It is harmless to call
   * this method redundantly if the hostname has already been verified.
   *
   * <p>Wildcard certificates are allowed to verify any matching hostname, so "foo.bar.example.com"
   * is verified if the peer has a certificate for "*.example.com".
   *
   * @param socket An SSL socket which has been connected to a server
   * @param hostname The expected hostname of the remote server
   * @throws IOException if something goes wrong handshaking with the server
   * @throws SSLPeerUnverifiedException if the server cannot prove its identity
   * @hide
   */
  public static void verifyHostname(Socket socket, String hostname) throws IOException {
    if (!(socket instanceof SSLSocket)) {
      throw new IllegalArgumentException("Attempt to verify non-SSL socket");
    }

    if (!isSslCheckRelaxed()) {
      // The code at the start of OpenSSLSocketImpl.startHandshake()
      // ensures that the call is idempotent, so we can safely call it.
      SSLSocket ssl = (SSLSocket) socket;
      ssl.startHandshake();

      SSLSession session = ssl.getSession();
      if (session == null) {
        throw new SSLException("Cannot verify SSL socket without session");
      }
      if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
        throw new SSLPeerUnverifiedException("Cannot verify hostname: " + hostname);
      }
    }
  }

  private SSLSocketFactory makeSocketFactory(
      KeyManager[] keyManagers, TrustManager[] trustManagers) {
    try {
      OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
      sslContext.engineInit(keyManagers, trustManagers, null);
      sslContext.engineGetClientSessionContext().setPersistentCache(mSessionCache);
      return sslContext.engineGetSocketFactory();
    } catch (KeyManagementException e) {
      Log.wtf(TAG, e);
      return (SSLSocketFactory) SSLSocketFactory.getDefault(); // Fallback
    }
  }

  private static boolean isSslCheckRelaxed() {
    return "1".equals(SystemProperties.get("ro.debuggable"))
        && "yes".equals(SystemProperties.get("socket.relaxsslcheck"));
  }

  private synchronized SSLSocketFactory getDelegate() {
    // Relax the SSL check if instructed (for this factory, or systemwide)
    if (!mSecure || isSslCheckRelaxed()) {
      if (mInsecureFactory == null) {
        if (mSecure) {
          Log.w(TAG, "*** BYPASSING SSL SECURITY CHECKS (socket.relaxsslcheck=yes) ***");
        } else {
          Log.w(TAG, "Bypassing SSL security checks at caller's request");
        }
        mInsecureFactory = makeSocketFactory(mKeyManagers, INSECURE_TRUST_MANAGER);
      }
      return mInsecureFactory;
    } else {
      if (mSecureFactory == null) {
        mSecureFactory = makeSocketFactory(mKeyManagers, mTrustManagers);
      }
      return mSecureFactory;
    }
  }

  /** Sets the {@link TrustManager}s to be used for connections made by this factory. */
  public void setTrustManagers(TrustManager[] trustManager) {
    mTrustManagers = trustManager;

    // Clear out all cached secure factories since configurations have changed.
    mSecureFactory = null;
    // Note - insecure factories only ever use the INSECURE_TRUST_MANAGER so they need not
    // be cleared out here.
  }

  /**
   * Sets the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next Protocol
   * Negotiation (NPN)</a> protocols that this peer is interested in.
   *
   * <p>For servers this is the sequence of protocols to advertise as supported, in order of
   * preference. This list is sent unencrypted to all clients that support NPN.
   *
   * <p>For clients this is a list of supported protocols to match against the server's list. If
   * there is no protocol supported by both client and server then the first protocol in the
   * client's list will be selected. The order of the client's protocols is otherwise insignificant.
   *
   * @param npnProtocols a non-empty list of protocol byte arrays. All arrays must be non-empty and
   *     of length less than 256.
   */
  public void setNpnProtocols(byte[][] npnProtocols) {
    this.mNpnProtocols = toLengthPrefixedList(npnProtocols);
  }

  /**
   * Sets the <a href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg-01">Application
   * Layer Protocol Negotiation (ALPN)</a> protocols that this peer is interested in.
   *
   * <p>For servers this is the sequence of protocols to advertise as supported, in order of
   * preference. This list is sent unencrypted to all clients that support ALPN.
   *
   * <p>For clients this is a list of supported protocols to match against the server's list. If
   * there is no protocol supported by both client and server then the first protocol in the
   * client's list will be selected. The order of the client's protocols is otherwise insignificant.
   *
   * @param protocols a non-empty list of protocol byte arrays. All arrays must be non-empty and of
   *     length less than 256.
   * @hide
   */
  public void setAlpnProtocols(byte[][] protocols) {
    this.mAlpnProtocols = toLengthPrefixedList(protocols);
  }

  /** Returns an array containing the concatenation of length-prefixed byte strings. */
  static byte[] toLengthPrefixedList(byte[]... items) {
    if (items.length == 0) {
      throw new IllegalArgumentException("items.length == 0");
    }
    int totalLength = 0;
    for (byte[] s : items) {
      if (s.length == 0 || s.length > 255) {
        throw new IllegalArgumentException("s.length == 0 || s.length > 255: " + s.length);
      }
      totalLength += 1 + s.length;
    }
    byte[] result = new byte[totalLength];
    int pos = 0;
    for (byte[] s : items) {
      result[pos++] = (byte) s.length;
      for (byte b : s) {
        result[pos++] = b;
      }
    }
    return result;
  }

  /**
   * Returns the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next Protocol
   * Negotiation (NPN)</a> protocol selected by client and server, or null if no protocol was
   * negotiated.
   *
   * @param socket a socket created by this factory.
   * @throws IllegalArgumentException if the socket was not created by this factory.
   */
  public byte[] getNpnSelectedProtocol(Socket socket) {
    return castToOpenSSLSocket(socket).getNpnSelectedProtocol();
  }

  /**
   * Returns the <a href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg-01">Application
   * Layer Protocol Negotiation (ALPN)</a> protocol selected by client and server, or null if no
   * protocol was negotiated.
   *
   * @param socket a socket created by this factory.
   * @throws IllegalArgumentException if the socket was not created by this factory.
   * @hide
   */
  public byte[] getAlpnSelectedProtocol(Socket socket) {
    return castToOpenSSLSocket(socket).getAlpnSelectedProtocol();
  }

  /** Sets the {@link KeyManager}s to be used for connections made by this factory. */
  public void setKeyManagers(KeyManager[] keyManagers) {
    mKeyManagers = keyManagers;

    // Clear out any existing cached factories since configurations have changed.
    mSecureFactory = null;
    mInsecureFactory = null;
  }

  /**
   * Sets the private key to be used for TLS Channel ID by connections made by this factory.
   *
   * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables TLS
   *     Channel ID). The private key has to be an Elliptic Curve (EC) key based on the NIST P-256
   *     curve (aka SECG secp256r1 or ANSI X9.62 prime256v1).
   * @hide
   */
  public void setChannelIdPrivateKey(PrivateKey privateKey) {
    mChannelIdPrivateKey = privateKey;
  }

  /**
   * Enables <a href="http://tools.ietf.org/html/rfc5077#section-3.2">session ticket</a> support on
   * the given socket.
   *
   * @param socket a socket created by this factory
   * @param useSessionTickets {@code true} to enable session ticket support on this socket.
   * @throws IllegalArgumentException if the socket was not created by this factory.
   */
  public void setUseSessionTickets(Socket socket, boolean useSessionTickets) {
    castToOpenSSLSocket(socket).setUseSessionTickets(useSessionTickets);
  }

  /**
   * Turns on <a href="http://tools.ietf.org/html/rfc6066#section-3">Server Name Indication
   * (SNI)</a> on a given socket.
   *
   * @param socket a socket created by this factory.
   * @param hostName the desired SNI hostname, null to disable.
   * @throws IllegalArgumentException if the socket was not created by this factory.
   */
  public void setHostname(Socket socket, String hostName) {
    castToOpenSSLSocket(socket).setHostname(hostName);
  }

  /**
   * Sets this socket's SO_SNDTIMEO write timeout in milliseconds. Use 0 for no timeout. To take
   * effect, this option must be set before the blocking method was called.
   *
   * @param socket a socket created by this factory.
   * @param timeout the desired write timeout in milliseconds.
   * @throws IllegalArgumentException if the socket was not created by this factory.
   * @hide
   */
  public void setSoWriteTimeout(Socket socket, int writeTimeoutMilliseconds)
      throws SocketException {
    castToOpenSSLSocket(socket).setSoWriteTimeout(writeTimeoutMilliseconds);
  }

  private static OpenSSLSocketImpl castToOpenSSLSocket(Socket socket) {
    if (!(socket instanceof OpenSSLSocketImpl)) {
      throw new IllegalArgumentException("Socket not created by this factory: " + socket);
    }

    return (OpenSSLSocketImpl) socket;
  }

  /**
   * {@inheritDoc}
   *
   * <p>This method verifies the peer's certificate hostname after connecting (unless created with
   * {@link #getInsecure(int, SSLSessionCache)}).
   */
  @Override
  public Socket createSocket(Socket k, String host, int port, boolean close) throws IOException {
    OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(k, host, port, close);
    s.setNpnProtocols(mNpnProtocols);
    s.setAlpnProtocols(mAlpnProtocols);
    s.setHandshakeTimeout(mHandshakeTimeoutMillis);
    s.setChannelIdPrivateKey(mChannelIdPrivateKey);
    if (mSecure) {
      verifyHostname(s, host);
    }
    return s;
  }

  /**
   * Creates a new socket which is not connected to any remote host. You must use {@link
   * Socket#connect} to connect the socket.
   *
   * <p class="caution"><b>Warning:</b> Hostname verification is not performed with this method. You
   * MUST verify the server's identity after connecting the socket to avoid man-in-the-middle
   * attacks.
   */
  @Override
  public Socket createSocket() throws IOException {
    OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket();
    s.setNpnProtocols(mNpnProtocols);
    s.setAlpnProtocols(mAlpnProtocols);
    s.setHandshakeTimeout(mHandshakeTimeoutMillis);
    s.setChannelIdPrivateKey(mChannelIdPrivateKey);
    return s;
  }

  /**
   * {@inheritDoc}
   *
   * <p class="caution"><b>Warning:</b> Hostname verification is not performed with this method. You
   * MUST verify the server's identity after connecting the socket to avoid man-in-the-middle
   * attacks.
   */
  @Override
  public Socket createSocket(InetAddress addr, int port, InetAddress localAddr, int localPort)
      throws IOException {
    OpenSSLSocketImpl s =
        (OpenSSLSocketImpl) getDelegate().createSocket(addr, port, localAddr, localPort);
    s.setNpnProtocols(mNpnProtocols);
    s.setAlpnProtocols(mAlpnProtocols);
    s.setHandshakeTimeout(mHandshakeTimeoutMillis);
    s.setChannelIdPrivateKey(mChannelIdPrivateKey);
    return s;
  }

  /**
   * {@inheritDoc}
   *
   * <p class="caution"><b>Warning:</b> Hostname verification is not performed with this method. You
   * MUST verify the server's identity after connecting the socket to avoid man-in-the-middle
   * attacks.
   */
  @Override
  public Socket createSocket(InetAddress addr, int port) throws IOException {
    OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(addr, port);
    s.setNpnProtocols(mNpnProtocols);
    s.setAlpnProtocols(mAlpnProtocols);
    s.setHandshakeTimeout(mHandshakeTimeoutMillis);
    s.setChannelIdPrivateKey(mChannelIdPrivateKey);
    return s;
  }

  /**
   * {@inheritDoc}
   *
   * <p>This method verifies the peer's certificate hostname after connecting (unless created with
   * {@link #getInsecure(int, SSLSessionCache)}).
   */
  @Override
  public Socket createSocket(String host, int port, InetAddress localAddr, int localPort)
      throws IOException {
    OpenSSLSocketImpl s =
        (OpenSSLSocketImpl) getDelegate().createSocket(host, port, localAddr, localPort);
    s.setNpnProtocols(mNpnProtocols);
    s.setAlpnProtocols(mAlpnProtocols);
    s.setHandshakeTimeout(mHandshakeTimeoutMillis);
    s.setChannelIdPrivateKey(mChannelIdPrivateKey);
    if (mSecure) {
      verifyHostname(s, host);
    }
    return s;
  }

  /**
   * {@inheritDoc}
   *
   * <p>This method verifies the peer's certificate hostname after connecting (unless created with
   * {@link #getInsecure(int, SSLSessionCache)}).
   */
  @Override
  public Socket createSocket(String host, int port) throws IOException {
    OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(host, port);
    s.setNpnProtocols(mNpnProtocols);
    s.setAlpnProtocols(mAlpnProtocols);
    s.setHandshakeTimeout(mHandshakeTimeoutMillis);
    s.setChannelIdPrivateKey(mChannelIdPrivateKey);
    if (mSecure) {
      verifyHostname(s, host);
    }
    return s;
  }

  @Override
  public String[] getDefaultCipherSuites() {
    return getDelegate().getSupportedCipherSuites();
  }

  @Override
  public String[] getSupportedCipherSuites() {
    return getDelegate().getSupportedCipherSuites();
  }
}
예제 #8
0
// 自己定制SSLSocketFactory,在createSocket时替换为HTTPDNS的IP,并进行SNI/HostNameVerify配置
class TlsSniSocketFactory extends SSLSocketFactory {
  private final String TAG = TlsSniSocketFactory.class.getSimpleName();
  HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
  private HttpsURLConnection conn;

  public TlsSniSocketFactory(HttpsURLConnection conn) {
    this.conn = conn;
  }

  @Override
  public Socket createSocket() throws IOException {
    return null;
  }

  @Override
  public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
    return null;
  }

  @Override
  public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
      throws IOException, UnknownHostException {
    return null;
  }

  @Override
  public Socket createSocket(InetAddress host, int port) throws IOException {
    return null;
  }

  @Override
  public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
      throws IOException {
    return null;
  }

  // TLS layer

  @Override
  public String[] getDefaultCipherSuites() {
    return new String[0];
  }

  @Override
  public String[] getSupportedCipherSuites() {
    return new String[0];
  }

  @Override
  public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose)
      throws IOException {
    String peerHost = this.conn.getRequestProperty("Host");
    if (peerHost == null) peerHost = host;
    Log.i(TAG, "customized createSocket. host: " + peerHost);
    InetAddress address = plainSocket.getInetAddress();
    if (autoClose) {
      // we don't need the plainSocket
      plainSocket.close();
    }
    // create and connect SSL socket, but don't do hostname/certificate verification yet
    SSLCertificateSocketFactory sslSocketFactory =
        (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
    SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(address, port);

    // enable TLSv1.1/1.2 if available
    ssl.setEnabledProtocols(ssl.getSupportedProtocols());

    // set up SNI before the handshake
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
      Log.i(TAG, "Setting SNI hostname");
      sslSocketFactory.setHostname(ssl, peerHost);
    } else {
      Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection");
      try {
        java.lang.reflect.Method setHostnameMethod =
            ssl.getClass().getMethod("setHostname", String.class);
        setHostnameMethod.invoke(ssl, peerHost);
      } catch (Exception e) {
        Log.w(TAG, "SNI not useable", e);
      }
    }

    // verify hostname and certificate
    SSLSession session = ssl.getSession();

    if (!hostnameVerifier.verify(peerHost, session))
      throw new SSLPeerUnverifiedException("Cannot verify hostname: " + peerHost);

    Log.i(
        TAG,
        "Established "
            + session.getProtocol()
            + " connection with "
            + session.getPeerHost()
            + " using "
            + session.getCipherSuite());

    return ssl;
  }
}
예제 #9
0
 /**
  * Contacts the remote URL and returns the response.
  *
  * @param constructedUrl the url to contact.
  * @param encoding the encoding to use.
  * @return the response.
  */
 public static String getResponseFromServer(final URL constructedUrl, final String encoding) {
   return getResponseFromServer(
       constructedUrl, HttpsURLConnection.getDefaultHostnameVerifier(), encoding);
 }
public class MailTransport {

  // TODO protected eventually
  /*protected*/ public static final int SOCKET_CONNECT_TIMEOUT = 10000;
  /*protected*/ public static final int SOCKET_READ_TIMEOUT = 60000;

  private static final HostnameVerifier HOSTNAME_VERIFIER =
      HttpsURLConnection.getDefaultHostnameVerifier();

  private final String mDebugLabel;
  private final Context mContext;
  protected final HostAuth mHostAuth;

  private Socket mSocket;
  private InputStream mIn;
  private OutputStream mOut;

  public MailTransport(Context context, String debugLabel, HostAuth hostAuth) {
    super();
    mContext = context;
    mDebugLabel = debugLabel;
    mHostAuth = hostAuth;
  }

  /**
   * Returns a new transport, using the current transport as a model. The new transport is
   * configured identically (as if {@link #setSecurity(int, boolean)}, {@link #setPort(int)} and
   * {@link #setHost(String)} were invoked), but not opened or connected in any way.
   */
  @Override
  public MailTransport clone() {
    return new MailTransport(mContext, mDebugLabel, mHostAuth);
  }

  public String getHost() {
    return mHostAuth.mAddress;
  }

  public int getPort() {
    return mHostAuth.mPort;
  }

  public boolean canTrySslSecurity() {
    return (mHostAuth.mFlags & HostAuth.FLAG_SSL) != 0;
  }

  public boolean canTryTlsSecurity() {
    return (mHostAuth.mFlags & HostAuth.FLAG_TLS) != 0;
  }

  public boolean canTrustAllCertificates() {
    return (mHostAuth.mFlags & HostAuth.FLAG_TRUST_ALL) != 0;
  }

  /**
   * Attempts to open a connection using the Uri supplied for connection parameters. Will attempt an
   * SSL connection if indicated.
   */
  public void open() throws MessagingException, CertificateValidationException {
    if (MailActivityEmail.DEBUG) {
      LogUtils.d(
          Logging.LOG_TAG,
          "*** " + mDebugLabel + " open " + getHost() + ":" + String.valueOf(getPort()));
    }

    try {
      SocketAddress socketAddress = new InetSocketAddress(getHost(), getPort());
      if (canTrySslSecurity()) {
        mSocket =
            SSLUtils.getSSLSocketFactory(mContext, mHostAuth, canTrustAllCertificates())
                .createSocket();
      } else {
        mSocket = new Socket();
      }
      mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
      // After the socket connects to an SSL server, confirm that the hostname is as expected
      if (canTrySslSecurity() && !canTrustAllCertificates()) {
        verifyHostname(mSocket, getHost());
      }
      mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
      mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
      mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
    } catch (SSLException e) {
      if (MailActivityEmail.DEBUG) {
        LogUtils.d(Logging.LOG_TAG, e.toString());
      }
      throw new CertificateValidationException(e.getMessage(), e);
    } catch (IOException ioe) {
      if (MailActivityEmail.DEBUG) {
        LogUtils.d(Logging.LOG_TAG, ioe.toString());
      }
      throw new MessagingException(MessagingException.IOERROR, ioe.toString());
    } catch (IllegalArgumentException iae) {
      if (MailActivityEmail.DEBUG) {
        LogUtils.d(Logging.LOG_TAG, iae.toString());
      }
      throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION, iae.toString());
    }
  }

  /**
   * Attempts to reopen a TLS connection using the Uri supplied for connection parameters.
   *
   * <p>NOTE: No explicit hostname verification is required here, because it's handled automatically
   * by the call to createSocket().
   *
   * <p>TODO should we explicitly close the old socket? This seems funky to abandon it.
   */
  public void reopenTls() throws MessagingException {
    try {
      mSocket =
          SSLUtils.getSSLSocketFactory(mContext, mHostAuth, canTrustAllCertificates())
              .createSocket(mSocket, getHost(), getPort(), true);
      mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
      mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
      mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);

    } catch (SSLException e) {
      if (MailActivityEmail.DEBUG) {
        LogUtils.d(Logging.LOG_TAG, e.toString());
      }
      throw new CertificateValidationException(e.getMessage(), e);
    } catch (IOException ioe) {
      if (MailActivityEmail.DEBUG) {
        LogUtils.d(Logging.LOG_TAG, ioe.toString());
      }
      throw new MessagingException(MessagingException.IOERROR, ioe.toString());
    }
  }

  /**
   * Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this service
   * but is not in the public API.
   *
   * <p>Verify the hostname of the certificate used by the other end of a connected socket. You MUST
   * call this if you did not supply a hostname to SSLCertificateSocketFactory.createSocket(). It is
   * harmless to call this method redundantly if the hostname has already been verified.
   *
   * <p>Wildcard certificates are allowed to verify any matching hostname, so "foo.bar.example.com"
   * is verified if the peer has a certificate for "*.example.com".
   *
   * @param socket An SSL socket which has been connected to a server
   * @param hostname The expected hostname of the remote server
   * @throws IOException if something goes wrong handshaking with the server
   * @throws SSLPeerUnverifiedException if the server cannot prove its identity
   */
  private static void verifyHostname(Socket socket, String hostname) throws IOException {
    // The code at the start of OpenSSLSocketImpl.startHandshake()
    // ensures that the call is idempotent, so we can safely call it.
    SSLSocket ssl = (SSLSocket) socket;
    ssl.startHandshake();

    SSLSession session = ssl.getSession();
    if (session == null) {
      throw new SSLException("Cannot verify SSL socket without session");
    }
    // TODO: Instead of reporting the name of the server we think we're connecting to,
    // we should be reporting the bad name in the certificate.  Unfortunately this is buried
    // in the verifier code and is not available in the verifier API, and extracting the
    // CN & alts is beyond the scope of this patch.
    if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
      throw new SSLPeerUnverifiedException(
          "Certificate hostname not useable for server: " + hostname);
    }
  }

  /**
   * Set the socket timeout.
   *
   * @param timeoutMilliseconds the read timeout value if greater than {@code 0}, or {@code 0} for
   *     an infinite timeout.
   */
  public void setSoTimeout(int timeoutMilliseconds) throws SocketException {
    mSocket.setSoTimeout(timeoutMilliseconds);
  }

  public boolean isOpen() {
    return (mIn != null
        && mOut != null
        && mSocket != null
        && mSocket.isConnected()
        && !mSocket.isClosed());
  }

  /** Close the connection. MUST NOT return any exceptions - must be "best effort" and safe. */
  public void close() {
    try {
      mIn.close();
    } catch (Exception e) {
      // May fail if the connection is already closed.
    }
    try {
      mOut.close();
    } catch (Exception e) {
      // May fail if the connection is already closed.
    }
    try {
      mSocket.close();
    } catch (Exception e) {
      // May fail if the connection is already closed.
    }
    mIn = null;
    mOut = null;
    mSocket = null;
  }

  public InputStream getInputStream() {
    return mIn;
  }

  public OutputStream getOutputStream() {
    return mOut;
  }

  /** Writes a single line to the server using \r\n termination. */
  public void writeLine(String s, String sensitiveReplacement) throws IOException {
    if (MailActivityEmail.DEBUG) {
      if (sensitiveReplacement != null && !Logging.DEBUG_SENSITIVE) {
        LogUtils.d(Logging.LOG_TAG, ">>> " + sensitiveReplacement);
      } else {
        LogUtils.d(Logging.LOG_TAG, ">>> " + s);
      }
    }

    OutputStream out = getOutputStream();
    out.write(s.getBytes());
    out.write('\r');
    out.write('\n');
    out.flush();
  }

  /**
   * Reads a single line from the server, using either \r\n or \n as the delimiter. The delimiter
   * char(s) are not included in the result.
   */
  public String readLine(boolean loggable) throws IOException {
    StringBuffer sb = new StringBuffer();
    InputStream in = getInputStream();
    int d;
    while ((d = in.read()) != -1) {
      if (((char) d) == '\r') {
        continue;
      } else if (((char) d) == '\n') {
        break;
      } else {
        sb.append((char) d);
      }
    }
    if (d == -1 && MailActivityEmail.DEBUG) {
      LogUtils.d(Logging.LOG_TAG, "End of stream reached while trying to read line.");
    }
    String ret = sb.toString();
    if (loggable && MailActivityEmail.DEBUG) {
      LogUtils.d(Logging.LOG_TAG, "<<< " + ret);
    }
    return ret;
  }

  public InetAddress getLocalAddress() {
    if (isOpen()) {
      return mSocket.getLocalAddress();
    } else {
      return null;
    }
  }
}
예제 #11
0
/**
 * This class implements the common aspects of "transport", one layer below the specific wire
 * protocols such as POP3, IMAP, or SMTP.
 */
public class MailTransport implements Transport {

  // TODO protected eventually
  /*protected*/ public static final int SOCKET_CONNECT_TIMEOUT = 10000;
  /*protected*/ public static final int SOCKET_READ_TIMEOUT = 60000;

  private static final HostnameVerifier HOSTNAME_VERIFIER =
      HttpsURLConnection.getDefaultHostnameVerifier();

  private String mDebugLabel;

  private String mHost;
  private int mPort;
  private String[] mUserInfoParts;
  private int mConnectionSecurity;
  private boolean mTrustCertificates;

  private Socket mSocket;
  private InputStream mIn;
  private OutputStream mOut;

  /**
   * Simple constructor for starting from scratch. Call setUri() and setSecurity() to complete the
   * configuration.
   *
   * @param debugLabel Label used for Log.d calls
   */
  public MailTransport(String debugLabel) {
    super();
    mDebugLabel = debugLabel;
  }

  /**
   * Get a new transport, using an existing one as a model. The new transport is configured as if
   * setUri() and setSecurity() have been called, but not opened or connected in any way.
   *
   * @return a new Transport ready to open()
   */
  public Transport newInstanceWithConfiguration() {
    MailTransport newObject = new MailTransport(mDebugLabel);

    newObject.mDebugLabel = mDebugLabel;
    newObject.mHost = mHost;
    newObject.mPort = mPort;
    if (mUserInfoParts != null) {
      newObject.mUserInfoParts = mUserInfoParts.clone();
    }
    newObject.mConnectionSecurity = mConnectionSecurity;
    newObject.mTrustCertificates = mTrustCertificates;
    return newObject;
  }

  public void setUri(URI uri, int defaultPort) {
    mHost = uri.getHost();

    mPort = defaultPort;
    if (uri.getPort() != -1) {
      mPort = uri.getPort();
    }

    if (uri.getUserInfo() != null) {
      mUserInfoParts = uri.getUserInfo().split(":", 2);
    }
  }

  public String[] getUserInfoParts() {
    return mUserInfoParts;
  }

  public String getHost() {
    return mHost;
  }

  public int getPort() {
    return mPort;
  }

  public void setSecurity(int connectionSecurity, boolean trustAllCertificates) {
    mConnectionSecurity = connectionSecurity;
    mTrustCertificates = trustAllCertificates;
  }

  public int getSecurity() {
    return mConnectionSecurity;
  }

  public boolean canTrySslSecurity() {
    return mConnectionSecurity == CONNECTION_SECURITY_SSL;
  }

  public boolean canTryTlsSecurity() {
    return mConnectionSecurity == Transport.CONNECTION_SECURITY_TLS;
  }

  public boolean canTrustAllCertificates() {
    return mTrustCertificates;
  }

  /**
   * Attempts to open a connection using the Uri supplied for connection parameters. Will attempt an
   * SSL connection if indicated.
   */
  public void open() throws MessagingException, CertificateValidationException {
    if (Config.LOGD && Email.DEBUG) {
      Log.d(
          Email.LOG_TAG,
          "*** " + mDebugLabel + " open " + getHost() + ":" + String.valueOf(getPort()));
    }

    try {
      SocketAddress socketAddress = new InetSocketAddress(getHost(), getPort());
      if (canTrySslSecurity()) {
        mSocket = SSLUtils.getSSLSocketFactory(canTrustAllCertificates()).createSocket();
      } else {
        mSocket = new Socket();
      }
      mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
      // After the socket connects to an SSL server, confirm that the hostname is as expected
      if (canTrySslSecurity() && !canTrustAllCertificates()) {
        verifyHostname(mSocket, getHost());
      }
      mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
      mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);

    } catch (SSLException e) {
      if (Config.LOGD && Email.DEBUG) {
        Log.d(Email.LOG_TAG, e.toString());
      }
      throw new CertificateValidationException(e.getMessage(), e);
    } catch (IOException ioe) {
      if (Config.LOGD && Email.DEBUG) {
        Log.d(Email.LOG_TAG, ioe.toString());
      }
      throw new MessagingException(MessagingException.IOERROR, ioe.toString());
    }
  }

  /**
   * Attempts to reopen a TLS connection using the Uri supplied for connection parameters.
   *
   * <p>NOTE: No explicit hostname verification is required here, because it's handled automatically
   * by the call to createSocket().
   *
   * <p>TODO should we explicitly close the old socket? This seems funky to abandon it.
   */
  public void reopenTls() throws MessagingException {
    try {
      mSocket =
          SSLUtils.getSSLSocketFactory(canTrustAllCertificates())
              .createSocket(mSocket, getHost(), getPort(), true);
      mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
      mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
      mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);

    } catch (SSLException e) {
      if (Config.LOGD && Email.DEBUG) {
        Log.d(Email.LOG_TAG, e.toString());
      }
      throw new CertificateValidationException(e.getMessage(), e);
    } catch (IOException ioe) {
      if (Config.LOGD && Email.DEBUG) {
        Log.d(Email.LOG_TAG, ioe.toString());
      }
      throw new MessagingException(MessagingException.IOERROR, ioe.toString());
    }
  }

  /**
   * Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this service
   * but is not in the public API.
   *
   * <p>Verify the hostname of the certificate used by the other end of a connected socket. You MUST
   * call this if you did not supply a hostname to SSLCertificateSocketFactory.createSocket(). It is
   * harmless to call this method redundantly if the hostname has already been verified.
   *
   * <p>Wildcard certificates are allowed to verify any matching hostname, so "foo.bar.example.com"
   * is verified if the peer has a certificate for "*.example.com".
   *
   * @param socket An SSL socket which has been connected to a server
   * @param hostname The expected hostname of the remote server
   * @throws IOException if something goes wrong handshaking with the server
   * @throws SSLPeerUnverifiedException if the server cannot prove its identity
   */
  private void verifyHostname(Socket socket, String hostname) throws IOException {
    // The code at the start of OpenSSLSocketImpl.startHandshake()
    // ensures that the call is idempotent, so we can safely call it.
    SSLSocket ssl = (SSLSocket) socket;
    ssl.startHandshake();

    SSLSession session = ssl.getSession();
    if (session == null) {
      throw new SSLException("Cannot verify SSL socket without session");
    }
    // TODO: Instead of reporting the name of the server we think we're connecting to,
    // we should be reporting the bad name in the certificate.  Unfortunately this is buried
    // in the verifier code and is not available in the verifier API, and extracting the
    // CN & alts is beyond the scope of this patch.
    if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
      throw new SSLPeerUnverifiedException(
          "Certificate hostname not useable for server: " + hostname);
    }
  }

  /**
   * Set the socket timeout.
   *
   * @param timeoutMilliseconds the read timeout value if greater than {@code 0}, or {@code 0} for
   *     an infinite timeout.
   */
  public void setSoTimeout(int timeoutMilliseconds) throws SocketException {
    mSocket.setSoTimeout(timeoutMilliseconds);
  }

  public boolean isOpen() {
    return (mIn != null
        && mOut != null
        && mSocket != null
        && mSocket.isConnected()
        && !mSocket.isClosed());
  }

  /** Close the connection. MUST NOT return any exceptions - must be "best effort" and safe. */
  public void close() {
    try {
      mIn.close();
    } catch (Exception e) {
      // May fail if the connection is already closed.
    }
    try {
      mOut.close();
    } catch (Exception e) {
      // May fail if the connection is already closed.
    }
    try {
      mSocket.close();
    } catch (Exception e) {
      // May fail if the connection is already closed.
    }
    mIn = null;
    mOut = null;
    mSocket = null;
  }

  public InputStream getInputStream() {
    return mIn;
  }

  public OutputStream getOutputStream() {
    return mOut;
  }

  /** Writes a single line to the server using \r\n termination. */
  public void writeLine(String s, String sensitiveReplacement) throws IOException {
    if (Config.LOGD && Email.DEBUG) {
      if (sensitiveReplacement != null && !Email.DEBUG_SENSITIVE) {
        Log.d(Email.LOG_TAG, ">>> " + sensitiveReplacement);
      } else {
        Log.d(Email.LOG_TAG, ">>> " + s);
      }
    }

    OutputStream out = getOutputStream();
    out.write(s.getBytes());
    out.write('\r');
    out.write('\n');
    out.flush();
  }

  /**
   * Reads a single line from the server, using either \r\n or \n as the delimiter. The delimiter
   * char(s) are not included in the result.
   */
  public String readLine() throws IOException {
    StringBuffer sb = new StringBuffer();
    InputStream in = getInputStream();
    int d;
    while ((d = in.read()) != -1) {
      if (((char) d) == '\r') {
        continue;
      } else if (((char) d) == '\n') {
        break;
      } else {
        sb.append((char) d);
      }
    }
    if (d == -1 && Config.LOGD && Email.DEBUG) {
      Log.d(Email.LOG_TAG, "End of stream reached while trying to read line.");
    }
    String ret = sb.toString();
    if (Config.LOGD) {
      if (Email.DEBUG) {
        Log.d(Email.LOG_TAG, "<<< " + ret);
      }
    }
    return ret;
  }
}