2013-08-06 63 views
6

我使用帶有HTTPS和有效證書的Jetty,並且我不確定是否正確,因爲密碼套件在服務器日誌中似乎爲SSL_NULL_WITH_NULL_NULL。然而,客戶端日誌看起來不錯。SSL_NULL_WITH_NULL_NULL密碼套件在碼頭日誌中

漫長的故事:我附上了一個期望Jetty-7.6.10和兩個腳本來創建密鑰庫和信任庫的Java示例。

JettyHttpsForStackOverflow一起運行客戶端和服務器,或者單獨運行客戶端和服務器以清理日誌。

create-chains.sh腳本創建密鑰庫和信任庫。密鑰庫包含由臨時密鑰庫生成的根證書頒發機構結束的鏈。它使用認證中心和中間證書複製真實案例。

create-single-autosigned.sh腳本也創建密鑰庫和信任庫,但使用自簽名證書。

請注意,SSL_NULL_WITH_NULL_NULL作爲服務器的密碼套件與兩個證書鏈一起出現。

我認爲服務器域名沒有問題。在正確簽署的證書中,域名與可分辨名稱匹配的計算機上運行的服務器出現同樣的問題。 SSLLab確認我的服務器上的SSL工作正常(B級),並且Google Chrome連接愉快。

我認爲Jetty客戶端沒有問題。正如我使用它,它只是調用我設置的SSLContextFactory來創建SSLSocket。令人驚訝的是,在Jetty客戶端日誌中,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA似乎是正在使用的密碼套件。

在Jetty服務器日誌中獲取SSL_NULL_WITH_NULL_NULL是否正常?如果沒有,如何讓事情正確?

create-single-autosigned.sh

#!/bin/bash 

rm their-keystore.jks 2> /dev/null 
rm my-keystore.jks 2> /dev/null 
rm my-truststore.jks 2> /dev/null 

echo "====================================================" 
echo "Creating fake third-party chain ca2 -> ca1 -> ca ..." 
echo "====================================================" 

keytool -genkeypair -alias ca -dname cn=ca       \ 
    -validity 10000 -keyalg RSA -keysize 2048       \ 
    -ext BasicConstraints:critical=ca:true,pathlen:10000    \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass 

keytool -genkeypair -alias ca1 -dname cn=ca1       \ 
    -validity 10000 -keyalg RSA -keysize 2048       \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass 

keytool -genkeypair -alias ca2 -dname cn=ca2       \ 
    -validity 10000 -keyalg RSA -keysize 2048       \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass 


    keytool -certreq -alias ca1           \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -gencert -alias ca            \ 
    -ext KeyUsage:critical=keyCertSign         \ 
    -ext SubjectAlternativeName=dns:ca1         \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -importcert -alias ca1           \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass 

#echo "Debug exit" ; exit 0 

    keytool -certreq -alias ca2           \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -gencert -alias ca1           \ 
    -ext KeyUsage:critical=keyCertSign         \ 
    -ext SubjectAlternativeName=dns:ca2         \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -importcert -alias ca2          \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass 

keytool -list -v -storepass Storepass -keystore their-keystore.jks 


echo "====================================================================" 
echo "Fake third-party chain generated. Now generating my-keystore.jks ..." 
echo "====================================================================" 
read -p "Press a key to continue." 

# Import authority's certificate chain 

    keytool -exportcert -alias ca           \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -importcert -trustcacerts -noprompt -alias ca     \ 
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass 

    keytool -exportcert -alias ca1          \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -importcert -noprompt -alias ca1        \ 
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass 

    keytool -exportcert -alias ca2          \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -importcert -noprompt -alias ca2        \ 
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass 

# Create our own certificate, the authority signs it. 

keytool -genkeypair -alias e1 -dname cn=e1      \ 
    -validity 10000 -keyalg RSA -keysize 2048      \ 
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass 

    keytool -certreq -alias e1           \ 
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass  \ 
| keytool -gencert -alias ca2           \ 
    -ext SubjectAlternativeName=dns:localhost,ip:127.0.0.1    \ 
    -ext KeyUsage:critical=keyEncipherment,digitalSignature    \ 
    -ext ExtendedKeyUsage=serverAuth,clientAuth       \ 
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -importcert -alias e1           \ 
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass 

keytool -list -v -storepass Storepass -keystore my-keystore.jks 

echo "=================================================" 
echo "Keystore generated. Now generating truststore ..." 
echo "=================================================" 
read -p "Press a key to continue." 

    keytool -exportcert -alias ca          \ 
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -importcert -trustcacerts -noprompt -alias ca    \ 
    -keystore my-truststore.jks -keypass Keypass -storepass Storepass 

    keytool -exportcert -alias ca1          \ 
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -importcert -noprompt -alias ca1        \ 
    -keystore my-truststore.jks -keypass Keypass -storepass Storepass 

    keytool -exportcert -alias ca2          \ 
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -importcert -noprompt -alias ca2        \ 
    -keystore my-truststore.jks -keypass Keypass -storepass Storepass 

    keytool -exportcert -alias e1          \ 
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -importcert -noprompt -alias e1        \ 
    -keystore my-truststore.jks -keypass Keypass -storepass Storepass 

keytool -list -v -storepass Storepass -keystore my-truststore.jks 

rm their-keystore.jks 2> /dev/null 

create-single-autosigned.sh

#!/bin/bash 

rm my-keystore.jks 2> /dev/null 
rm my-truststore.jks 2> /dev/null 

keytool -genkeypair -alias e1 -dname cn=e1      \ 
    -validity 10000 -keyalg RSA -keysize 2048      \ 
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass 



keytool -list -v -storepass Storepass -keystore my-keystore.jks 

echo "=================================================" 
echo "Keystore generated. Now generating truststore ..." 
echo "=================================================" 
read -p "Press a key to continue." 

    keytool -exportcert -alias e1          \ 
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass \ 
| keytool -importcert -noprompt -alias e1        \ 
    -keystore my-truststore.jks -keypass Keypass -storepass Storepass 

keytool -list -v -storepass Storepass -keystore my-truststore.jks 

JettyHttpsForStackOverflow.java

import java.io.IOException; 
import java.io.InputStream; 
import java.net.URL; 
import java.security.KeyManagementException; 
import java.security.KeyStore; 
import java.security.KeyStoreException; 
import java.security.NoSuchAlgorithmException; 
import java.security.SecureRandom; 
import java.security.UnrecoverableKeyException; 
import java.security.cert.Certificate; 
import java.security.cert.CertificateException; 
import java.security.cert.X509Certificate; 
import javax.net.ssl.KeyManager; 
import javax.net.ssl.KeyManagerFactory; 
import javax.net.ssl.SSLContext; 
import javax.net.ssl.SSLEngine; 
import javax.net.ssl.TrustManager; 
import javax.net.ssl.TrustManagerFactory; 
import javax.net.ssl.X509TrustManager; 

import org.eclipse.jetty.client.ContentExchange; 
import org.eclipse.jetty.client.HttpClient; 
import org.eclipse.jetty.server.Server; 
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; 
import org.eclipse.jetty.util.ssl.SslContextFactory; 


/** 
* Code sample for Jetty {@link HttpClient} with HTTPS, in a completely standalone fashion. 
* Use create-chains.sh and create-empty.sh to generate completely standalone certificates. 
*/ 
public class JettyHttpsForStackOverflow { 

    public static void main(final String... arguments) throws Exception { 
    System.setProperty("javax.net.debug", "all") ; 

    try { 
     if(arguments.length == 0 || "server".equals(arguments[ 0 ])) { 
     runServer() ; 
     } 
     if(arguments.length == 0 || "client".equals(arguments[ 0 ])) { 
     runClient() ; 
     } 
    } catch(Exception e) { 
     e.printStackTrace() ; 
     System.exit(1) ; // Avoids keeping the port open. 
    } 

    } 

    private static void runServer() throws Exception { 
    final KeyStore keyStore = loadKeystore() ; 
    final SSLContext sslContext = createSslContext(
     keyStore, 
     KEYPASS, 
     newTrustManagers(keyStore, CERTIFICATE_ALIAS) 
    ) ; 

    final SslContextFactory sslContextFactory = new SslContextFactory() { 
     @Override 
     public SSLEngine newSslEngine() { 
     return sslContext.createSSLEngine() ; 
     } 
     @Override 
     public SSLEngine newSslEngine(final String host, final int port) { 
     return sslContext.createSSLEngine(host, port) ; 
     } 
    } ; 
    sslContextFactory.setAllowRenegotiate(true) ; 
    sslContextFactory.setNeedClientAuth(false) ; 
    sslContextFactory.setWantClientAuth(false) ; 
    sslContextFactory.setKeyStorePath(keyStore.toString()) ; // Better logging. 
    sslContextFactory.setKeyStore(keyStore) ; 
    sslContextFactory.setCertAlias(CERTIFICATE_ALIAS) ; 
    sslContextFactory.setKeyManagerPassword(KEYPASS) ; 

    final SslSelectChannelConnector sslConnector = 
     new SslSelectChannelConnector(sslContextFactory) ; 
    sslConnector.setPort(PORT) ; 
    sslConnector.open() ; 

    final Server jettyServer = new Server() ; 
    jettyServer.addConnector(sslConnector) ; 

    jettyServer.start() ; 
    } 

    public static void runClient() throws Exception { 
    final KeyStore keyStore = loadTruststore() ; 

    final HttpClient httpClient = new HttpClient() ; 
    httpClient.getSslContextFactory().setKeyStore(keyStore) ; // Better logging. 
    httpClient.getSslContextFactory().setKeyStorePassword("storepwd") ; 
    httpClient.getSslContextFactory().setKeyManagerPassword(KEYPASS) ; 
    httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL) ; 
    httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET); 


    // Don't need that because shipping our own certificate in the truststore. 
    // Anyways, it blows when set to true. 
// httpClient.getSslContextFactory().setValidateCerts(false) ; 

    httpClient.start() ; 

    final ContentExchange contentExchange = new ContentExchange() ; 
    contentExchange.setURI(new URL("https://localhost:" + PORT).toURI()) ; 
    contentExchange.setTimeout(36_000_000) ; // Leave time for debugging. 
    httpClient.send(contentExchange) ; 
    contentExchange.waitForDone() ; 
    assert(contentExchange.getStatus() == ContentExchange.STATUS_COMPLETED) ; 
    } 

    private static SSLContext createSslContext(
     final KeyStore keyStore, 
     final String keypass, 
     final TrustManager[] trustManagers 
) { 
    try { 
     final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509") ; 
     keyManagerFactory.init(keyStore, keypass == null ? null : keypass.toCharArray()) ; 
     final KeyManager[] keyManagers = keyManagerFactory.getKeyManagers() ; 
     final SecureRandom secureRandom = new SecureRandom() ; 

     final SSLContext sslContext = SSLContext.getInstance("TLS") ; 
     sslContext.init(
      keyManagers, 
      trustManagers, 
      secureRandom 
    ) ; 
     return sslContext ; 
    } catch(NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException 
     | KeyManagementException e 
    ) { 
     throw new RuntimeException(e) ; 
    } 
    } 



    private static TrustManager[] newTrustManagers(
     final KeyStore keyStore, 
     final String certificateAlias 
) { 
    try { 
     final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509") ; 
     trustManagerFactory.init(keyStore) ; 
     final TrustManager[] trustManagers ; 
     if(certificateAlias == null) { 
     trustManagers = trustManagerFactory.getTrustManagers() ; 
     } else { 
     final Certificate certificate = keyStore.getCertificate(certificateAlias) ; 
     final X509Certificate[] x509Certificates ; 
     if(certificate == null) { 
      x509Certificates = new X509Certificate[ 0 ] ; 
     } else { 
      x509Certificates = new X509Certificate[] { (X509Certificate) certificate } ; 
     } 
     trustManagers = new TrustManager[] { newX509TrustManager(x509Certificates) } ; 

     } 
     return trustManagers ; 
    } catch(KeyStoreException | NoSuchAlgorithmException e) { 
     throw new RuntimeException(e); 
    } 

    } 

    private static final TrustManager newX509TrustManager(final X509Certificate[] certificates) { 
    return new X509TrustManager() { 

     public X509Certificate[] getAcceptedIssuers() { 
     return certificates ; 
     } 

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

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


    public static KeyStore loadKeystore() 
     throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException 
    { 
    return loadKeystore(KEYSTORE_RESOURCE_URL) ; 
    } 

    public static KeyStore loadTruststore() 
     throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException 
    { 
    return loadKeystore(TRUSTSTORE_RESOURCE_URL) ; 
    } 

    public static KeyStore loadKeystore(final URL keystoreResourceUrl) 
     throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException 
    { 
    try(final InputStream inputStream = keystoreResourceUrl.openStream()) { 
     final KeyStore keyStore = KeyStore.getInstance("JKS") ; 
     // We don't need the storepass for just reading one password-protected certificate 
     // of our own, or a trusted entry. 
     keyStore.load(inputStream, null) ; 
     return keyStore ; 
    } 
    } 


    private static final int PORT = 8443 ; 

    private static final String CERTIFICATE_ALIAS = "e1"; 

    private static final String KEYPASS = "Keypass"; 

    private static final URL KEYSTORE_RESOURCE_URL 
     = JettyHttpsForStackOverflow.class.getResource("my-keystore.jks") ; 

    private static final URL TRUSTSTORE_RESOURCE_URL 
     = JettyHttpsForStackOverflow.class.getResource("my-truststore.jks") ; 


} 

回答

12

事實證明,在SslConnection碼頭,7.6.10.v20130312日誌錯誤,同時加密發生,因爲它應該。

長篇故事:創建時,SslConnectionSSLEngine中提取初始SSLSession對象並繼續記錄。初始SSLSession有一個SSL_NULL_WITH_NULL_NULL密碼,這很正常,因爲SSL握手尚未發生。激活-Djavax.net.debug=all表明握手確實發生,並且交互式調試顯示SSLEngine使用真實密碼升級到SSLSession。問題僅僅是Jetty的SslConnection,它仍然以初始SSLSession對象記錄。 (它也使用來自初始SSLSession的值來分配緩衝區,但這是另一個問題。)

修補SslConnection用於以_engine.getSession()進行日誌記錄,得出預期結果。

結語:碼頭9完全重寫它的SslConnection

+3

良好的調查工作。愚蠢的錯誤。 – EJP

+0

我用Jetty 9.3.6獲得了同樣的結果..... – user2818782

1

你會看到這下至少有以下情況:

  1. 您已經修改了enabledCipherSuites包括所有支持的加密套件。 (不要!)

  2. SSL握手尚未完成。

+2

在上面的例子中,我沒有觸及可用的密碼套件,但是我是爲生產服務器做的。這僅用於限制列表,因爲SSL LABS會將其標記爲不安全。 我讓示例安靜地運行。它產生一個帶有加密消息的404,所以我認爲SSL握手確實發生了。 奇怪的是,我得到一個非常基本的案例,但用Google搜索,我看到沒有人遇到同樣的問題。 –