2014-07-01 100 views
5

这是一个我最近一直在努力解决的问题,很大程度上是因为我觉得互联网上关于这个问题的信息太多了,没有帮助。所以,因为我刚刚找到了适合我的解决方案,所以我决定在这里发布问题和解决方案,希望我能够让互联网成为一个稍微好一点的地方,让那些追随我的人! (希望这不会导致“无益”的内容!)使用自签名证书的Android SSLSockets

我有一个Android应用程序,我一直在开发。直到最近,我刚刚使用ServerSockets和套接字在我的应用程序和我的服务器之间进行通信。但是,通信需要安全,所以我一直在试图将这些转换为SSLServerSockets和SSLSockets,结果比我预期的要困难得多。

鉴于这只是一个原型,在使用自签名证书时没有(安全)危害,这正是我正在做的。正如你可能猜到的那样,这是问题出现的地方。这就是我所做的,以及我遇到的问题。

我生成的文件 “mykeystore.jks” 用下面的命令:

keytool -genkey -alias serverAlias -keyalg RSA -keypass MY_PASSWORD -storepass MY_PASSWORD -keystore mykeystore.jks 

这是服务器代码:

import java.io.BufferedReader; 
import java.io.BufferedWriter; 
import java.io.InputStreamReader; 
import java.io.OutputStreamWriter; 
import java.net.InetAddress; 
import javax.net.ssl.SSLServerSocket; 
import javax.net.ssl.SSLServerSocketFactory; 
import javax.net.ssl.SSLSocket; 

/** 
* Server 
*/ 
public class simplesslserver { 
    // Global variables 
    private static SSLServerSocketFactory ssf; 
    private static SSLServerSocket ss; 
    private static final int port = 8081; 
    private static String address; 

    /** 
    * Main: Starts the server and waits for clients to connect. 
    * Each client is given its own thread. 
    * @param args 
    */ 
    public static void main(String[] args) { 
     try { 
      // System properties 
      System.setProperty("javax.net.ssl.keyStore","mykeystore.jks"); 
      System.setProperty("javax.net.ssl.keyStorePassword","MY_PASSWORD"); 

      // Start server 
      ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); 
      ss = (SSLServerSocket) ssf.createServerSocket(port); 
      address = InetAddress.getLocalHost().toString(); 
      System.out.println("Server started at "+address+" on port "+port+"\n"); 

      // Wait for messages 
      while (true) { 
       SSLSocket connected = (SSLSocket) ss.accept(); 
       new clientThread(connected).start(); 
      }     
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 

    /** 
    * Client thread. 
    */ 
    private static class clientThread extends Thread { 
     // Variables 
     private SSLSocket cs; 
     private InputStreamReader isr; 
     private OutputStreamWriter osw; 
     private BufferedReader br; 
     private BufferedWriter bw; 

     /** 
     * Constructor: Initialises client socket. 
     * @param clientSocket The socket connected to the client. 
     */ 
     public clientThread(SSLSocket clientSocket) { 
      cs = clientSocket; 
     } 

     /** 
     * Starts the thread. 
     */ 
     public void run() { 
      try { 
       // Initialise streams 
       isr = new InputStreamReader(cs.getInputStream()); 
       br = new BufferedReader(isr); 
       osw = new OutputStreamWriter(cs.getOutputStream()); 
       bw = new BufferedWriter(osw); 

       // Get request from client 
       String tmp = br.readLine(); 
       System.out.println("received: "+tmp); 

       // Send response to client 
       String resp = "You said '"+tmp+"'!"; 
       bw.write(resp);   
       bw.newLine(); 
       bw.flush(); 
       System.out.println("response: "+resp); 
      } catch (Exception e) { 
       e.printStackTrace(); 
      } 
     } 
    } 
} 

这是从的Android的提取物应用程序(客户端)

String message = "Hello World"; 
try{ 
    // Create SSLSocketFactory 
    SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); 

    // Create socket using SSLSocketFactory 
    SSLSocket client = (SSLSocket) factory.createSocket("SERVER_IP_ADDRESS", 8081); 

    // Print system information 
    System.out.println("Connected to server " + client.getInetAddress() + ": " + client.getPort()); 

    // Writer and Reader 
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); 
    BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream())); 

    // Send request to server 
    System.out.println("Sending request: "+message); 
    writer.write(message); 
    writer.newLine(); 
    writer.flush(); 

    // Receive response from server 
    String response = reader.readLine(); 
    System.out.println("Received from the Server: "+response); 

    // Close connection 
    client.close(); 

    return response; 
} catch(Exception e) { 
    e.printStackTrace(); 
} 
return "Something went wrong..."; 

当我运行代码时,它不起作用,这是我得到的输出。

客户端输出:

Connected to server /SERVER_IP_ADDRESS: 8081 
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found. 
... stack trace ... 

服务器输出:

received: null 
java.net.SocketException: Connection closed by remote host 

由于证书是自签名的应用程序不信任它。我查看了Google,总的来说,我需要创建一个基于自定义TrustManager的SSLContext(在客户端),该自定义TrustManager接受这个自签名证书。很简单,我想。在接下来的一周里,我尝试了更多的方法来解决这个问题,但我无法记住,但没有成功。我现在请你回到我原来的陈述:那里有太多不完整的信息,这使得解决方案比本来要困难得多。

我发现的唯一工作解决方案是使TrustManager接受ALL证书。

private static class AcceptAllTrustManager implements X509TrustManager { 
    @Override 
    public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {} 

    @Override 
    public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {} 

    @Override 
    public X509Certificate[] getAcceptedIssuers() { return null; } 
} 

可以使用这样

SSLContext sslctx = SSLContext.getInstance("TLS"); 
sslctx.init(new KeyManager[0], new TrustManager[] {new AcceptAllTrustManager()}, new SecureRandom()); 
SSLSocketFactory factory = sslctx.getSocketFactory(); 
SSLSocket client = (SSLSocket) factory.createSocket("IP_ADDRESS", 8081); 

并给出了没有例外的好和快乐的输出!

Connected to server /SERVER_IP_ADDRESS: 8081 
Sending request: Hello World 
Received from the Server: You said 'Hello World'! 

然而,这不是一个好主意,因为应用程序仍然是不安全的,由于潜在的人在这方面的中间人攻击。

所以我被卡住了。我怎么能让应用程序信任我自己的自签名证书,但不仅仅是那里的任何证书?

回答

3

我找到了一种显然应该基于SSLContext创建SSLSocket的方法,该SSLContext基于信任mykeystore的TrustManager。 诀窍是,我们需要将密钥库加载到自定义信任管理器中,以便SSLSocket基于信任我自己的自签名证书的SSLContext。这是通过将密钥库加载到信任管理器中完成的。

我发现这样做,这是的代码如下:

KeyStore keyStore = KeyStore.getInstance("JKS"); 
keyStore.load(getResources().openRawResource(R.raw.mykeystore), "MY_PASSWORD".toCharArray()); 

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 
trustManagerFactory.init(keyStore); 

SSLContext sslctx = SSLContext.getInstance("TLS"); 
sslctx.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom()); 

SSLSocketFactory factory = sslctx.getSocketFactory(); 

哪个及时失败。

java.security.KeyStoreException: java.security.NoSuchAlgorithmException: KeyStore JKS implementation not found 

显然,Android不支持JKS。它必须采用BKS格式。

于是我找到了一种方法,从JKS转换为BKS通过运行以下命令:

keytool -importkeystore -srckeystore mykeystore.jks -destkeystore mykeystore.bks -srcstoretype JKS -deststoretype BKS -srcstorepass MY_PASSWORD -deststorepass MY_PASSWORD -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-jdk15on-150.jar 

现在,我有一个名为mykeystore.bks文件,该文件是完全一样mykeystore.jks,除了在BKS格式(这是Android接受的唯一格式)。

在我的Android应用程序中使用“mykeystore.bks”,在我的服务器上使用“mykeystore.jks”,它可以工作!

客户端输出:

Connected to server /SERVER_IP_ADDRESS: 8081 
Sending request: Hello World 
Received from the Server: You said 'Hello World'! 

服务器输出:

received: Hello World 
response: You said 'Hello World'! 

我们就大功告成了!我的Android应用程序和我的服务器之间的SSLServerSocket/SSLSocket连接现在与我的自签名证书一起工作。

这里是我的Android应用程序中的最终代码:

String message = "Hello World"; 
try{ 
    // Load the server keystore 
    KeyStore keyStore = KeyStore.getInstance("BKS"); 
    keyStore.load(ctx.getResources().openRawResource(R.raw.mykeystore), "MY_PASSWORD".toCharArray()); 

    // Create a custom trust manager that accepts the server self-signed certificate 
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 
    trustManagerFactory.init(keyStore); 

    // Create the SSLContext for the SSLSocket to use 
    SSLContext sslctx = SSLContext.getInstance("TLS"); 
    sslctx.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom()); 

    // Create SSLSocketFactory 
    SSLSocketFactory factory = sslctx.getSocketFactory(); 

    // Create socket using SSLSocketFactory 
    SSLSocket client = (SSLSocket) factory.createSocket("SERVER_IP_ADDRESS", 8081); 

    // Print system information 
    System.out.println("Connected to server " + client.getInetAddress() + ": " + client.getPort()); 

    // Writer and Reader 
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); 
    BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream())); 

    // Send request to server 
    System.out.println("Sending request: "+message); 
    writer.write(message); 
    writer.newLine(); 
    writer.flush(); 

    // Receive response from server 
    String response = reader.readLine(); 
    System.out.println("Received from the Server: "+response); 

    // Close connection 
    client.close(); 

    return response; 
} catch(Exception e) { 
    e.printStackTrace(); 
} 
return "Something went wrong..."; 

(请注意,服务器的代码并没有改变,以及完整的服务器代码是在原来的问题。)