Merge pull request #18062 from spray/w/15687-enable-endpoint-verification

#18059 #15687 Enable https hostname verification
This commit is contained in:
Konrad Malawski 2015-08-03 11:35:37 +02:00
commit d89b9e19b1
17 changed files with 440 additions and 20 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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-----

View file

@ -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-----

View file

@ -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-----

View file

@ -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-----

View file

@ -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-----

Binary file not shown.

View file

@ -0,0 +1,53 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
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
}
}
}

View file

@ -146,4 +146,6 @@ package util {
}
}
}
private[http] class ReadTheDocumentationException(message: String) extends RuntimeException(message)
}

View file

@ -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
@ -186,7 +186,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)
@ -391,13 +391,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]].
*/
@ -470,9 +482,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
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
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)
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
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))
}

View file

@ -103,7 +103,7 @@ private[akka] case class ActorMaterializerImpl(
case tls: TlsModule // TODO solve this so TlsModule doesn't need special treatment here
val es = effectiveSettings(effectiveAttributes)
val props =
SslTlsCipherActor.props(es, tls.sslContext, tls.firstSession, tracing = false, tls.role, tls.closing)
SslTlsCipherActor.props(es, tls.sslContext, tls.firstSession, tracing = false, tls.role, tls.closing, tls.hostInfo)
val impl = actorOf(props, stageName(effectiveAttributes), es.dispatcher)
def factory(id: Int) = new ActorPublisher[Any](impl) {
override val wakeUpMsg = FanOut.SubstreamSubscribePending(id)

View file

@ -34,8 +34,9 @@ private[akka] object SslTlsCipherActor {
firstSession: NegotiateNewSession,
tracing: Boolean,
role: Role,
closing: Closing): Props =
Props(new SslTlsCipherActor(settings, sslContext, firstSession, tracing, role, closing)).withDeploy(Deploy.local)
closing: Closing,
hostInfo: Option[(String, Int)]): Props =
Props(new SslTlsCipherActor(settings, sslContext, firstSession, tracing, role, closing, hostInfo)).withDeploy(Deploy.local)
final val TransportIn = 0
final val TransportOut = 0
@ -49,7 +50,7 @@ private[akka] object SslTlsCipherActor {
*/
private[akka] class SslTlsCipherActor(settings: ActorMaterializerSettings, sslContext: SSLContext,
firstSession: NegotiateNewSession, tracing: Boolean,
role: Role, closing: Closing)
role: Role, closing: Closing, hostInfo: Option[(String, Int)])
extends Actor with ActorLogging with Pump {
import SslTlsCipherActor._
@ -146,7 +147,11 @@ private[akka] class SslTlsCipherActor(settings: ActorMaterializerSettings, sslCo
transportInChoppingBlock.prepare(transportInBuffer)
val engine: SSLEngine = {
val e = sslContext.createSSLEngine()
val e = hostInfo match {
case Some((hostname, port)) sslContext.createSSLEngine(hostname, port)
case None sslContext.createSSLEngine()
}
e.setUseClientMode(role == Client)
e
}

View file

@ -3,6 +3,9 @@
*/
package akka.stream.io
import java.lang.{ Integer jInteger }
import akka.japi
import akka.stream._
import akka.stream.impl.StreamLayout.Module
import akka.util.ByteString
@ -10,7 +13,6 @@ import javax.net.ssl._
import scala.annotation.varargs
import scala.collection.immutable
import java.security.cert.Certificate
import akka.event.Logging.simpleName
/**
* Stream cipher support based upon JSSE.
@ -63,10 +65,15 @@ object SslTls {
* protocol.
*
* For a description of the `closing` parameter please refer to [[Closing]].
*
* The `hostInfo` parameter allows to optionally specify a pair of hostname and port
* that will be used when creating the SSLEngine with `sslContext.createSslEngine`.
* The SSLEngine may use this information e.g. when an endpoint identification algorithm was
* configured using [[SSLParameters.setEndpointIdentificationAlgorithm]].
*/
def apply(sslContext: SSLContext, firstSession: NegotiateNewSession,
role: Role, closing: Closing = IgnoreComplete): ScalaFlow =
new scaladsl.BidiFlow(TlsModule(Attributes.none, sslContext, firstSession, role, closing))
def apply(sslContext: SSLContext, firstSession: NegotiateNewSession, role: Role,
closing: Closing = IgnoreComplete, hostInfo: Option[(String, Int)] = None): ScalaFlow =
new scaladsl.BidiFlow(TlsModule(Attributes.none, sslContext, firstSession, role, closing, hostInfo))
/**
* Java API: create a StreamTls [[akka.stream.javadsl.BidiFlow]] in client mode. The
@ -92,9 +99,14 @@ object SslTls {
* protocol.
*
* For a description of the `closing` parameter please refer to [[Closing]].
*
* The `hostInfo` parameter allows to optionally specify a pair of hostname and port
* that will be used when creating the SSLEngine with `sslContext.createSslEngine`.
* The SSLEngine may use this information e.g. when an endpoint identification algorithm was
* configured using [[SSLParameters.setEndpointIdentificationAlgorithm]].
*/
def create(sslContext: SSLContext, firstSession: NegotiateNewSession, role: Role, closing: Closing): JavaFlow =
new javadsl.BidiFlow(apply(sslContext, firstSession, role, closing))
def create(sslContext: SSLContext, firstSession: NegotiateNewSession, role: Role, hostInfo: japi.Option[japi.Pair[String, jInteger]], closing: Closing): JavaFlow =
new javadsl.BidiFlow(apply(sslContext, firstSession, role, closing, hostInfo.asScala.map(e (e.first, e.second))))
/**
* INTERNAL API.
@ -103,12 +115,12 @@ object SslTls {
cipherIn: Inlet[ByteString], cipherOut: Outlet[ByteString],
shape: Shape, attributes: Attributes,
sslContext: SSLContext, firstSession: NegotiateNewSession,
role: Role, closing: Closing) extends Module {
role: Role, closing: Closing, hostInfo: Option[(String, Int)]) extends Module {
override def subModules: Set[Module] = Set.empty
override def withAttributes(att: Attributes): Module = copy(attributes = att)
override def carbonCopy: Module = {
val mod = TlsModule(attributes, sslContext, firstSession, role, closing)
val mod = TlsModule(attributes, sslContext, firstSession, role, closing, hostInfo)
if (plainIn == shape.inlets(0)) mod
else mod.replaceShape(mod.shape.asInstanceOf[BidiShape[_, _, _, _]].reversed)
}
@ -123,14 +135,14 @@ object SslTls {
* INTERNAL API.
*/
private[akka] object TlsModule {
def apply(attributes: Attributes, sslContext: SSLContext, firstSession: NegotiateNewSession, role: Role, closing: Closing): TlsModule = {
def apply(attributes: Attributes, sslContext: SSLContext, firstSession: NegotiateNewSession, role: Role, closing: Closing, hostInfo: Option[(String, Int)]): TlsModule = {
val name = attributes.nameOrDefault(s"StreamTls($role)")
val cipherIn = Inlet[ByteString](s"$name.cipherIn")
val cipherOut = Outlet[ByteString](s"$name.cipherOut")
val plainIn = Inlet[SslTlsOutbound](s"$name.transportIn")
val plainOut = Outlet[SslTlsInbound](s"$name.transportOut")
val shape = new BidiShape(plainIn, cipherOut, cipherIn, plainOut)
TlsModule(plainIn, plainOut, cipherIn, cipherOut, shape, attributes, sslContext, firstSession, role, closing)
TlsModule(plainIn, plainOut, cipherIn, cipherOut, shape, attributes, sslContext, firstSession, role, closing, hostInfo)
}
}
}