2016-02-24 18 views
7

Desidero recuperare il certificato del server pubblico inviato di un Microsoft SQL Server (2012/2014) durante l'handshake SSL/TLS utilizzando la mia applicazione Java.Recupera chiave certificato server pubblico durante l'handshake

Il mio ambiente prima:

  • MS SQL è configurato per utilizzare la crittografia forza
  • accetta solo connessione SSL/TLS
  • ha tra l'altro una CA autofirmato e un certificato rilasciato da detto CA
  • certificato rilasciato è utilizzato dal server MS SQL

per realizzare questo programma maticamente Sto usando la mia implementazione del gestore di fiducia. Si prega di consultare l'estratto del relativo codice qui:

SSLSocket sslSocket = (SSLSocket) getFactory().createSocket(socket, host, port, true); 
sslSocket.startHandshake(); 

getFactory():

private SSLSocketFactory getFactory() throws IOException 
{ 
    // irrelevant code removed here 
    return factory(); 
} 

fabbrica():

private static SSLSocketFactory factory() throws NoSuchAlgorithmException, KeyManagementException 
{ 
    SSLSocketFactory factorySingleton; 
    SSLContext ctx = SSLContext.getInstance("TLS"); 
    ctx.init(null, getTrustManager(), null); 
    factorySingleton = ctx.getSocketFactory(); 

    return factorySingleton; 
} 

getTrustManager():

private static TrustManager[] getTrustManager() 
{ 
    X509Certificate[] server = null; 
    X509Certificate[] client = null; 
    X509TrustManager tm = new X509TrustManager() 
    { 
     X509Certificate[] server1 = null; 
     X509Certificate[] client1 = null; 
     public X509Certificate[] getAcceptedIssuers() 
     { 
      return new X509Certificate[0]; 
     } 

     public void checkServerTrusted(X509Certificate[] chain, String x) 
     { 
      server1 = chain; 
      Logger.println("X509 Certificate chain: " + chain); 
     } 

     public void checkClientTrusted(X509Certificate[] chain, String x) 
     { 
      client1 = chain; 
      Logger.println("X509 Certificate chain: " + chain); 
     } 
    }; 

    return new X509TrustManager[]{tm}; 
} 

Mi aspettavo che la chiamata a startHandshake() a un certo punto renderebbe la mia applicazione ricevere i diversi certificati dal mio server SQL e nel tentativo di verificarli chiamano il mio gestore di fiducia personalizzato. A questo punto avrei i certificati (catena X509Certificate []). Ma il mio gestore di fiducia non viene chiamato o almeno i punti di interruzione all'interno di entrambi i metodi di controllo non vengono chiamati.

Questo è uno dei documenti MS che ho usato come riferimento: https://msdn.microsoft.com/en-us/library/bb879919(v=sql.110).aspx#Anchor_1

"Durante handshake SSL, il server invia il suo certificato di chiave pubblica al cliente." < --- esattamente quello che voglio/ho bisogno.

+0

Questo dovrebbe funzionare esattamente come previsto. Forse lo ssl-handshake o la connessione tcp falliscono già? – Ctx

+0

Non credo perché posso vedere la stretta di mano che si verifica con l'opzione di debug di jvm e se esporto manualmente il certificato del server e lo metto sulla mia macchina in un trust store e lo utilizzo per la connessione ssl. – sceiler

+0

Forse c'è qualcosa di sbagliato nella registrazione? Ho testato il codice (usando System.out.println) e l'output di debug è apparso come previsto – Ctx

risposta

2

Dopo lunghe ricerche di una settimana ho trovato il problema. Ciò che non funziona/è solo una soluzione alternativa può essere visto qui: https://superuser.com/questions/1042525/retrieve-server-certificate-from-sql-server-2012-to-trust

Il problema/problema è il protocollo TDS (Tabular Data Stream) utilizzato da Microsoft, che è un protocollo a livello di applicazione che include tutti i livelli e le connessioni sottostanti. Ciò significa che un driver deve implementare questo protocollo TDS durante la connessione al server Microsoft SQL o Sybase (inizialmente TDS è stato creato da Sybase). FreeTDS è una tale implementazione e per Java c'è jTDS, che purtroppo è per lo più morto. Nonostante ciò ci sono ancora alcune correzioni fatte ma non incluse e rilasciate come una nuova versione di jTDS. jTDS può essere trovato qui: https://sourceforge.net/projects/jtds/files/ ma con Java 1.8 c'è stata una modifica a un tipo di dati che ha causato a jTDS l'invio di 256 byte di assurdità a MSSQL rendendo quindi impossibile SSL/TLS. Questo è stato risolto in r1286 (https://sourceforge.net/p/jtds/code/commit_browser)

Dopo aver applicato queste modifiche e utilizzando almeno la proprietà stringa di connessione SSL=require il manager fiducia personalizzata in net\sourceforge\jtds\ssl\SocketFactories.java:

private static TrustManager[] trustManagers() 
{ 
    X509TrustManager tm = new X509TrustManager() 
    { 
     public X509Certificate[] getAcceptedIssuers() 
     { 
      return new X509Certificate[0]; 
     } 

     public void checkServerTrusted(X509Certificate[] chain, String x) 
     { 
      // Dummy method 
     } 

     public void checkClientTrusted(X509Certificate[] chain, String x) 
     { 
      // Dummy method 
     } 
    }; 

    return new X509TrustManager[]{tm}; 
} 

sarà chiamato. Con questo il modo descritto in OP può essere usato per recuperare un certificato dal server.Questo non è l'utilizzo previsto così uno ha bisogno di aggiungere un po 'brutto getter/setter e trucchi per ottenere effettivamente il certificato di un tale approccio è le seguenti modifiche:

In net\sourceforge\jtds\jdbc\SharedSocket.java cambiamento enableEncryption() a questo:

void enableEncryption(String ssl) throws IOException 
{ 
    Logger.println("Enabling TLS encryption"); 
    SocketFactory sf = SocketFactories.getSocketFactory(ssl, socket); 
    sslSocket = sf.createSocket(getHost(), getPort()); 
    SSLSocket s = (SSLSocket) sslSocket; 
    s.startHandshake(); 
    setX509Certificates(s.getSession().getPeerCertificateChain()); 

    setOut(new DataOutputStream(sslSocket.getOutputStream())); 
    setIn(new DataInputStream(sslSocket.getInputStream())); 
} 

e aggiungere il seguente campo con la sua getter/setter:

private javax.security.cert.X509Certificate[] x509Certificates; 

private void setX509Certificates(javax.security.cert.X509Certificate[] certs) 
{ 
    x509Certificates = certs; 
} 

public javax.security.cert.X509Certificate[] getX509Certificates() 
{ 
    return x509Certificates; 
} 

In net\sourceforge\jtds\jdbc\TdsCore.java cambiamento negotiateSSL() in modo che questo si COMPRESO:

if (sslMode != SSL_NO_ENCRYPT) 
{ 
    socket.enableEncryption(ssl); 
    setX509Certificate(socket.getX509Certificates()); 
} 

E ancora hanno lo stesso campo esattamente con getter/setter:

Lo stesso deve essere fatto per net\sourceforge\jtds\jdbc\JtdsConnection.java s costruttore JtdsConnection()

chiamare setX509Certificates(baseTds.getX509Certificate()) dopo negotiateSSL() è stato chiamato sul baseTds.negotiateSSL() all'interno il costruttore. Questa classe deve contenere anche il getter/setter:

public javax.security.cert.X509Certificate[] getX509Certificates() 
{ 
    return x509Certificates; 
} 

public void setX509Certificates(javax.security.cert.X509Certificate[] x509Certificates) 
{ 
    this.x509Certificates = x509Certificates; 
} 

private javax.security.cert.X509Certificate[] x509Certificates; 

Infine, si può creare la propria classe di utilità a fare uso di tutti questi Inoltre in questo modo:

JtdsConnection jtdsConnection = new JtdsConnection(url, <properties to be inserted>); 
X509Certificate[] certs = jtdsConnection.getX509Certificates() 

Per le proprietà (non sono tutti quelli standard che di solito si trovano per jdbc) utilizzano lo DefaultProperties.addDefaultProperties() fornito e successivamente cambiano utente, password, host, ecc nell'oggetto new Properties().

PS .: Ci si potrebbe chiedere perché tutte queste modifiche ingombranti ... ad esempio, a causa di motivi di licenza non si può spedire il driver jdbc di Microsofts o non si vuole/non può usarlo, questo fornisce e un'alternativa.