Questo è un problema che ho dovuto davvero risolvere di recente, in gran parte perché ritenevo che ci fossero troppe informazioni su Internet relative a questo problema che non ha aiutato. Quindi, da quando ho appena trovato una soluzione che funziona per me, ho deciso di postare il problema e la soluzione qui, nella speranza che io possa rendere Internet un posto leggermente migliore per coloro che verranno dopo di me! (Si spera che questo non contribuisca al contenuto "inutile"!)SSLSocket Android che utilizzano certificati autofirmati
Ho un'applicazione Android che ho sviluppato. Fino a poco tempo fa, stavo usando ServerSockets e Sockets per comunicare tra la mia app e il mio server. Tuttavia, le comunicazioni devono essere sicure, quindi ho cercato di convertirle in SSLServerSockets e SSLSockets, che risulta essere molto più difficile di quanto mi aspettassi.
Visto che si tratta solo di un prototipo, non c'è alcun danno (di sicurezza) nell'utilizzo di certificati autofirmati, che è quello che sto facendo. Come probabilmente avete indovinato, è qui che i problemi arrivano. Questo è quello che ho fatto e i problemi che ho incontrato.
ho generato il file "mykeystore.jks" con il seguente comando:
keytool -genkey -alias serverAlias -keyalg RSA -keypass MY_PASSWORD -storepass MY_PASSWORD -keystore mykeystore.jks
Questo è il codice server:
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();
}
}
}
}
Questo è un estratto del Android applicazione (client):
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...";
Quando ho eseguito il codice, non ha funzionato, e questo è l'output che stavo ottenendo.
uscita Cliente:
Connected to server /SERVER_IP_ADDRESS: 8081
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
... stack trace ...
uscita Server:
received: null
java.net.SocketException: Connection closed by remote host
Come il certificato è autofirmato, l'applicazione non si fida di esso. Ho dato un'occhiata a Google, e il consenso generale è che ho bisogno di creare un SSLContext (nel client) che è basato su un TrustManager personalizzato che accetta questo certificato autofirmato. Abbastanza semplice, pensai. Durante la settimana successiva, ho provato più metodi per risolvere questo problema di quanto io possa ricordare, senza successo. Ora ti rimando alla mia dichiarazione originale: ci sono troppe informazioni incomplete là fuori, che hanno reso la soluzione molto più difficile di quanto avrebbe dovuto essere.
L'unica soluzione funzionante che ho trovato è stata la creazione di un TrustManager che accetta i certificati 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; }
}
che può essere utilizzato in questo modo
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);
E dà l'output bello e felice senza eccezioni!
Connected to server /SERVER_IP_ADDRESS: 8081
Sending request: Hello World
Received from the Server: You said 'Hello World'!
Tuttavia, questa non è una buona idea, in quanto l'applicazione sarà ancora non protetta, grazie a potenziali attacchi man-in-the-middle.
Quindi ero bloccato. Come posso ottenere che l'app consideri attendibile il mio certificato autofirmato, ma non tutti i certificati in circolazione?