diff --git a/akka-docs-dev/rst/java/http/client-side/https-support.rst b/akka-docs-dev/rst/java/http/client-side/https-support.rst index da4c6baa78..af38437fbf 100644 --- a/akka-docs-dev/rst/java/http/client-side/https-support.rst +++ b/akka-docs-dev/rst/java/http/client-side/https-support.rst @@ -37,5 +37,21 @@ Afterwards you simply use ``outgoingConnectionTls``, ``newHostConnectionPoolTls` ``superPool`` or ``singleRequest`` without a specific ``httpsContext`` argument, which causes encrypted connections to rely on the configured default client-side ``HttpsContext``. +If no custom ``HttpsContext`` is defined the default context uses Java's default TLS settings. Customizing the +``HttpsContext`` can make the Https client less secure. Understand what you are doing! + +Hostname verification on Java 6 +------------------------------- + +Hostname verification proves that the Akka HTTP client is actually communicating with the server it intended to +communicate with. Without this check a man-in-the-middle attack is possible. In the attack scenario, an alternative +certificate would be presented which was issued for another host name. Checking the host name in the certificate +against the host name the connection was opened against is therefore vital. + +The default ``HttpsContext`` enables hostname verification. Akka HTTP relies on a Java 7 feature to implement +the verification. To prevent an unintended security downgrade, accessing the default ``HttpsContext`` on Java 6 +will fail with an exception. Specifying a custom ``HttpsContext`` or customizing the default one is also possible +on Java 6. + .. _akka.http.javadsl.Http: @github@/akka-http-core/src/main/scala/akka/http/javadsl/Http.scala diff --git a/akka-docs-dev/rst/scala/http/client-side/https-support.rst b/akka-docs-dev/rst/scala/http/client-side/https-support.rst index f2627640b8..31bd9eef9c 100644 --- a/akka-docs-dev/rst/scala/http/client-side/https-support.rst +++ b/akka-docs-dev/rst/scala/http/client-side/https-support.rst @@ -36,5 +36,21 @@ Afterwards you simply use ``outgoingConnectionTls``, ``newHostConnectionPoolTls` ``superPool`` or ``singleRequest`` without a specific ``httpsContext`` argument, which causes encrypted connections to rely on the configured default client-side ``HttpsContext``. +If no custom ``HttpsContext`` is defined the default context uses Java's default TLS settings. Customizing the +``HttpsContext`` can make the Https client less secure. Understand what you are doing! + +Hostname verification on Java 6 +------------------------------- + +Hostname verification proves that the Akka HTTP client is actually communicating with the server it intended to +communicate with. Without this check a man-in-the-middle attack is possible. In the attack scenario, an alternative +certificate would be presented which was issued for another host name. Checking the host name in the certificate +against the host name the connection was opened against is therefore vital. + +The default ``HttpsContext`` enables hostname verification. Akka HTTP relies on a Java 7 feature to implement +the verification. To prevent an unintended security downgrade, accessing the default ``HttpsContext`` on Java 6 +will fail with an exception. Specifying a custom ``HttpsContext`` or customizing the default one is also possible +on Java 6. + .. _akka.http.scaladsl.Http: @github@/akka-http-core/src/main/scala/akka/http/scaladsl/Http.scala diff --git a/akka-http-core/src/main/resources/keys/README b/akka-http-core/src/main/resources/keys/README new file mode 100644 index 0000000000..65dfa96457 --- /dev/null +++ b/akka-http-core/src/main/resources/keys/README @@ -0,0 +1,39 @@ +Keys for running Tls tests using the `ExampleHttpContexts` +---------------------------------------------------------- + +Instructions adapted from + + * http://datacenteroverlords.com/2012/03/01/creating-your-own-ssl-certificate-authority/ + * http://security.stackexchange.com/questions/9600/how-to-use-openssl-generated-keys-in-java + + +# Create a rootCA key: + +openssl genrsa -out rootCA.key 2048 + +# Self-sign CA: + +openssl req -x509 -new -nodes -key rootCA.key -days 3560 -out rootCA.crt + +# Create server key: + +openssl genrsa -out device.key 2048 + +# Create server CSR: + +openssl req -new -key server.key -out server.csr + +# Create server certificate: + +openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 3560 + +# Create certificate chain: + +cat server.crt rootCA.crt > chain.pem + +# Convert certificate and key to pkcs12 (you need to provide a password manually, `ExampleHttpContexts` +# expects the password to be "abcdef"): + +openssl pkcs12 -export -name servercrt -in chain.pem -inkey server.key -out server.p12 + + diff --git a/akka-http-core/src/main/resources/keys/chain.pem b/akka-http-core/src/main/resources/keys/chain.pem new file mode 100644 index 0000000000..766871eba2 --- /dev/null +++ b/akka-http-core/src/main/resources/keys/chain.pem @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIDITCCAgkCCQCo8H6OcPrArzANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMB4XDTE1MDcyMzA5NTEyMloXDTI1MDQyMTA5NTEyMlowYDELMAkG +A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDEZMBcGA1UEAwwQYWtrYS5leGFtcGxlLm9yZzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANMy/wgrSYVhVtu9OGbo2rSKauiz +5V56X4uCqtCHF9UeHtnVtFLCBMa+pimOS+UyUAT4mbBsxW22BhoNUBZ15KPxltyD +yEsqNCKwWGxL3r8AXQtze2MEpTl22Lvp/iCTXO1vbML/+9r3uqUjw/AAP9HwF9Wd +j/yOrs6q8WE4sfc48iOj6N60/h2pRfn2WNJmo9W9FLC53NznixfsG5oN6Jmb9RM+ +fMHYXLfL/Vt6NrgVX1uqHt9HvuoxfNKhhXE5VU8bNfFfzPYvIt4aZXGxO15vEqsq +OaZ7YJyKr1oFfJC8LmE5xPa3GHToCqmkdMXQK38mpslMQWlQLYnmkS5Qzv8CAwEA +ATANBgkqhkiG9w0BAQsFAAOCAQEAEPDd1gAF9q2LtoZqTdcwmeBjdbT7n0WDRSuI +BzQ/qKjvymwpFKQ0pZSPUyaw2qfRRiTQ/QTbqYep2mhvl5n+gW3ifTp83zgTGKH/ +3sDlX0HPSCBYCDy2gP/AOIgV/57ADMpEkTlz8yyLMH+pLDAoNFIPwy7blAkq+ULQ +y6TfEBmZXoemSaIh5tRnexCD+pTvL4MRrGlBEoxdejDnIAt4n6BxmF0b4hKg8uta +UvivA85lBKzWUoR/Vam5/SC8jtcyLt9RThRcNSj6zP6s5d+o+8PLznrSEadAtfD9 +0q+t4TYF81tClEEgGruVPNL4WIpDniOfw9AJgQNVJGfy5TKY1Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJANYwx08wP3STMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTUwNzIzMDk0ODI2WhcNMjUwNDIxMDk0ODI2WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEArk0K/Rn7uND2YGFBks5Sok1WvNdHQccPESEw2hNVF32ExAhbBXCrFaIl +Io0q4eYSbypeauEjDXB/NJXurEefL8ONXK62erJDKKQ0aTTYqsVifoNYA9ORWoGE +XhtAfOx4xvzr6vF1e3kz0PB/A4ftn0vvVygYnf/2E2bQZgaw8dXP5lIGasEzzigB +LX/qTEW/vBOL98Rxp6JvjwvYMbPSZGwNwSz+tI5W2psdE1Mga2Qnsv3j+STWlD9v ++JlgdN8r3PyR1sl3jC7gCj3AaOhv4RbAbqjwnZ9nrckx16PFiMtJiVRea7CQXN7g +191EVujQnlg1LOhiSMKwVsuoXr08ywIDAQABo1AwTjAdBgNVHQ4EFgQU2THI/ilU +M0xds3vZlV4CvhAZ1d8wHwYDVR0jBBgwFoAU2THI/ilUM0xds3vZlV4CvhAZ1d8w +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAK9LO0HyIi0xbTISsc+A5 +LQyZowgRAGqsNNmni7NKDXauPLZrCfDVhvo/FPP1XSFShXo7ARvro9lul4AJlkNN +VgX0gbWtkiAx0uLqlbMsC6imj2L9boRse7mzI/Ymem5SNTn9GUnlMiZ74rca9UT4 +Dk9YytrT4FSpomiL6z8Xj604W3RuLSdEfpfcn3Jh2tFSZ9hyLwB7ATUTA/yuj1SU +G1gmoPMvlnPzNj2lIqyIdQxGdxt+L3mFO20CxBkeieWqQuNptpjwptliFjkZJJZP +wQlx9qLLvs/eFC2AUWj+hbsl37PuARR9hoeqbKRcUjwGtaXOqikrvX1qzPc2+ij9 +/w== +-----END CERTIFICATE----- diff --git a/akka-http-core/src/main/resources/keys/rootCA.crt b/akka-http-core/src/main/resources/keys/rootCA.crt new file mode 100644 index 0000000000..6ba9fb756c --- /dev/null +++ b/akka-http-core/src/main/resources/keys/rootCA.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJANYwx08wP3STMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTUwNzIzMDk0ODI2WhcNMjUwNDIxMDk0ODI2WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEArk0K/Rn7uND2YGFBks5Sok1WvNdHQccPESEw2hNVF32ExAhbBXCrFaIl +Io0q4eYSbypeauEjDXB/NJXurEefL8ONXK62erJDKKQ0aTTYqsVifoNYA9ORWoGE +XhtAfOx4xvzr6vF1e3kz0PB/A4ftn0vvVygYnf/2E2bQZgaw8dXP5lIGasEzzigB +LX/qTEW/vBOL98Rxp6JvjwvYMbPSZGwNwSz+tI5W2psdE1Mga2Qnsv3j+STWlD9v ++JlgdN8r3PyR1sl3jC7gCj3AaOhv4RbAbqjwnZ9nrckx16PFiMtJiVRea7CQXN7g +191EVujQnlg1LOhiSMKwVsuoXr08ywIDAQABo1AwTjAdBgNVHQ4EFgQU2THI/ilU +M0xds3vZlV4CvhAZ1d8wHwYDVR0jBBgwFoAU2THI/ilUM0xds3vZlV4CvhAZ1d8w +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAK9LO0HyIi0xbTISsc+A5 +LQyZowgRAGqsNNmni7NKDXauPLZrCfDVhvo/FPP1XSFShXo7ARvro9lul4AJlkNN +VgX0gbWtkiAx0uLqlbMsC6imj2L9boRse7mzI/Ymem5SNTn9GUnlMiZ74rca9UT4 +Dk9YytrT4FSpomiL6z8Xj604W3RuLSdEfpfcn3Jh2tFSZ9hyLwB7ATUTA/yuj1SU +G1gmoPMvlnPzNj2lIqyIdQxGdxt+L3mFO20CxBkeieWqQuNptpjwptliFjkZJJZP +wQlx9qLLvs/eFC2AUWj+hbsl37PuARR9hoeqbKRcUjwGtaXOqikrvX1qzPc2+ij9 +/w== +-----END CERTIFICATE----- diff --git a/akka-http-core/src/main/resources/keys/rootCA.key b/akka-http-core/src/main/resources/keys/rootCA.key new file mode 100644 index 0000000000..119caf0dd1 --- /dev/null +++ b/akka-http-core/src/main/resources/keys/rootCA.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEArk0K/Rn7uND2YGFBks5Sok1WvNdHQccPESEw2hNVF32ExAhb +BXCrFaIlIo0q4eYSbypeauEjDXB/NJXurEefL8ONXK62erJDKKQ0aTTYqsVifoNY +A9ORWoGEXhtAfOx4xvzr6vF1e3kz0PB/A4ftn0vvVygYnf/2E2bQZgaw8dXP5lIG +asEzzigBLX/qTEW/vBOL98Rxp6JvjwvYMbPSZGwNwSz+tI5W2psdE1Mga2Qnsv3j ++STWlD9v+JlgdN8r3PyR1sl3jC7gCj3AaOhv4RbAbqjwnZ9nrckx16PFiMtJiVRe +a7CQXN7g191EVujQnlg1LOhiSMKwVsuoXr08ywIDAQABAoIBAQCSXAEpLMNRmq33 +mlMMqhF7VcPKyF5+Xl9Je/xgcjFWi0CLt5Ruyf/vJ3tVOwLSM3YxQHuN9cSQSXGX +P3rt0SpbWjJ+q/pwpvV7z/5uhUCWjS46m6GxfNsmC3GR8AJDo/F67fBQFTcYWlrn +TLrqxR4EUCgGoJWjPsZr3j6KHX5BYmzyTuJFBzxxipK42hnJQ7tMB8l6/5r4nRka +d6SGFpJDkyhO+Wl0sBXjxHu1E4g8asI061jEOhcROV1Dk4hp1CYhd8TBj//6FSBC +ttsIe2gxT0fk8bnNC78FuO0CUTCj4hFOWP7apr/NhLlxypu+4hj17NMhlptRvGxz +6pPlMVDJAoGBANPVTS5nkJpMyczA5vaHsyTF/nwunogwHVeVYsQQ3Bed28Ldp7gr +Dr4hgYFvGkEmlLvWOleHvGISuD3lHLd112LcPyLFMRrs8wX9vWTueZGYj5KDLS3C +i3GaYMqqYbuiFY1QYprF36zRQkLMKUiOomE2+baCasbhluAqqx32KEKvAoGBANKk +cG0X0svJ/TTQIE5nfDtKePDUA7wEPYGrQOO4vKKZUlytVhf+gEcYr575bPjkTl1h +5jrrhr4OWpFDmRyBpi7wB95Fe93Df+0o4KmiNtsioZsi/MA5Tga2rAZPBBuZ9+5l +alYl0fTo5PR3fOXJJoJ+w7+QI4N/9TGuBJoiEl6lAoGBAM8XapsBOIcApxB7TdCa +HXLH9eDlmqq9jxH+w022xdR4yU2acMtFnOYXz4oAWgRzeVihOOw1kN+4OVKZWBer +JuRJOZf+e+E84OFsjOnNkh/arBGqGFLyLGzlZdb79wv+i19ZxOxWojNLaKHxAjMi +7nBn1Hyux0CjbmK8lAl4iyeVAoGAT6r4BprTFFaiGN56yYykVPx2v4dAnlTwOmHe +GgLd/ZWFrB23CT4toDY6/iKST5Rx+ymy3SgFf06IfJaXi0uR4gDQyQV4sshlUvp5 +9k6u9rSjcLyL4dwKoclnSL+L6zCRsC3VSR3myf1n0vp6V6J7mTF+sa4/cFXuE8sg +XHd0gS0CgYAXNDcF+zYoSmbfdG7uM7qOPQwNRbr0pHvAg0NmtM9JOj8gZPoaeAy3 +3jEk9AMQrK0MNsRynAoMkhy+7WOU6TNLvyxXAKGZffOmABzSB9LEFgHkVPutl5/i +wL2pE1SoG2QwSqFYGv+rHgIpREJzDTNwbmSbl/Za50JrIZ3OFfTMDQ== +-----END RSA PRIVATE KEY----- diff --git a/akka-http-core/src/main/resources/keys/server.crt b/akka-http-core/src/main/resources/keys/server.crt new file mode 100644 index 0000000000..4395b589d5 --- /dev/null +++ b/akka-http-core/src/main/resources/keys/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDITCCAgkCCQCo8H6OcPrArzANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMB4XDTE1MDcyMzA5NTEyMloXDTI1MDQyMTA5NTEyMlowYDELMAkG +A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDEZMBcGA1UEAwwQYWtrYS5leGFtcGxlLm9yZzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANMy/wgrSYVhVtu9OGbo2rSKauiz +5V56X4uCqtCHF9UeHtnVtFLCBMa+pimOS+UyUAT4mbBsxW22BhoNUBZ15KPxltyD +yEsqNCKwWGxL3r8AXQtze2MEpTl22Lvp/iCTXO1vbML/+9r3uqUjw/AAP9HwF9Wd +j/yOrs6q8WE4sfc48iOj6N60/h2pRfn2WNJmo9W9FLC53NznixfsG5oN6Jmb9RM+ +fMHYXLfL/Vt6NrgVX1uqHt9HvuoxfNKhhXE5VU8bNfFfzPYvIt4aZXGxO15vEqsq +OaZ7YJyKr1oFfJC8LmE5xPa3GHToCqmkdMXQK38mpslMQWlQLYnmkS5Qzv8CAwEA +ATANBgkqhkiG9w0BAQsFAAOCAQEAEPDd1gAF9q2LtoZqTdcwmeBjdbT7n0WDRSuI +BzQ/qKjvymwpFKQ0pZSPUyaw2qfRRiTQ/QTbqYep2mhvl5n+gW3ifTp83zgTGKH/ +3sDlX0HPSCBYCDy2gP/AOIgV/57ADMpEkTlz8yyLMH+pLDAoNFIPwy7blAkq+ULQ +y6TfEBmZXoemSaIh5tRnexCD+pTvL4MRrGlBEoxdejDnIAt4n6BxmF0b4hKg8uta +UvivA85lBKzWUoR/Vam5/SC8jtcyLt9RThRcNSj6zP6s5d+o+8PLznrSEadAtfD9 +0q+t4TYF81tClEEgGruVPNL4WIpDniOfw9AJgQNVJGfy5TKY1Q== +-----END CERTIFICATE----- diff --git a/akka-http-core/src/main/resources/keys/server.key b/akka-http-core/src/main/resources/keys/server.key new file mode 100644 index 0000000000..117cb40355 --- /dev/null +++ b/akka-http-core/src/main/resources/keys/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA0zL/CCtJhWFW2704ZujatIpq6LPlXnpfi4Kq0IcX1R4e2dW0 +UsIExr6mKY5L5TJQBPiZsGzFbbYGGg1QFnXko/GW3IPISyo0IrBYbEvevwBdC3N7 +YwSlOXbYu+n+IJNc7W9swv/72ve6pSPD8AA/0fAX1Z2P/I6uzqrxYTix9zjyI6Po +3rT+HalF+fZY0maj1b0UsLnc3OeLF+wbmg3omZv1Ez58wdhct8v9W3o2uBVfW6oe +30e+6jF80qGFcTlVTxs18V/M9i8i3hplcbE7Xm8Sqyo5pntgnIqvWgV8kLwuYTnE +9rcYdOgKqaR0xdArfyamyUxBaVAtieaRLlDO/wIDAQABAoIBADfqTXkVNM7aWYut +yiv8xEJ+TxWy4ywjS/58psq0qYukANj9alNqyKbxvL5NzSwuKN9YDiCWe6KzSWRG +WAjKR7Fb+ewB+9pinxD8DT0GzT9WUkwA1A8AINpY68K8jaqEOVsnX+00prJvWfv0 +vyBggIUNgtHseD2ObRuMSIHL59oivxoBKmeRqFl26PCq+m6Dp1SsMwL8NE02rfUu +uVW0zSz0/A5ZK90l8St3N78Puw/qicvfrI4PrGi4kLKW9UKJKP5FzfPF7Kf9itVA +1VB3gd8Gs98vRnzHwZlwgjyAQkePzS/iEQid9uRA/Xys5ozcT1arYM00t3I7ZEUg +GJTKHBECgYEA+K/M6smzPrTAi0BEuI1NCb3zfxkjbBhC0cco9U4VIuhYVU+7Ukre +zi5yI+BQR8MPbftSeeosXV6eQaq04pKCrHWF+ql+3Io9Hojghd/EnNCOtGxjTGmI +Px8G7byeIr4+QyP+JSEdsVBfIEEQ9BJ8Up84RibsMfWcKe6ntzAMEmkCgYEA2Wj6 +DqPisPp4WwGi8bSvSRZsF3h3xu0saml+ug28j+b3kOa99Uz49kCi99sacJArYOWv +Dn+DPl2K2/lwYO0bfyXwWaLp8pd/MAmwhKZ2+qvoUnkZJFRU3yrUoPp7CURZSbcG +aD7IKotFH7wutqj8pZ50y8VGqKVACenhRSAH2ScCgYAuX7IJslUfg1tIXFK0S30r +LOXENK7bUGbdcZMcs1PTr5oRRo362YVU02prcD/oMeKlsrD9lQJy4tsGCcwzV/jQ +KhYy2PqUK58cG5AqxsCGMYn68R9PN3q1spZ7LKocdndr08FnsRY1Y3Rpslhz+yJ9 +0b0Pr+BprJBTbXKPAYGuyQKBgAJFu59djSgGZi2lVburBM4Bwv13z+CvZ/Bwy9dL +/3WNl3bXQpMGy+9e+5UVoDAfAaUQoYTIRmnndmUYNVl+APSSQ/Hb5xAXD0hEQakR +SFsUYuhBxcaAbyap/vDzzUdqhHhlxlZemZ8AN6e+Qsq793APuO7MUBHBMGsqG6Wq +UQqvAoGAINEINXhFXp2qVRDBUY57rRtpjQHajeNTMChgWTg30owfVNBY4evjRj8f +9XDuUkTumYcDcnOKmX3L6n9rg4noHlfNvxmn9pmG9vP0mG0MEOOxSxXFHVIuBw10 +wdTb0WE/i3FhyufdaRHLGhPAMQjaCeFSV3sMxMHuNePvCxnKD3E= +-----END RSA PRIVATE KEY----- diff --git a/akka-http-core/src/main/resources/keys/server.p12 b/akka-http-core/src/main/resources/keys/server.p12 new file mode 100644 index 0000000000..d72cc9f3d4 Binary files /dev/null and b/akka-http-core/src/main/resources/keys/server.p12 differ diff --git a/akka-http-core/src/main/scala/akka/http/impl/util/Java6Compat.scala b/akka-http-core/src/main/scala/akka/http/impl/util/Java6Compat.scala new file mode 100644 index 0000000000..a1329c5e49 --- /dev/null +++ b/akka-http-core/src/main/scala/akka/http/impl/util/Java6Compat.scala @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +package akka.http.impl.util + +import java.lang.reflect.{ InvocationTargetException, Method } +import javax.net.ssl.SSLParameters + +import scala.util.control.NonFatal + +/** + * INTERNAL API + * + * Enables accessing SslParameters even if compiled against Java 6. + */ +private[http] object Java6Compat { + /** + * Returns true if setting the algorithm was successful. + */ + def setEndpointIdentificationAlgorithm(parameters: SSLParameters, algorithm: String): Boolean = + setEndpointIdentificationAlgorithmFunction(parameters, algorithm) + + private[this] val setEndpointIdentificationAlgorithmFunction: (SSLParameters, String) ⇒ Boolean = { + def unsupported: (SSLParameters, String) ⇒ Boolean = (_, _) ⇒ false + + def callReflectively(m: Method) = + (params: SSLParameters, algorithm: String) ⇒ + try { + m.invoke(params, algorithm) + true + } catch { + case ite: InvocationTargetException ⇒ throw ite.getTargetException + } + + try { + val m = classOf[SSLParameters].getMethod("setEndpointIdentificationAlgorithm", classOf[java.lang.String]) + + val candidate = + if (m.getReturnType == Void.TYPE && m.getParameterTypes.toSeq == Seq(classOf[java.lang.String])) { + if (!m.isAccessible) m.setAccessible(true) + callReflectively(m) + } else unsupported + + // probe so that we can be sure a reflective problem only turns up once + // here during construction + candidate(new SSLParameters(), "https") + candidate + } catch { + case NonFatal(_) ⇒ unsupported + } + } +} diff --git a/akka-http-core/src/main/scala/akka/http/impl/util/package.scala b/akka-http-core/src/main/scala/akka/http/impl/util/package.scala index 574f8b3015..001e9886d8 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/util/package.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/util/package.scala @@ -146,4 +146,6 @@ package util { } } } + + private[http] class ReadTheDocumentationException(message: String) extends RuntimeException(message) } diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/Http.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/Http.scala index 2c310f9930..bf79711ee8 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/Http.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/Http.scala @@ -14,7 +14,7 @@ import akka.event.LoggingAdapter import akka.http._ import akka.http.impl.engine.client._ import akka.http.impl.engine.server._ -import akka.http.impl.util.StreamUtils +import akka.http.impl.util.{ ReadTheDocumentationException, Java6Compat, StreamUtils } import akka.http.scaladsl.model._ import akka.http.scaladsl.model.headers.Host import akka.http.scaladsl.util.FastFuture @@ -185,7 +185,7 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E log: LoggingAdapter): Flow[HttpRequest, HttpResponse, Future[OutgoingConnection]] = { val hostHeader = if (port == (if (httpsContext.isEmpty) 80 else 443)) Host(host) else Host(host, port) val layer = clientLayer(hostHeader, settings, log) - val tlsStage = sslTlsStage(httpsContext, Client) + val tlsStage = sslTlsStage(httpsContext, Client, Some(host -> port)) val transportFlow = Tcp().outgoingConnection(new InetSocketAddress(host, port), localAddress, settings.socketOptions, halfClose = true, settings.connectingTimeout, settings.idleTimeout) @@ -390,13 +390,25 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E synchronized { _defaultClientHttpsContext match { case null ⇒ - val ctx = HttpsContext(SSLContext.getDefault) + val ctx = createDefaultClientHttpsContext _defaultClientHttpsContext = ctx ctx case ctx ⇒ ctx } } + private def createDefaultClientHttpsContext: HttpsContext = { + val defaultCtx = SSLContext.getDefault + + val params = new SSLParameters + if (!Java6Compat.setEndpointIdentificationAlgorithm(params, "https")) + throw new ReadTheDocumentationException( + "Cannot enable HTTPS hostname verification on Java 6. See the " + + "\"Client-Side HTTPS Support\" section in the documentation") + + HttpsContext(defaultCtx, sslParameters = Some(params)) + } + /** * Sets the default client-side [[HttpsContext]]. */ @@ -469,9 +481,9 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E private def effectiveHttpsContext(ctx: Option[HttpsContext]): Option[HttpsContext] = ctx orElse Some(defaultClientHttpsContext) - private def sslTlsStage(httpsContext: Option[HttpsContext], role: Role) = + private[http] def sslTlsStage(httpsContext: Option[HttpsContext], role: Role, hostInfo: Option[(String, Int)] = None) = httpsContext match { - case Some(hctx) ⇒ SslTls(hctx.sslContext, hctx.firstSession, role) + case Some(hctx) ⇒ SslTls(hctx.sslContext, hctx.firstSession, role, hostInfo = hostInfo) case None ⇒ SslTlsPlacebo.forScala } } diff --git a/akka-http-core/src/test/scala/akka/http/impl/engine/client/TlsEndpointVerificationSpec.scala b/akka-http-core/src/test/scala/akka/http/impl/engine/client/TlsEndpointVerificationSpec.scala new file mode 100644 index 0000000000..63fdc36703 --- /dev/null +++ b/akka-http-core/src/test/scala/akka/http/impl/engine/client/TlsEndpointVerificationSpec.scala @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +package akka.http.impl.engine.client + +import org.scalatest.concurrent.PatienceConfiguration.Timeout +import org.scalatest.concurrent.ScalaFutures + +import akka.stream.ActorMaterializer +import akka.stream.io._ +import akka.stream.scaladsl._ +import akka.stream.testkit.AkkaSpec + +import akka.http.impl.util._ + +import akka.http.scaladsl.{ HttpsContext, Http } +import akka.http.scaladsl.model.{ StatusCodes, HttpResponse, HttpRequest } +import akka.http.scaladsl.model.headers.Host +import org.scalatest.time.{ Span, Seconds } + +import scala.concurrent.Future + +class TlsEndpointVerificationSpec extends AkkaSpec(""" + #akka.loggers = [] + akka.loglevel = DEBUG + akka.io.tcp.trace-logging = off + akka.io.tcp.windows-connection-abort-workaround-enabled=auto""") with ScalaFutures { + implicit val materializer = ActorMaterializer() + val timeout = Timeout(Span(3, Seconds)) + + "The client implementation" should { + "not accept certificates signed by unknown CA" in { + val pipe = pipeline(Http().defaultClientHttpsContext, hostname = "akka.example.org") // default context doesn't include custom CA + + whenReady(pipe(HttpRequest(uri = "https://akka.example.org/")).failed, timeout) { e ⇒ + e shouldBe an[Exception] + } + } + "accept certificates signed by known CA" in { + val pipe = pipeline(ExampleHttpContexts.exampleClientContext, hostname = "akka.example.org") // example context does include custom CA + + whenReady(pipe(HttpRequest(uri = "https://akka.example.org/")), timeout) { response ⇒ + response.status shouldEqual StatusCodes.OK + } + } + "not accept certificates for foreign hosts" in { + val pipe = pipeline(ExampleHttpContexts.exampleClientContext, hostname = "hijack.de") // example context does include custom CA + + whenReady(pipe(HttpRequest(uri = "https://hijack.de/")).failed, timeout) { e ⇒ + e shouldBe an[Exception] + } + } + } + + def pipeline(clientContext: HttpsContext, hostname: String): HttpRequest ⇒ Future[HttpResponse] = req ⇒ + Source.single(req).via(pipelineFlow(clientContext, hostname)).runWith(Sink.head) + + def pipelineFlow(clientContext: HttpsContext, hostname: String): Flow[HttpRequest, HttpResponse, Unit] = { + val handler: HttpRequest ⇒ HttpResponse = _ ⇒ HttpResponse() + + val serverSideTls = Http().sslTlsStage(Some(ExampleHttpContexts.exampleServerContext), Server) + val clientSideTls = Http().sslTlsStage(Some(clientContext), Client, Some(hostname -> 8080)) + + val server = + Http().serverLayer() + .atop(serverSideTls) + .reversed + .join(Flow[HttpRequest].map(handler)) + + val client = + Http().clientLayer(Host(hostname, 8080)) + .atop(clientSideTls) + + client.join(server) + } +} diff --git a/akka-http-core/src/test/scala/akka/http/impl/util/ExampleHttpContexts.scala b/akka-http-core/src/test/scala/akka/http/impl/util/ExampleHttpContexts.scala new file mode 100644 index 0000000000..b9531de10b --- /dev/null +++ b/akka-http-core/src/test/scala/akka/http/impl/util/ExampleHttpContexts.scala @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +package akka.http.impl.util + +import java.io.InputStream +import java.security.{ SecureRandom, KeyStore } +import java.security.cert.{ CertificateFactory, Certificate } +import javax.net.ssl.{ SSLParameters, SSLContext, TrustManagerFactory, KeyManagerFactory } + +import akka.http.scaladsl.HttpsContext + +/** + * These are HTTPS example configurations that take key material from the resources/key folder. + */ +object ExampleHttpContexts { + val exampleServerContext = { + val ks = KeyStore.getInstance("PKCS12") + ks.load(resourceStream("keys/server.p12"), "abcdef".toCharArray) + + val keyManagerFactory = KeyManagerFactory.getInstance("SunX509") + keyManagerFactory.init(ks, "abcdef".toCharArray) + + val context = SSLContext.getInstance("TLS") + context.init(keyManagerFactory.getKeyManagers, null, new SecureRandom) + + HttpsContext(context) + } + val exampleClientContext = { + val certStore = KeyStore.getInstance(KeyStore.getDefaultType) + certStore.load(null, null) + certStore.setCertificateEntry("ca", loadX509Certificate("keys/rootCA.crt")) + + val certManagerFactory = TrustManagerFactory.getInstance("SunX509") + certManagerFactory.init(certStore) + + val context = SSLContext.getInstance("TLS") + context.init(null, certManagerFactory.getTrustManagers, new SecureRandom) + + val params = new SSLParameters() + Java6Compat.setEndpointIdentificationAlgorithm(params, "https") + HttpsContext(context, sslParameters = Some(params)) + } + + def resourceStream(resourceName: String): InputStream = { + val is = getClass.getClassLoader.getResourceAsStream(resourceName) + require(is ne null, s"Resource $resourceName not found") + is + } + + def loadX509Certificate(resourceName: String): Certificate = + CertificateFactory.getInstance("X.509").generateCertificate(resourceStream(resourceName)) +}